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
 | 
						model.cpp
 | 
				
			||||||
	elapsedtimer.cpp
 | 
						elapsedtimer.cpp
 | 
				
			||||||
	desktopentry.cpp
 | 
						desktopentry.cpp
 | 
				
			||||||
 | 
						objectrepeater.cpp
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set_source_files_properties(main.cpp PROPERTIES COMPILE_DEFINITIONS GIT_REVISION="${GIT_REVISION}")
 | 
					set_source_files_properties(main.cpp PROPERTIES COMPILE_DEFINITIONS GIT_REVISION="${GIT_REVISION}")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,5 +21,6 @@ headers = [
 | 
				
			||||||
	"model.hpp",
 | 
						"model.hpp",
 | 
				
			||||||
	"elapsedtimer.hpp",
 | 
						"elapsedtimer.hpp",
 | 
				
			||||||
	"desktopentry.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