NOISSUE Comment and bugfix the Resource system
This commit is contained in:
		
				
					committed by
					
						 Petr Mrázek
						Petr Mrázek
					
				
			
			
				
	
			
			
			
						parent
						
							24db645167
						
					
				
				
					commit
					1e51b62c88
				
			| @@ -22,6 +22,7 @@ set(LOGIC_SOURCES | ||||
| 	BaseConfigObject.cpp | ||||
| 	AbstractCommonModel.h | ||||
| 	AbstractCommonModel.cpp | ||||
| 	TypeMagic.h | ||||
|  | ||||
| 	# Prefix tree where node names are strings between separators | ||||
| 	SeparatorPrefixTree.h | ||||
|   | ||||
							
								
								
									
										37
									
								
								logic/TypeMagic.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								logic/TypeMagic.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| #pragma once | ||||
|  | ||||
| namespace TypeMagic | ||||
| { | ||||
| /** "Cleans" the given type T by stripping references (&) and cv-qualifiers (const, volatile) from it | ||||
|  * const int => int | ||||
|  * QString & => QString | ||||
|  * const unsigned long long & => unsigned long long | ||||
|  * | ||||
|  * Usage: | ||||
|  *   using Cleaned = Detail::CleanType<const int>; | ||||
|  *   static_assert(std::is_same<Cleaned, int>, "Cleaned == int"); | ||||
|  */ | ||||
| // the order of remove_cv and remove_reference matters! | ||||
| template <typename T> | ||||
| using CleanType = typename std::remove_cv<typename std::remove_reference<T>::type>::type; | ||||
|  | ||||
| /// For functors (structs with operator()), including lambdas, which in **most** cases are functors | ||||
| /// "Calls" Function<Ret(*)(Arg)> or Function<Ret(C::*)(Arg)> | ||||
| template <typename T> struct Function : public Function<decltype(&T::operator())> {}; | ||||
| /// For function pointers (&function), including static members (&Class::member) | ||||
| template <typename Ret, typename Arg> struct Function<Ret(*)(Arg)> : public Function<Ret(Arg)> {}; | ||||
| /// Default specialization used by others. | ||||
| template <typename Ret, typename Arg> struct Function<Ret(Arg)> | ||||
| { | ||||
| 	using ReturnType = Ret; | ||||
| 	using Argument = Arg; | ||||
| }; | ||||
| /// For member functions. Also used by the lambda overload if the lambda captures [this] | ||||
| template <class C, typename Ret, typename Arg> struct Function<Ret(C::*)(Arg)> : public Function<Ret(Arg)> {}; | ||||
| template <class C, typename Ret, typename Arg> struct Function<Ret(C::*)(Arg) const> : public Function<Ret(Arg)> {}; | ||||
| /// Overload for references | ||||
| template <typename F> struct Function<F&> : public Function<F> {}; | ||||
| /// Overload for rvalues | ||||
| template <typename F> struct Function<F&&> : public Function<F> {}; | ||||
| // for more info: https://functionalcpp.wordpress.com/2013/08/05/function-traits/ | ||||
| } | ||||
| @@ -15,6 +15,7 @@ void IconResourceHandler::setTheme(const QString &theme) | ||||
| { | ||||
| 	m_theme = theme; | ||||
|  | ||||
| 	// notify everyone | ||||
| 	for (auto handler : m_iconHandlers) | ||||
| 	{ | ||||
| 		std::shared_ptr<IconResourceHandler> ptr = handler.lock(); | ||||
| @@ -28,6 +29,7 @@ void IconResourceHandler::setTheme(const QString &theme) | ||||
| void IconResourceHandler::init(std::shared_ptr<ResourceHandler> &ptr) | ||||
| { | ||||
| 	m_iconHandlers.append(std::dynamic_pointer_cast<IconResourceHandler>(ptr)); | ||||
| 	// we always have a result, so lets report it now! | ||||
| 	setResult(get()); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -9,14 +9,17 @@ class IconResourceHandler : public ResourceHandler | ||||
| public: | ||||
| 	explicit IconResourceHandler(const QString &key); | ||||
|  | ||||
| 	/// Sets the current theme and notifies all IconResourceHandlers of the change | ||||
| 	static void setTheme(const QString &theme); | ||||
|  | ||||
| private: | ||||
| 	// we need to keep track of all IconResourceHandlers so that we can update them if the theme changes | ||||
| 	void init(std::shared_ptr<ResourceHandler> &ptr) override; | ||||
| 	static QList<std::weak_ptr<IconResourceHandler>> m_iconHandlers; | ||||
|  | ||||
| 	QString m_key; | ||||
| 	static QString m_theme; | ||||
| 	static QList<std::weak_ptr<IconResourceHandler>> m_iconHandlers; | ||||
|  | ||||
| 	// the workhorse, returns QVariantMap (filename => size) for m_key/m_theme | ||||
| 	QVariant get() const; | ||||
| }; | ||||
|   | ||||
| @@ -6,12 +6,16 @@ | ||||
| #include "IconResourceHandler.h" | ||||
| #include "ResourceObserver.h" | ||||
|  | ||||
| // definition of static members of Resource | ||||
| QMap<QString, std::function<std::shared_ptr<ResourceHandler>(const QString &)>> Resource::m_handlers; | ||||
| QMap<QPair<int, int>, std::function<QVariant(QVariant)>> Resource::m_transfomers; | ||||
| QMap<QString, std::weak_ptr<Resource>> Resource::m_resources; | ||||
|  | ||||
| Resource::Resource(const QString &resource) | ||||
| 	: m_resource(resource) | ||||
| { | ||||
| 	// register default handlers | ||||
| 	// QUESTION: move elsewhere? | ||||
| 	if (!m_handlers.contains("web")) | ||||
| 	{ | ||||
| 		registerHandler<WebResourceHandler>("web"); | ||||
| @@ -21,33 +25,48 @@ Resource::Resource(const QString &resource) | ||||
| 		registerHandler<IconResourceHandler>("icon"); | ||||
| 	} | ||||
|  | ||||
| 	// a valid resource identifier has the format <id>:<data> | ||||
| 	Q_ASSERT(resource.contains(':')); | ||||
| 	// "parse" the resource identifier into id and data | ||||
| 	const QString resourceId = resource.left(resource.indexOf(':')); | ||||
| 	const QString resourceData = resource.mid(resource.indexOf(':') + 1); | ||||
|  | ||||
| 	// create and set up the handler | ||||
| 	Q_ASSERT(m_handlers.contains(resourceId)); | ||||
| 	m_handler = m_handlers.value(resourceId)(resource.mid(resource.indexOf(':') + 1)); | ||||
| 	m_handler = m_handlers.value(resourceId)(resourceData); | ||||
| 	Q_ASSERT(m_handler); | ||||
| 	m_handler->init(m_handler); | ||||
| 	m_handler->setResource(this); | ||||
| 	Q_ASSERT(m_handler); | ||||
| } | ||||
| Resource::~Resource() | ||||
| { | ||||
| 	qDeleteAll(m_observers); | ||||
| } | ||||
|  | ||||
| Resource::Ptr Resource::create(const QString &resource) | ||||
| Resource::Ptr Resource::create(const QString &resource, Ptr placeholder) | ||||
| { | ||||
| 	Resource::Ptr ptr = m_resources.contains(resource) | ||||
| 			? m_resources.value(resource).lock() | ||||
| 	const QString storageId = storageIdentifier(resource, placeholder); | ||||
|  | ||||
| 	// do we already have a resource? even if m_resources contains it it might not be valid any longer (weak_ptr) | ||||
| 	Resource::Ptr ptr = m_resources.contains(storageId) | ||||
| 			? m_resources.value(storageId).lock() | ||||
| 			: nullptr; | ||||
| 	// did we have one? and is it still valid? | ||||
| 	if (!ptr) | ||||
| 	{ | ||||
| 		/* We don't want Resource to have a public constructor, but std::make_shared needs it, | ||||
| 		 * so we create a subclass of Resource here that exposes the constructor as public. | ||||
| 		 * The alternative would be making the allocator for std::make_shared a friend, but it | ||||
| 		 * differs between different STL implementations, so that would be a pain. | ||||
| 		 */ | ||||
| 		struct ConstructableResource : public Resource | ||||
| 		{ | ||||
| 			explicit ConstructableResource(const QString &resource) | ||||
| 				: Resource(resource) {} | ||||
| 		}; | ||||
| 		ptr = std::make_shared<ConstructableResource>(resource); | ||||
| 		m_resources.insert(resource, ptr); | ||||
| 		ptr->m_placeholder = placeholder; | ||||
| 		m_resources.insert(storageId, ptr); | ||||
| 	} | ||||
| 	return ptr; | ||||
| } | ||||
| @@ -56,39 +75,35 @@ Resource::Ptr Resource::applyTo(ResourceObserver *observer) | ||||
| { | ||||
| 	m_observers.append(observer); | ||||
| 	observer->setSource(shared_from_this()); // give the observer a shared_ptr for us so we don't get deleted | ||||
| 	observer->resourceUpdated(); | ||||
| 	return shared_from_this(); | ||||
| 	observer->resourceUpdated(); // ask the observer to poll us immediently, we might already have data | ||||
| 	return shared_from_this(); // allow chaining | ||||
| } | ||||
| Resource::Ptr Resource::applyTo(QObject *target, const char *property) | ||||
| { | ||||
| 	// the cast to ResourceObserver* is required to ensure the right overload gets choosen | ||||
| 	// the cast to ResourceObserver* is required to ensure the right overload gets choosen, | ||||
| 	// since QObjectResourceObserver also inherits from QObject | ||||
| 	return applyTo(static_cast<ResourceObserver *>(new QObjectResourceObserver(target, property))); | ||||
| } | ||||
|  | ||||
| Resource::Ptr Resource::placeholder(Resource::Ptr other) | ||||
| { | ||||
| 	m_placeholder = other; | ||||
| 	for (ResourceObserver *observer : m_observers) | ||||
| 	{ | ||||
| 		observer->resourceUpdated(); | ||||
| 	} | ||||
| 	return shared_from_this(); | ||||
| } | ||||
|  | ||||
| QVariant Resource::getResourceInternal(const int typeId) const | ||||
| { | ||||
| 	// no result (yet), but a placeholder? delegate to the placeholder. | ||||
| 	if (m_handler->result().isNull() && m_placeholder) | ||||
| 	{ | ||||
| 		return m_placeholder->getResourceInternal(typeId); | ||||
| 	} | ||||
| 	const QVariant variant = m_handler->result(); | ||||
| 	const auto typePair = qMakePair(int(variant.type()), typeId); | ||||
|  | ||||
| 	// do we have an explicit transformer? use it. | ||||
| 	if (m_transfomers.contains(typePair)) | ||||
| 	{ | ||||
| 		return m_transfomers.value(typePair)(variant); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		// we do not have an explicit transformer, so we just pass the QVariant, which will automatically | ||||
| 		// transform some types for us (different numbers to each other etc.) | ||||
| 		return variant; | ||||
| 	} | ||||
| } | ||||
| @@ -119,3 +134,15 @@ void Resource::notifyObserverDeleted(ResourceObserver *observer) | ||||
| { | ||||
| 	m_observers.removeAll(observer); | ||||
| } | ||||
|  | ||||
| QString Resource::storageIdentifier(const QString &id, Resource::Ptr placeholder) | ||||
| { | ||||
| 	if (placeholder) | ||||
| 	{ | ||||
| 		return id + '#' + storageIdentifier(placeholder->m_resource, placeholder->m_placeholder); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		return id; | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -7,24 +7,10 @@ | ||||
| #include <memory> | ||||
|  | ||||
| #include "ResourceObserver.h" | ||||
| #include "TypeMagic.h" | ||||
|  | ||||
| class ResourceHandler; | ||||
|  | ||||
| namespace Detail | ||||
| { | ||||
| template <typename T> struct Function : public Function<decltype(&T::operator())> {}; | ||||
| template <typename Ret, typename Arg> struct Function<Ret(*)(Arg)> : public Function<Ret(Arg)> {}; | ||||
| template <typename Ret, typename Arg> struct Function<Ret(Arg)> | ||||
| { | ||||
| 	using ReturnType = Ret; | ||||
| 	using Argument = Arg; | ||||
| }; | ||||
| template <class C, typename Ret, typename Arg> struct Function<Ret(C::*)(Arg)> : public Function<Ret(Arg)> {}; | ||||
| template <class C, typename Ret, typename Arg> struct Function<Ret(C::*)(Arg) const> : public Function<Ret(Arg)> {}; | ||||
| template <typename F> struct Function<F&> : public Function<F> {}; | ||||
| template <typename F> struct Function<F&&> : public Function<F> {}; | ||||
| } | ||||
|  | ||||
| /** Frontend class for resources | ||||
|  * | ||||
|  * Usage: | ||||
| @@ -39,10 +25,11 @@ template <typename F> struct Function<F&&> : public Function<F> {}; | ||||
|  *	placeholder (if present). This means a resource stays valid while it's still used ("applied to" etc.) | ||||
|  *	by something. When nothing uses it anymore it gets deleted. | ||||
|  * | ||||
|  *	\note Always pass resource around using ResourcePtr! Copy and move constructors are disabled for a reason. | ||||
|  *	@note Always pass resource around using Resource::Ptr! Copy and move constructors are disabled for a reason. | ||||
|  */ | ||||
| class Resource : public std::enable_shared_from_this<Resource> | ||||
| { | ||||
| 	// only allow creation from Resource::create and disallow passing around non-pointers | ||||
| 	explicit Resource(const QString &resource); | ||||
| 	Resource(const Resource &) = delete; | ||||
| 	Resource(Resource &&) = delete; | ||||
| @@ -51,11 +38,9 @@ public: | ||||
|  | ||||
| 	~Resource(); | ||||
|  | ||||
| 	/// The returned pointer needs to be stored until either Resource::then is called, or it is used as the argument to Resource::placeholder. | ||||
| 	static Ptr create(const QString &resource); | ||||
|  | ||||
| 	/// This can e.g. be used to set a local icon as the placeholder while a slow (remote) icon is fetched | ||||
| 	Ptr placeholder(Ptr other); | ||||
| 	/// The returned pointer needs to be stored until either Resource::applyTo or Resource::then is called, or it is passed as | ||||
| 	/// a placeholder to Resource::create itself. | ||||
| 	static Ptr create(const QString &resource, Ptr placeholder = nullptr); | ||||
|  | ||||
| 	/// Use these functions to specify what should happen when e.g. the resource changes | ||||
| 	Ptr applyTo(ResourceObserver *observer); | ||||
| @@ -63,30 +48,49 @@ public: | ||||
| 	template<typename Func> | ||||
| 	Ptr then(Func &&func) | ||||
| 	{ | ||||
| 		using Arg = typename std::remove_cv< | ||||
| 			typename std::remove_reference<typename Detail::Function<Func>::Argument>::type | ||||
| 		>::type; | ||||
| 		return applyTo(new FunctionResourceObserver< | ||||
| 					   typename Detail::Function<Func>::ReturnType, | ||||
| 					   Arg, Func | ||||
| 					   >(std::forward<Func>(func))); | ||||
| 		// Arg will be the functions argument with references and cv-qualifiers (const, volatile) removed | ||||
| 		using Arg = TypeMagic::CleanType<typename TypeMagic::Function<Func>::Argument>; | ||||
| 		// Ret will be the functions return type | ||||
| 		using Ret = typename TypeMagic::Function<Func>::ReturnType; | ||||
|  | ||||
| 		// FunctionResourceObserver<ReturnType, ArgumentType, FunctionSignature> | ||||
| 		return applyTo(new FunctionResourceObserver<Ret, Arg, Func>(std::forward<Func>(func))); | ||||
| 	} | ||||
|  | ||||
| 	/// Retrieve the currently active resource. If it's type is different from T a conversion will be attempted. | ||||
| 	template<typename T> | ||||
| 	T getResource() const { return getResourceInternal(qMetaTypeId<T>()).template value<T>(); } | ||||
|  | ||||
| 	/// @internal Used by ResourceObserver and ResourceProxyModel | ||||
| 	QVariant getResourceInternal(const int typeId) const; | ||||
|  | ||||
| 	/** Register a new ResourceHandler. T needs to inherit from ResourceHandler | ||||
| 	 * Usage: Resource::registerHandler<MyResourceHandler>("myid"); | ||||
| 	 */ | ||||
| 	template<typename T> | ||||
| 	static void registerHandler(const QString &id) | ||||
| 	{ | ||||
| 		m_handlers.insert(id, [](const QString &res) { return std::make_shared<T>(res); }); | ||||
| 	} | ||||
| 	/** Register a new resource transformer | ||||
| 	 * Resource transformers are functions that are responsible for converting between different types, | ||||
| 	 * for example converting from a QByteArray to a QPixmap. They are registered "externally" because not | ||||
| 	 * all types might be available in this library, for example gui types like QPixmap. | ||||
| 	 * | ||||
| 	 * Usage: Resource::registerTransformer([](const InputType &type) { return OutputType(type); }); | ||||
| 	 *   This assumes that OutputType has a constructor that takes InputType as an argument. More | ||||
| 	 *   complicated transformers can of course also be registered. | ||||
| 	 * | ||||
| 	 * When a ResourceObserver requests a type that's different from the actual resource type, a matching | ||||
| 	 * transformer will be looked up from the list of transformers. | ||||
| 	 * @note Only one-stage transforms will be performed (you can't registerTransformers for QString => int | ||||
| 	 *       and int => float and expect QString to automatically be transformed into a float. | ||||
| 	 */ | ||||
| 	template<typename Func> | ||||
| 	static void registerTransformer(Func &&func) | ||||
| 	{ | ||||
| 		using Out = typename Detail::Function<Func>::ReturnType; | ||||
| 		using In = typename std::remove_cv<typename std::remove_reference<typename Detail::Function<Func>::Argument>::type>::type; | ||||
| 		using Out = typename TypeMagic::Function<Func>::ReturnType; | ||||
| 		using In = TypeMagic::CleanType<typename TypeMagic::Function<Func>::Argument>; | ||||
| 		static_assert(!std::is_same<Out, In>::value, "It does not make sense to transform a value to itself"); | ||||
| 		m_transfomers.insert(qMakePair(qMetaTypeId<In>(), qMetaTypeId<Out>()), [func](const QVariant &in) | ||||
| 		{ | ||||
| @@ -94,23 +98,33 @@ public: | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| private: | ||||
| private: // half private, implementation details | ||||
| 	friend class ResourceHandler; | ||||
| 	// the following three functions are called by ResourceHandlers | ||||
| 	/** Notifies the observers. They will call Resource::getResourceInternal which will call ResourceHandler::result | ||||
| 	 * or delegate to it's placeholder. | ||||
| 	 */ | ||||
| 	void reportResult(); | ||||
| 	void reportFailure(const QString &reason); | ||||
| 	void reportProgress(const int progress); | ||||
|  | ||||
| 	friend class ResourceObserver; | ||||
| 	/// Removes observer from the list of observers so that we don't attempt to notify something that doesn't exist | ||||
| 	void notifyObserverDeleted(ResourceObserver *observer); | ||||
|  | ||||
| private: | ||||
| private: // truly private | ||||
| 	QList<ResourceObserver *> m_observers; | ||||
| 	std::shared_ptr<ResourceHandler> m_handler = nullptr; | ||||
| 	Ptr m_placeholder = nullptr; | ||||
| 	const QString m_resource; | ||||
|  | ||||
| 	static QString storageIdentifier(const QString &id, Ptr placeholder = nullptr); | ||||
| 	QString storageIdentifier() const; | ||||
|  | ||||
| 	// a list of resource handler factories, registered using registerHandler | ||||
| 	static QMap<QString, std::function<std::shared_ptr<ResourceHandler>(const QString &)>> m_handlers; | ||||
| 	// a list of resource transformers, registered using registerTransformer | ||||
| 	static QMap<QPair<int, int>, std::function<QVariant(QVariant)>> m_transfomers; | ||||
| 	// a list of resources so that we can reuse them | ||||
| 	static QMap<QString, std::weak_ptr<Resource>> m_resources; | ||||
| }; | ||||
|   | ||||
| @@ -17,7 +17,8 @@ public: | ||||
| 	virtual ~ResourceHandler() {} | ||||
|  | ||||
| 	void setResource(Resource *resource) { m_resource = resource; } | ||||
| 	// reimplement this if you need to do something after you have been put in a shared pointer | ||||
| 	/// reimplement this if you need to do something after you have been put in a shared pointer | ||||
| 	// we do this instead of inheriting from std::enable_shared_from_this | ||||
| 	virtual void init(std::shared_ptr<ResourceHandler>&) {} | ||||
|  | ||||
| 	QVariant result() const { return m_result; } | ||||
|   | ||||
| @@ -51,6 +51,11 @@ private: | ||||
| 	QMetaProperty m_property; | ||||
| }; | ||||
|  | ||||
| /** Observer for functions, lambdas etc. | ||||
|  * Template arguments: | ||||
|  *   * We need Ret and Arg in order to create the std::function | ||||
|  *   * We need Func in order to std::forward the function | ||||
|  */ | ||||
| template <typename Ret, typename Arg, typename Func> | ||||
| class FunctionResourceObserver : public ResourceObserver | ||||
| { | ||||
|   | ||||
| @@ -5,8 +5,6 @@ | ||||
| #include "Resource.h" | ||||
| #include "ResourceObserver.h" | ||||
|  | ||||
| //Q_DECLARE_METATYPE(QVector<int>) | ||||
|  | ||||
| class ModelResourceObserver : public ResourceObserver | ||||
| { | ||||
| public: | ||||
| @@ -20,6 +18,7 @@ public: | ||||
| 	{ | ||||
| 		if (m_index.isValid()) | ||||
| 		{ | ||||
| 			// the resource changed, pretend to be the model and notify the views of the update. they will re-poll the model which will return the new resource value | ||||
| 			QMetaObject::invokeMethod(const_cast<QAbstractItemModel *>(m_index.model()), | ||||
| 									  "dataChanged", Qt::QueuedConnection, | ||||
| 									  Q_ARG(QModelIndex, m_index), Q_ARG(QModelIndex, m_index), Q_ARG(QVector<int>, QVector<int>() << m_role)); | ||||
| @@ -39,24 +38,29 @@ ResourceProxyModel::ResourceProxyModel(const int resultTypeId, QObject *parent) | ||||
| QVariant ResourceProxyModel::data(const QModelIndex &proxyIndex, int role) const | ||||
| { | ||||
| 	const QModelIndex mapped = mapToSource(proxyIndex); | ||||
| 	// valid cell that's a Qt::DecorationRole and that contains a non-empty string | ||||
| 	if (mapped.isValid() && role == Qt::DecorationRole && !mapToSource(proxyIndex).data(role).toString().isEmpty()) | ||||
| 	{ | ||||
| 		// do we already have a resource for this index? | ||||
| 		if (!m_resources.contains(mapped)) | ||||
| 		{ | ||||
| 			Resource::Ptr res = Resource::create(mapToSource(proxyIndex).data(role).toString()) | ||||
| 					->applyTo(new ModelResourceObserver(proxyIndex, role)); | ||||
|  | ||||
| 			const QVariant placeholder = mapped.data(PlaceholderRole); | ||||
| 			if (!placeholder.isNull() && placeholder.type() == QVariant::String) | ||||
| 			Resource::Ptr placeholder; | ||||
| 			const QVariant placeholderIdentifier = mapped.data(PlaceholderRole); | ||||
| 			if (!placeholderIdentifier.isNull() && placeholderIdentifier.type() == QVariant::String) | ||||
| 			{ | ||||
| 				res->placeholder(Resource::create(placeholder.toString())); | ||||
| 				placeholder = Resource::create(placeholderIdentifier.toString()); | ||||
| 			} | ||||
|  | ||||
| 			// create the Resource and apply the observer for models | ||||
| 			Resource::Ptr res = Resource::create(mapToSource(proxyIndex).data(role).toString(), placeholder) | ||||
| 					->applyTo(new ModelResourceObserver(proxyIndex, role)); | ||||
|  | ||||
| 			m_resources.insert(mapped, res); | ||||
| 		} | ||||
|  | ||||
| 		return m_resources.value(mapped)->getResourceInternal(m_resultTypeId); | ||||
| 	} | ||||
| 	// otherwise fall back to the source model | ||||
| 	return mapped.data(role); | ||||
| } | ||||
|  | ||||
| @@ -70,7 +74,8 @@ void ResourceProxyModel::setSourceModel(QAbstractItemModel *model) | ||||
| 	{ | ||||
| 		connect(model, &QAbstractItemModel::dataChanged, this, [this](const QModelIndex &tl, const QModelIndex &br, const QVector<int> &roles) | ||||
| 		{ | ||||
| 			if (roles.contains(Qt::DecorationRole) || roles.isEmpty()) | ||||
| 			// invalidate resources so that they will be re-created | ||||
| 			if (roles.contains(Qt::DecorationRole) || roles.contains(PlaceholderRole) || roles.isEmpty()) | ||||
| 			{ | ||||
| 				const QItemSelectionRange range(tl, br); | ||||
| 				for (const QModelIndex &index : range.indexes()) | ||||
| @@ -78,25 +83,6 @@ void ResourceProxyModel::setSourceModel(QAbstractItemModel *model) | ||||
| 					m_resources.remove(index); | ||||
| 				} | ||||
| 			} | ||||
| 			else if (roles.contains(PlaceholderRole)) | ||||
| 			{ | ||||
| 				const QItemSelectionRange range(tl, br); | ||||
| 				for (const QModelIndex &index : range.indexes()) | ||||
| 				{ | ||||
| 					if (m_resources.contains(index)) | ||||
| 					{ | ||||
| 						const QVariant placeholder = index.data(PlaceholderRole); | ||||
| 						if (!placeholder.isNull() && placeholder.type() == QVariant::String) | ||||
| 						{ | ||||
| 							m_resources.value(index)->placeholder(Resource::create(placeholder.toString())); | ||||
| 						} | ||||
| 						else | ||||
| 						{ | ||||
| 							m_resources.value(index)->placeholder(nullptr); | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
| 	QIdentityProxyModel::setSourceModel(model); | ||||
|   | ||||
| @@ -20,6 +20,7 @@ public: | ||||
| 	QVariant data(const QModelIndex &proxyIndex, int role) const override; | ||||
| 	void setSourceModel(QAbstractItemModel *model) override; | ||||
|  | ||||
| 	/// Helper function, usage: m_view->setModel(ResourceProxyModel::mixin<QIcon>(m_model)); | ||||
| 	template <typename T> | ||||
| 	static QAbstractItemModel *mixin(QAbstractItemModel *model) | ||||
| 	{ | ||||
|   | ||||
| @@ -29,6 +29,7 @@ add_unit_test(modutils tst_modutils.cpp) | ||||
| add_unit_test(inifile tst_inifile.cpp) | ||||
| add_unit_test(UpdateChecker tst_UpdateChecker.cpp) | ||||
| add_unit_test(DownloadTask tst_DownloadTask.cpp) | ||||
| add_unit_test(Resource tst_Resource.cpp) | ||||
|  | ||||
| # Tests END # | ||||
|  | ||||
|   | ||||
| @@ -43,8 +43,8 @@ public: | ||||
| class ResourceTest : public QObject | ||||
| { | ||||
| 	Q_OBJECT | ||||
| private | ||||
| slots: | ||||
| 	private | ||||
| 	slots: | ||||
| 	void initTestCase() | ||||
| 	{ | ||||
| 		Resource::registerHandler<DummyStringResourceHandler>("dummy"); | ||||
| @@ -75,10 +75,9 @@ slots: | ||||
|  | ||||
| 	void test_DontRequestPlaceholder() | ||||
| 	{ | ||||
| 		auto resource = Resource::create("dummy:asdf") | ||||
| 		// since dummy:asdf immediently gives a value we should not get the placeholder | ||||
| 		Resource::create("dummy:asdf", Resource::create("dummy:fdsa")) | ||||
| 				->then([](const QString &key) { QCOMPARE(key, QStringLiteral("asdf")); }); | ||||
| 		// the following call should not notify the observer. if it does the above QCOMPARE would fail. | ||||
| 		resource->placeholder(Resource::create("dummy:fdsa")); | ||||
| 	} | ||||
|  | ||||
| 	void test_MergedResources() | ||||
| @@ -94,6 +93,23 @@ slots: | ||||
| 		QVERIFY(r2 != r3); | ||||
| 		QVERIFY(r4 != r3); | ||||
| 	} | ||||
|  | ||||
| 	void test_MergedResourceWithPlaceholder() | ||||
| 	{ | ||||
| 		auto p1 = Resource::create("dummy:placeA"); | ||||
| 		auto p2 = Resource::create("dummy:placeB"); | ||||
|  | ||||
| 		auto r1 = Resource::create("dummy:asdf"); | ||||
| 		auto r2 = Resource::create("dummy:asdf", p1); | ||||
| 		auto r3 = Resource::create("dummy:asdf", p2); | ||||
| 		auto r4 = Resource::create("dummy:asdf", p1); | ||||
|  | ||||
| 		QCOMPARE(r2, r4); | ||||
| 		QVERIFY(r1 != r2); | ||||
| 		QVERIFY(r1 != r3); | ||||
| 		QVERIFY(r1 != r4); | ||||
| 		QVERIFY(r2 != r3); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| QTEST_GUILESS_MAIN(ResourceTest) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user