forked from quickshell/quickshell
		
	core/objectrepeater: add ObjectRepeater
This commit is contained in:
		
							parent
							
								
									d8fa9e7bb3
								
							
						
					
					
						commit
						09d8a7a07d
					
				
					 4 changed files with 279 additions and 0 deletions
				
			
		| 
						 | 
				
			
			@ -29,6 +29,7 @@ qt_add_library(quickshell-core STATIC
 | 
			
		|||
	model.cpp
 | 
			
		||||
	elapsedtimer.cpp
 | 
			
		||||
	desktopentry.cpp
 | 
			
		||||
	objectrepeater.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
set_source_files_properties(main.cpp PROPERTIES COMPILE_DEFINITIONS GIT_REVISION="${GIT_REVISION}")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,5 +21,6 @@ headers = [
 | 
			
		|||
	"model.hpp",
 | 
			
		||||
	"elapsedtimer.hpp",
 | 
			
		||||
	"desktopentry.hpp",
 | 
			
		||||
	"objectrepeater.hpp",
 | 
			
		||||
]
 | 
			
		||||
-----
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										191
									
								
								src/core/objectrepeater.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								src/core/objectrepeater.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,191 @@
 | 
			
		|||
#include "objectrepeater.hpp"
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
#include <qabstractitemmodel.h>
 | 
			
		||||
#include <qcontainerfwd.h>
 | 
			
		||||
#include <qhash.h>
 | 
			
		||||
#include <qlogging.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qqmlcomponent.h>
 | 
			
		||||
#include <qqmlcontext.h>
 | 
			
		||||
#include <qqmlengine.h>
 | 
			
		||||
#include <qqmllist.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <qtypes.h>
 | 
			
		||||
#include <qvariant.h>
 | 
			
		||||
 | 
			
		||||
QVariant ObjectRepeater::model() const { return this->mModel; }
 | 
			
		||||
 | 
			
		||||
void ObjectRepeater::setModel(QVariant model) {
 | 
			
		||||
	if (model == this->mModel) return;
 | 
			
		||||
 | 
			
		||||
	if (this->itemModel != nullptr) {
 | 
			
		||||
		QObject::disconnect(this->itemModel, nullptr, this, nullptr);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->mModel = std::move(model);
 | 
			
		||||
	emit this->modelChanged();
 | 
			
		||||
	this->reloadElements();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectRepeater::onModelDestroyed() {
 | 
			
		||||
	this->mModel.clear();
 | 
			
		||||
	this->itemModel = nullptr;
 | 
			
		||||
	emit this->modelChanged();
 | 
			
		||||
	this->reloadElements();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QQmlComponent* ObjectRepeater::delegate() const { return this->mDelegate; }
 | 
			
		||||
 | 
			
		||||
void ObjectRepeater::setDelegate(QQmlComponent* delegate) {
 | 
			
		||||
	if (delegate == this->mDelegate) return;
 | 
			
		||||
 | 
			
		||||
	if (this->mDelegate != nullptr) {
 | 
			
		||||
		QObject::disconnect(this->mDelegate, nullptr, this, nullptr);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->mDelegate = delegate;
 | 
			
		||||
 | 
			
		||||
	if (delegate != nullptr) {
 | 
			
		||||
		QObject::connect(
 | 
			
		||||
		    this->mDelegate,
 | 
			
		||||
		    &QObject::destroyed,
 | 
			
		||||
		    this,
 | 
			
		||||
		    &ObjectRepeater::onDelegateDestroyed
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	emit this->delegateChanged();
 | 
			
		||||
	this->reloadElements();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectRepeater::onDelegateDestroyed() {
 | 
			
		||||
	this->mDelegate = nullptr;
 | 
			
		||||
	emit this->delegateChanged();
 | 
			
		||||
	this->reloadElements();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectRepeater::reloadElements() {
 | 
			
		||||
	for (auto i = this->valuesList.length() - 1; i >= 0; i--) {
 | 
			
		||||
		this->removeComponent(i);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (this->mDelegate == nullptr || !this->mModel.isValid()) return;
 | 
			
		||||
 | 
			
		||||
	if (this->mModel.canConvert<QAbstractItemModel*>()) {
 | 
			
		||||
		auto* model = this->mModel.value<QAbstractItemModel*>();
 | 
			
		||||
		this->itemModel = model;
 | 
			
		||||
 | 
			
		||||
		this->insertModelElements(model, 0, model->rowCount() - 1); // -1 is fine
 | 
			
		||||
 | 
			
		||||
		// clang-format off
 | 
			
		||||
		QObject::connect(model, &QObject::destroyed, this, &ObjectRepeater::onModelDestroyed);
 | 
			
		||||
		QObject::connect(model, &QAbstractItemModel::rowsInserted, this, &ObjectRepeater::onModelRowsInserted);
 | 
			
		||||
		QObject::connect(model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ObjectRepeater::onModelRowsAboutToBeRemoved);
 | 
			
		||||
		QObject::connect(model, &QAbstractItemModel::rowsMoved, this, &ObjectRepeater::onModelRowsMoved);
 | 
			
		||||
		QObject::connect(model, &QAbstractItemModel::modelAboutToBeReset, this, &ObjectRepeater::onModelAboutToBeReset);
 | 
			
		||||
		// clang-format on
 | 
			
		||||
	} else if (this->mModel.canConvert<QQmlListReference>()) {
 | 
			
		||||
		auto values = this->mModel.value<QQmlListReference>();
 | 
			
		||||
		auto len = values.count();
 | 
			
		||||
 | 
			
		||||
		for (auto i = 0; i != len; i++) {
 | 
			
		||||
			this->insertComponent(i, {{"modelData", QVariant::fromValue(values.at(i))}});
 | 
			
		||||
		}
 | 
			
		||||
	} else if (this->mModel.canConvert<QVector<QVariant>>()) {
 | 
			
		||||
		auto values = this->mModel.value<QVector<QVariant>>();
 | 
			
		||||
 | 
			
		||||
		for (auto& value: values) {
 | 
			
		||||
			this->insertComponent(this->valuesList.length(), {{"modelData", value}});
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		qCritical() << this
 | 
			
		||||
		            << "Cannot create components as the model is not compatible:" << this->mModel;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectRepeater::insertModelElements(QAbstractItemModel* model, int first, int last) {
 | 
			
		||||
	auto roles = model->roleNames();
 | 
			
		||||
	auto roleDataVec = QVector<QModelRoleData>();
 | 
			
		||||
	for (auto id: roles.keys()) {
 | 
			
		||||
		roleDataVec.push_back(QModelRoleData(id));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto values = QModelRoleDataSpan(roleDataVec);
 | 
			
		||||
	auto props = QVariantMap();
 | 
			
		||||
 | 
			
		||||
	for (auto i = first; i != last + 1; i++) {
 | 
			
		||||
		auto index = model->index(i, 0);
 | 
			
		||||
		model->multiData(index, values);
 | 
			
		||||
 | 
			
		||||
		for (auto [id, name]: roles.asKeyValueRange()) {
 | 
			
		||||
			props.insert(name, *values.dataForRole(id));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this->insertComponent(i, props);
 | 
			
		||||
 | 
			
		||||
		props.clear();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectRepeater::onModelRowsInserted(const QModelIndex& parent, int first, int last) {
 | 
			
		||||
	if (parent != QModelIndex()) return;
 | 
			
		||||
 | 
			
		||||
	this->insertModelElements(this->itemModel, first, last);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectRepeater::onModelRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last) {
 | 
			
		||||
	if (parent != QModelIndex()) return;
 | 
			
		||||
 | 
			
		||||
	for (auto i = last; i != first - 1; i--) {
 | 
			
		||||
		this->removeComponent(i);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectRepeater::onModelRowsMoved(
 | 
			
		||||
    const QModelIndex& sourceParent,
 | 
			
		||||
    int sourceStart,
 | 
			
		||||
    int sourceEnd,
 | 
			
		||||
    const QModelIndex& destParent,
 | 
			
		||||
    int destStart
 | 
			
		||||
) {
 | 
			
		||||
	auto hasSource = sourceParent != QModelIndex();
 | 
			
		||||
	auto hasDest = destParent != QModelIndex();
 | 
			
		||||
 | 
			
		||||
	if (!hasSource && !hasDest) return;
 | 
			
		||||
 | 
			
		||||
	if (hasSource) {
 | 
			
		||||
		this->onModelRowsAboutToBeRemoved(sourceParent, sourceStart, sourceEnd);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (hasDest) {
 | 
			
		||||
		this->onModelRowsInserted(destParent, destStart, destStart + (sourceEnd - sourceStart));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectRepeater::onModelAboutToBeReset() {
 | 
			
		||||
	auto last = static_cast<int>(this->valuesList.length() - 1);
 | 
			
		||||
	this->onModelRowsAboutToBeRemoved(QModelIndex(), 0, last); // -1 is fine
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectRepeater::insertComponent(qsizetype index, const QVariantMap& properties) {
 | 
			
		||||
	auto* context = QQmlEngine::contextForObject(this);
 | 
			
		||||
	auto* instance = this->mDelegate->createWithInitialProperties(properties, context);
 | 
			
		||||
 | 
			
		||||
	if (instance == nullptr) {
 | 
			
		||||
		qWarning().noquote() << this->mDelegate->errorString();
 | 
			
		||||
		qWarning() << this << "failed to create object for model data" << properties;
 | 
			
		||||
	} else {
 | 
			
		||||
		QQmlEngine::setObjectOwnership(instance, QQmlEngine::CppOwnership);
 | 
			
		||||
		instance->setParent(this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->insertObject(instance, index);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectRepeater::removeComponent(qsizetype index) {
 | 
			
		||||
	auto* instance = this->valuesList.at(index);
 | 
			
		||||
	delete instance;
 | 
			
		||||
 | 
			
		||||
	this->removeAt(index);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										86
									
								
								src/core/objectrepeater.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/core/objectrepeater.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,86 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <qabstractitemmodel.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qqmlcomponent.h>
 | 
			
		||||
#include <qqmlintegration.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <qtypes.h>
 | 
			
		||||
#include <qvariant.h>
 | 
			
		||||
 | 
			
		||||
#include "model.hpp"
 | 
			
		||||
 | 
			
		||||
///! A Repeater / for loop / map for non Item derived objects.
 | 
			
		||||
/// The ObjectRepeater creates instances of the provided delegate for every entry in the
 | 
			
		||||
/// given model, similarly to a [Repeater] but for non visual types.
 | 
			
		||||
///
 | 
			
		||||
/// [Repeater]: https://doc.qt.io/qt-6/qml-qtquick-repeater.html
 | 
			
		||||
class ObjectRepeater: public ObjectModel<QObject> {
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
	/// The model providing data to the ObjectRepeater.
 | 
			
		||||
	///
 | 
			
		||||
	/// Currently accepted model types are QML `list<T>` lists, javascript arrays,
 | 
			
		||||
	/// and [QAbstractListModel] derived models, though only one column will be repeated
 | 
			
		||||
	/// from the latter.
 | 
			
		||||
	///
 | 
			
		||||
	/// Note: [ObjectModel] is a [QAbstractListModel] with a single column.
 | 
			
		||||
	///
 | 
			
		||||
	/// [QAbstractListModel]: https://doc.qt.io/qt-6/qabstractlistmodel.html
 | 
			
		||||
	/// [ObjectModel]: ../objectmodel
 | 
			
		||||
	Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged);
 | 
			
		||||
	/// The delegate component to repeat.
 | 
			
		||||
	///
 | 
			
		||||
	/// The delegate is given the same properties as in a Repeater, except `index` which
 | 
			
		||||
	/// is not currently implemented.
 | 
			
		||||
	///
 | 
			
		||||
	/// If the model is a `list<T>` or javascript array, a `modelData` property will be
 | 
			
		||||
	/// exposed containing the entry from the model. If the model is a [QAbstractListModel],
 | 
			
		||||
	/// the roles from the model will be exposed.
 | 
			
		||||
	///
 | 
			
		||||
	/// Note: [ObjectModel] has a single role named `modelData` for compatibility with normal lists.
 | 
			
		||||
	///
 | 
			
		||||
	/// [QAbstractListModel]: https://doc.qt.io/qt-6/qabstractlistmodel.html
 | 
			
		||||
	/// [ObjectModel]: ../objectmodel
 | 
			
		||||
	Q_PROPERTY(QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged);
 | 
			
		||||
	Q_CLASSINFO("DefaultProperty", "delegate");
 | 
			
		||||
	QML_ELEMENT;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit ObjectRepeater(QObject* parent = nullptr): ObjectModel(parent) {}
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] QVariant model() const;
 | 
			
		||||
	void setModel(QVariant model);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] QQmlComponent* delegate() const;
 | 
			
		||||
	void setDelegate(QQmlComponent* delegate);
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void modelChanged();
 | 
			
		||||
	void delegateChanged();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onDelegateDestroyed();
 | 
			
		||||
	void onModelDestroyed();
 | 
			
		||||
	void onModelRowsInserted(const QModelIndex& parent, int first, int last);
 | 
			
		||||
	void onModelRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last);
 | 
			
		||||
 | 
			
		||||
	void onModelRowsMoved(
 | 
			
		||||
	    const QModelIndex& sourceParent,
 | 
			
		||||
	    int sourceStart,
 | 
			
		||||
	    int sourceEnd,
 | 
			
		||||
	    const QModelIndex& destParent,
 | 
			
		||||
	    int destStart
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	void onModelAboutToBeReset();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void reloadElements();
 | 
			
		||||
	void insertModelElements(QAbstractItemModel* model, int first, int last);
 | 
			
		||||
	void insertComponent(qsizetype index, const QVariantMap& properties);
 | 
			
		||||
	void removeComponent(qsizetype index);
 | 
			
		||||
 | 
			
		||||
	QVariant mModel;
 | 
			
		||||
	QAbstractItemModel* itemModel = nullptr;
 | 
			
		||||
	QQmlComponent* mDelegate = nullptr;
 | 
			
		||||
};
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue