16 changed files with 553 additions and 19 deletions
			
			
		| @ -0,0 +1,31 @@ | |||||
|  | #include "containerviewmodel.h" | ||||
|  | 
 | ||||
|  | ContainerViewModel::ContainerViewModel(QObject *parent) : | ||||
|  | 	ViewModel(parent) | ||||
|  | {} | ||||
|  | 
 | ||||
|  | ContainerViewModel::~ContainerViewModel() | ||||
|  | { | ||||
|  | 	qInfo(Q_FUNC_INFO); | ||||
|  | } | ||||
|  | 
 | ||||
|  | QString ContainerViewModel::vmType() const | ||||
|  | { | ||||
|  | 	return QString::fromUtf8(QMetaType::typeName(qMetaTypeId<ChildViewModel*>())); | ||||
|  | } | ||||
|  | 
 | ||||
|  | void ContainerViewModel::loadChild() | ||||
|  | { | ||||
|  | 	show<ChildViewModel>(); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | ChildViewModel::ChildViewModel(QObject *parent) : | ||||
|  | 	ViewModel(parent) | ||||
|  | {} | ||||
|  | 
 | ||||
|  | ChildViewModel::~ChildViewModel() | ||||
|  | { | ||||
|  | 	qInfo(Q_FUNC_INFO); | ||||
|  | } | ||||
| @ -0,0 +1,34 @@ | |||||
|  | #ifndef CONTAINERVIEWMODEL_H | ||||
|  | #define CONTAINERVIEWMODEL_H | ||||
|  | 
 | ||||
|  | #include <QtMvvmCore/ViewModel> | ||||
|  | 
 | ||||
|  | class ChildViewModel : public QtMvvm::ViewModel | ||||
|  | { | ||||
|  | 	Q_OBJECT | ||||
|  | 
 | ||||
|  | public: | ||||
|  | 	Q_INVOKABLE explicit ChildViewModel(QObject *parent = nullptr); | ||||
|  | 	~ChildViewModel(); | ||||
|  | }; | ||||
|  | 
 | ||||
|  | class ContainerViewModel : public QtMvvm::ViewModel | ||||
|  | { | ||||
|  | 	Q_OBJECT | ||||
|  | 
 | ||||
|  | 	Q_PROPERTY(QString vmType READ vmType CONSTANT) | ||||
|  | 
 | ||||
|  | public: | ||||
|  | 	Q_INVOKABLE explicit ContainerViewModel(QObject *parent = nullptr); | ||||
|  | 	~ContainerViewModel(); | ||||
|  | 
 | ||||
|  | 	QString vmType() const; | ||||
|  | 
 | ||||
|  | public Q_SLOTS: | ||||
|  | 	void loadChild(); | ||||
|  | }; | ||||
|  | 
 | ||||
|  | Q_DECLARE_METATYPE(ChildViewModel*) | ||||
|  | Q_DECLARE_METATYPE(ContainerViewModel*) | ||||
|  | 
 | ||||
|  | #endif // CONTAINERVIEWMODEL_H
 | ||||
| @ -0,0 +1,29 @@ | |||||
|  | import QtQuick 2.10 | ||||
|  | import QtQuick.Controls 2.3 | ||||
|  | import de.skycoder42.QtMvvm.Core 1.1 | ||||
|  | import de.skycoder42.QtMvvm.Quick 1.1 | ||||
|  | import de.skycoder42.QtMvvm.Sample 1.1 | ||||
|  | 
 | ||||
|  | Rectangle { | ||||
|  | 	property ChildViewModel viewModel: null | ||||
|  | 
 | ||||
|  | 	color: "red" | ||||
|  | 
 | ||||
|  | 	property bool dummyClosed: false | ||||
|  | 
 | ||||
|  | 	function closeAction() { | ||||
|  | 		if(dummyClosed) | ||||
|  | 			return false; | ||||
|  | 		else { | ||||
|  | 			dummyClosed = true; | ||||
|  | 			label.text = qsTr("I was closed!") | ||||
|  | 			return true; | ||||
|  | 		} | ||||
|  | 	} | ||||
|  | 
 | ||||
|  | 	Label { | ||||
|  | 		id: label | ||||
|  | 		anchors.centerIn: parent | ||||
|  | 		text: qsTr("I am the child!") | ||||
|  | 	} | ||||
|  | } | ||||
| @ -0,0 +1,71 @@ | |||||
|  | import QtQuick 2.10 | ||||
|  | import QtQuick.Controls 2.3 | ||||
|  | import QtQuick.Layouts 1.3 | ||||
|  | import de.skycoder42.QtMvvm.Core 1.1 | ||||
|  | import de.skycoder42.QtMvvm.Quick 1.1 | ||||
|  | import de.skycoder42.QtMvvm.Sample 1.1 | ||||
|  | 
 | ||||
|  | Page { | ||||
|  | 	id: containerView | ||||
|  | 	property ContainerViewModel viewModel: null | ||||
|  | 
 | ||||
|  | 	function presentItem(item) { | ||||
|  | 		return viewPlaceholder.presentItem(item); | ||||
|  | 	} | ||||
|  | 
 | ||||
|  | 	function closeAction() { | ||||
|  | 		return viewPlaceholder.closeAction(); | ||||
|  | 	} | ||||
|  | 
 | ||||
|  | 	header: ContrastToolBar { | ||||
|  | 		ToolBarLabel { | ||||
|  | 			text: qsTr("View Container") | ||||
|  | 			anchors.fill: parent | ||||
|  | 		} | ||||
|  | 	} | ||||
|  | 
 | ||||
|  | 	PresenterProgress {} | ||||
|  | 
 | ||||
|  | 	Pane { | ||||
|  | 		anchors.fill: parent | ||||
|  | 
 | ||||
|  | 		ColumnLayout { | ||||
|  | 			anchors.fill: parent | ||||
|  | 
 | ||||
|  | 			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 { | ||||
|  | 				Layout.fillWidth: true | ||||
|  | 				text: viewPlaceholder.loadedView ? qsTr("Discard child") : qsTr("Show Child") | ||||
|  | 				onClicked: viewPlaceholder.loadedView ? viewPlaceholder.discardView() : viewModel.loadChild(); | ||||
|  | 			} | ||||
|  | 		} | ||||
|  | 	} | ||||
|  | } | ||||
| @ -0,0 +1,201 @@ | |||||
|  | #include "qqmlviewplaceholder.h" | ||||
|  | #include <QtCore/QMetaMethod> | ||||
|  | #include <QtQml/QQmlInfo> | ||||
|  | #include <QtQml/QQmlProperty> | ||||
|  | #include <QtMvvmCore/CoreApp> | ||||
|  | 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); | ||||
|  | 
 | ||||
|  | 	// size changes
 | ||||
|  | 	connect(this, &QQmlViewPlaceholder::widthChanged, | ||||
|  | 			this, &QQmlViewPlaceholder::resizeView); | ||||
|  | 	connect(this, &QQmlViewPlaceholder::heightChanged, | ||||
|  | 			this, &QQmlViewPlaceholder::resizeView); | ||||
|  | 	connect(this, &QQmlViewPlaceholder::autoResizeViewChanged, | ||||
|  | 			this, &QQmlViewPlaceholder::resizeView); | ||||
|  | } | ||||
|  | 
 | ||||
|  | bool QQmlViewPlaceholder::presentItem(const QVariant &item) | ||||
|  | { | ||||
|  | 	// check if the parameter is valid
 | ||||
|  | 	auto quickItem = item.value<QQuickItem*>(); | ||||
|  | 	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
 | ||||
|  | 			_loadedView->setVisible(false); | ||||
|  | 			_loadedView->deleteLater(); | ||||
|  | 			_loadedView = nullptr; | ||||
|  | 		} else { | ||||
|  | 			qmlWarning(this) << R"(A view has already been presented. Discard it first via "discardView" or set "replaceViews" to true)"; | ||||
|  | 			return false; | ||||
|  | 		} | ||||
|  | 	} | ||||
|  | 
 | ||||
|  | 	// add
 | ||||
|  | 	_loadedView = quickItem; | ||||
|  | 	quickItem->setParent(this); | ||||
|  | 	quickItem->setParentItem(this); | ||||
|  | 	resizeView(); | ||||
|  | 	quickItem->setVisible(true); | ||||
|  | 
 | ||||
|  | 	// hide all children
 | ||||
|  | 	for(auto child : childItems()) { | ||||
|  | 		if(child != _loadedView) | ||||
|  | 			child->setVisible(false); | ||||
|  | 	} | ||||
|  | 
 | ||||
|  | 	emit loadedViewChanged(_loadedView); | ||||
|  | 	return true; | ||||
|  | } | ||||
|  | 
 | ||||
|  | bool QQmlViewPlaceholder::closeAction() | ||||
|  | { | ||||
|  | 	if(!_loadedView) | ||||
|  | 		return false; | ||||
|  | 
 | ||||
|  | 	// call close on the view
 | ||||
|  | 	auto vMetaObject = _loadedView->metaObject(); | ||||
|  | 	auto cMethodIndex = vMetaObject->indexOfMethod("closeAction()"); | ||||
|  | 	if(cMethodIndex != -1) { | ||||
|  | 		auto cMethod = vMetaObject->method(cMethodIndex); | ||||
|  | 		QVariant ok = false; | ||||
|  | 		cMethod.invoke(_loadedView, Q_RETURN_ARG(QVariant, ok)); | ||||
|  | 		if(ok.toBool()) | ||||
|  | 			return true; | ||||
|  | 	} | ||||
|  | 
 | ||||
|  | 	// close the view itself (if wished)
 | ||||
|  | 	if(_closeViewOnAction) { | ||||
|  | 		discardView(); | ||||
|  | 		return true; | ||||
|  | 	} | ||||
|  | 
 | ||||
|  | 	// otherwise: nothing closed
 | ||||
|  | 	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<QQuickItem*>(_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
 | ||||
|  | 	_loadedView->setVisible(false); | ||||
|  | 
 | ||||
|  | 	// show all children again
 | ||||
|  | 	for(auto child : childItems()) { | ||||
|  | 		if(child != _loadedView) | ||||
|  | 			child->setVisible(true); | ||||
|  | 	} | ||||
|  | 
 | ||||
|  | 	// now delete it
 | ||||
|  | 	_loadedView->deleteLater(); | ||||
|  | 	_loadedView = nullptr; | ||||
|  | 	emit loadedViewChanged(nullptr); | ||||
|  | } | ||||
|  | 
 | ||||
|  | void QQmlViewPlaceholder::parentItemVmChanged(ViewModel *viewModel) | ||||
|  | { | ||||
|  | 	// set vm without clearing the connection
 | ||||
|  | 	_clearParentVmCon = false; | ||||
|  | 	setParentViewModel(viewModel); | ||||
|  | 	_clearParentVmCon = true; | ||||
|  | } | ||||
|  | 
 | ||||
|  | void QQmlViewPlaceholder::presentIfReady() | ||||
|  | { | ||||
|  | 	if(_isReady && | ||||
|  | 	   _autoPresent && | ||||
|  | 	   !_loadedView && | ||||
|  | 	   _parentViewModel && | ||||
|  | 	   !_viewModelType.isEmpty()) | ||||
|  | 		presentView(); | ||||
|  | } | ||||
|  | 
 | ||||
|  | void QQmlViewPlaceholder::resizeView() | ||||
|  | { | ||||
|  | 	if(_loadedView && _autoResizeView) { | ||||
|  | 		_loadedView->setWidth(width()); | ||||
|  | 		_loadedView->setHeight(height()); | ||||
|  | 	} | ||||
|  | } | ||||
|  | 
 | ||||
|  | void QQmlViewPlaceholder::getParentViewModel() | ||||
|  | { | ||||
|  | 	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<ViewModel*>(); | ||||
|  | 	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)); | ||||
|  | } | ||||
| @ -0,0 +1,77 @@ | |||||
|  | #ifndef QTMVVM_QQMLVIEWPLACEHOLDER_H | ||||
|  | #define QTMVVM_QQMLVIEWPLACEHOLDER_H | ||||
|  | 
 | ||||
|  | #include <QtQuick/QQuickItem> | ||||
|  | 
 | ||||
|  | #include <QtMvvmCore/ViewModel> | ||||
|  | 
 | ||||
|  | namespace QtMvvm { | ||||
|  | 
 | ||||
|  | class QQmlViewPlaceholder : public QQuickItem | ||||
|  | { | ||||
|  | 	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) | ||||
|  | 	Q_PROPERTY(bool autoResizeView MEMBER _autoResizeView NOTIFY autoResizeViewChanged) | ||||
|  | 	Q_PROPERTY(bool replaceViews MEMBER _replaceViews NOTIFY replaceViewsChanged) | ||||
|  | 	Q_PROPERTY(bool closeViewOnAction MEMBER _closeViewOnAction NOTIFY closeViewOnActionChanged) | ||||
|  | 
 | ||||
|  | 	Q_PROPERTY(QQuickItem* loadedView READ loadedView NOTIFY loadedViewChanged) | ||||
|  | 
 | ||||
|  | public: | ||||
|  | 	explicit QQmlViewPlaceholder(QQuickItem *parent = nullptr); | ||||
|  | 
 | ||||
|  | 	Q_INVOKABLE bool presentItem(const QVariant &item); | ||||
|  | 	Q_INVOKABLE bool closeAction(); | ||||
|  | 
 | ||||
|  | 	void setParentViewModel(ViewModel *parentViewModel); | ||||
|  | 	QQuickItem* loadedView() const; | ||||
|  | 
 | ||||
|  | 	void componentComplete() override; | ||||
|  | 
 | ||||
|  | public Q_SLOTS: | ||||
|  | 	void presentView(); | ||||
|  | 	void discardView(); | ||||
|  | 
 | ||||
|  | Q_SIGNALS: | ||||
|  | 	void viewModelTypeChanged(const QString &viewModelType); | ||||
|  | 	void showParamsChanged(const QVariantHash &showParams); | ||||
|  | 	void parentViewModelChanged(QtMvvm::ViewModel* parentViewModel); | ||||
|  | 	void autoPresentChanged(bool autoPresent); | ||||
|  | 	void autoResizeViewChanged(bool autoResizeView); | ||||
|  | 	void replaceViewsChanged(bool replaceViews); | ||||
|  | 	void closeViewOnActionChanged(bool closeViewOnAction); | ||||
|  | 	void loadedViewChanged(QQuickItem* loadedView); | ||||
|  | 
 | ||||
|  | private Q_SLOTS: | ||||
|  | 	void parentItemVmChanged(QtMvvm::ViewModel *viewModel); | ||||
|  | 	void presentIfReady(); | ||||
|  | 
 | ||||
|  | 	void resizeView(); | ||||
|  | 
 | ||||
|  | private: | ||||
|  | 	QString _viewModelType; | ||||
|  | 	QVariantHash _showParams; | ||||
|  | 	ViewModel *_parentViewModel = nullptr; | ||||
|  | 	bool _autoPresent = true; | ||||
|  | 	bool _autoResizeView = true; | ||||
|  | 	bool _replaceViews = false; | ||||
|  | 	bool _closeViewOnAction = false; | ||||
|  | 
 | ||||
|  | 	QPointer<QQuickItem> _loadedView = nullptr; | ||||
|  | 
 | ||||
|  | 	bool _isReady = false; | ||||
|  | 	bool _clearParentVmCon = true; | ||||
|  | 	QMetaObject::Connection _parentVmCon; | ||||
|  | 
 | ||||
|  | 	void getParentViewModel(); | ||||
|  | 	void connectSizeChanges(); | ||||
|  | 	void disconnectSizeChanges(); | ||||
|  | }; | ||||
|  | 
 | ||||
|  | } | ||||
|  | 
 | ||||
|  | #endif // QTMVVM_QQMLVIEWPLACEHOLDER_H
 | ||||
					Loading…
					
					
				
		Reference in new issue