NOISSUE Comment and bugfix the Resource system
This commit is contained in:
parent
24db645167
commit
1e51b62c88
@ -22,6 +22,7 @@ set(LOGIC_SOURCES
|
|||||||
BaseConfigObject.cpp
|
BaseConfigObject.cpp
|
||||||
AbstractCommonModel.h
|
AbstractCommonModel.h
|
||||||
AbstractCommonModel.cpp
|
AbstractCommonModel.cpp
|
||||||
|
TypeMagic.h
|
||||||
|
|
||||||
# Prefix tree where node names are strings between separators
|
# Prefix tree where node names are strings between separators
|
||||||
SeparatorPrefixTree.h
|
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;
|
m_theme = theme;
|
||||||
|
|
||||||
|
// notify everyone
|
||||||
for (auto handler : m_iconHandlers)
|
for (auto handler : m_iconHandlers)
|
||||||
{
|
{
|
||||||
std::shared_ptr<IconResourceHandler> ptr = handler.lock();
|
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)
|
void IconResourceHandler::init(std::shared_ptr<ResourceHandler> &ptr)
|
||||||
{
|
{
|
||||||
m_iconHandlers.append(std::dynamic_pointer_cast<IconResourceHandler>(ptr));
|
m_iconHandlers.append(std::dynamic_pointer_cast<IconResourceHandler>(ptr));
|
||||||
|
// we always have a result, so lets report it now!
|
||||||
setResult(get());
|
setResult(get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,14 +9,17 @@ class IconResourceHandler : public ResourceHandler
|
|||||||
public:
|
public:
|
||||||
explicit IconResourceHandler(const QString &key);
|
explicit IconResourceHandler(const QString &key);
|
||||||
|
|
||||||
|
/// Sets the current theme and notifies all IconResourceHandlers of the change
|
||||||
static void setTheme(const QString &theme);
|
static void setTheme(const QString &theme);
|
||||||
|
|
||||||
private:
|
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;
|
void init(std::shared_ptr<ResourceHandler> &ptr) override;
|
||||||
|
static QList<std::weak_ptr<IconResourceHandler>> m_iconHandlers;
|
||||||
|
|
||||||
QString m_key;
|
QString m_key;
|
||||||
static QString m_theme;
|
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;
|
QVariant get() const;
|
||||||
};
|
};
|
||||||
|
@ -6,12 +6,16 @@
|
|||||||
#include "IconResourceHandler.h"
|
#include "IconResourceHandler.h"
|
||||||
#include "ResourceObserver.h"
|
#include "ResourceObserver.h"
|
||||||
|
|
||||||
|
// definition of static members of Resource
|
||||||
QMap<QString, std::function<std::shared_ptr<ResourceHandler>(const QString &)>> Resource::m_handlers;
|
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<QPair<int, int>, std::function<QVariant(QVariant)>> Resource::m_transfomers;
|
||||||
QMap<QString, std::weak_ptr<Resource>> Resource::m_resources;
|
QMap<QString, std::weak_ptr<Resource>> Resource::m_resources;
|
||||||
|
|
||||||
Resource::Resource(const QString &resource)
|
Resource::Resource(const QString &resource)
|
||||||
|
: m_resource(resource)
|
||||||
{
|
{
|
||||||
|
// register default handlers
|
||||||
|
// QUESTION: move elsewhere?
|
||||||
if (!m_handlers.contains("web"))
|
if (!m_handlers.contains("web"))
|
||||||
{
|
{
|
||||||
registerHandler<WebResourceHandler>("web");
|
registerHandler<WebResourceHandler>("web");
|
||||||
@ -21,33 +25,48 @@ Resource::Resource(const QString &resource)
|
|||||||
registerHandler<IconResourceHandler>("icon");
|
registerHandler<IconResourceHandler>("icon");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// a valid resource identifier has the format <id>:<data>
|
||||||
Q_ASSERT(resource.contains(':'));
|
Q_ASSERT(resource.contains(':'));
|
||||||
|
// "parse" the resource identifier into id and data
|
||||||
const QString resourceId = resource.left(resource.indexOf(':'));
|
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));
|
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->init(m_handler);
|
||||||
m_handler->setResource(this);
|
m_handler->setResource(this);
|
||||||
Q_ASSERT(m_handler);
|
|
||||||
}
|
}
|
||||||
Resource::~Resource()
|
Resource::~Resource()
|
||||||
{
|
{
|
||||||
qDeleteAll(m_observers);
|
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)
|
const QString storageId = storageIdentifier(resource, placeholder);
|
||||||
? m_resources.value(resource).lock()
|
|
||||||
|
// 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;
|
: nullptr;
|
||||||
|
// did we have one? and is it still valid?
|
||||||
if (!ptr)
|
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
|
struct ConstructableResource : public Resource
|
||||||
{
|
{
|
||||||
explicit ConstructableResource(const QString &resource)
|
explicit ConstructableResource(const QString &resource)
|
||||||
: Resource(resource) {}
|
: Resource(resource) {}
|
||||||
};
|
};
|
||||||
ptr = std::make_shared<ConstructableResource>(resource);
|
ptr = std::make_shared<ConstructableResource>(resource);
|
||||||
m_resources.insert(resource, ptr);
|
ptr->m_placeholder = placeholder;
|
||||||
|
m_resources.insert(storageId, ptr);
|
||||||
}
|
}
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
@ -56,39 +75,35 @@ Resource::Ptr Resource::applyTo(ResourceObserver *observer)
|
|||||||
{
|
{
|
||||||
m_observers.append(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->setSource(shared_from_this()); // give the observer a shared_ptr for us so we don't get deleted
|
||||||
observer->resourceUpdated();
|
observer->resourceUpdated(); // ask the observer to poll us immediently, we might already have data
|
||||||
return shared_from_this();
|
return shared_from_this(); // allow chaining
|
||||||
}
|
}
|
||||||
Resource::Ptr Resource::applyTo(QObject *target, const char *property)
|
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)));
|
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
|
QVariant Resource::getResourceInternal(const int typeId) const
|
||||||
{
|
{
|
||||||
|
// no result (yet), but a placeholder? delegate to the placeholder.
|
||||||
if (m_handler->result().isNull() && m_placeholder)
|
if (m_handler->result().isNull() && m_placeholder)
|
||||||
{
|
{
|
||||||
return m_placeholder->getResourceInternal(typeId);
|
return m_placeholder->getResourceInternal(typeId);
|
||||||
}
|
}
|
||||||
const QVariant variant = m_handler->result();
|
const QVariant variant = m_handler->result();
|
||||||
const auto typePair = qMakePair(int(variant.type()), typeId);
|
const auto typePair = qMakePair(int(variant.type()), typeId);
|
||||||
|
|
||||||
|
// do we have an explicit transformer? use it.
|
||||||
if (m_transfomers.contains(typePair))
|
if (m_transfomers.contains(typePair))
|
||||||
{
|
{
|
||||||
return m_transfomers.value(typePair)(variant);
|
return m_transfomers.value(typePair)(variant);
|
||||||
}
|
}
|
||||||
else
|
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;
|
return variant;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,3 +134,15 @@ void Resource::notifyObserverDeleted(ResourceObserver *observer)
|
|||||||
{
|
{
|
||||||
m_observers.removeAll(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 <memory>
|
||||||
|
|
||||||
#include "ResourceObserver.h"
|
#include "ResourceObserver.h"
|
||||||
|
#include "TypeMagic.h"
|
||||||
|
|
||||||
class ResourceHandler;
|
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
|
/** Frontend class for resources
|
||||||
*
|
*
|
||||||
* Usage:
|
* 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.)
|
* 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.
|
* 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>
|
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);
|
explicit Resource(const QString &resource);
|
||||||
Resource(const Resource &) = delete;
|
Resource(const Resource &) = delete;
|
||||||
Resource(Resource &&) = delete;
|
Resource(Resource &&) = delete;
|
||||||
@ -51,11 +38,9 @@ public:
|
|||||||
|
|
||||||
~Resource();
|
~Resource();
|
||||||
|
|
||||||
/// The returned pointer needs to be stored until either Resource::then is called, or it is used as the argument to Resource::placeholder.
|
/// The returned pointer needs to be stored until either Resource::applyTo or Resource::then is called, or it is passed as
|
||||||
static Ptr create(const QString &resource);
|
/// a placeholder to Resource::create itself.
|
||||||
|
static Ptr create(const QString &resource, Ptr placeholder = nullptr);
|
||||||
/// 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);
|
|
||||||
|
|
||||||
/// Use these functions to specify what should happen when e.g. the resource changes
|
/// Use these functions to specify what should happen when e.g. the resource changes
|
||||||
Ptr applyTo(ResourceObserver *observer);
|
Ptr applyTo(ResourceObserver *observer);
|
||||||
@ -63,30 +48,49 @@ public:
|
|||||||
template<typename Func>
|
template<typename Func>
|
||||||
Ptr then(Func &&func)
|
Ptr then(Func &&func)
|
||||||
{
|
{
|
||||||
using Arg = typename std::remove_cv<
|
// Arg will be the functions argument with references and cv-qualifiers (const, volatile) removed
|
||||||
typename std::remove_reference<typename Detail::Function<Func>::Argument>::type
|
using Arg = TypeMagic::CleanType<typename TypeMagic::Function<Func>::Argument>;
|
||||||
>::type;
|
// Ret will be the functions return type
|
||||||
return applyTo(new FunctionResourceObserver<
|
using Ret = typename TypeMagic::Function<Func>::ReturnType;
|
||||||
typename Detail::Function<Func>::ReturnType,
|
|
||||||
Arg, Func
|
// FunctionResourceObserver<ReturnType, ArgumentType, FunctionSignature>
|
||||||
>(std::forward<Func>(func)));
|
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.
|
/// Retrieve the currently active resource. If it's type is different from T a conversion will be attempted.
|
||||||
template<typename T>
|
template<typename T>
|
||||||
T getResource() const { return getResourceInternal(qMetaTypeId<T>()).template value<T>(); }
|
T getResource() const { return getResourceInternal(qMetaTypeId<T>()).template value<T>(); }
|
||||||
|
|
||||||
|
/// @internal Used by ResourceObserver and ResourceProxyModel
|
||||||
QVariant getResourceInternal(const int typeId) const;
|
QVariant getResourceInternal(const int typeId) const;
|
||||||
|
|
||||||
|
/** Register a new ResourceHandler. T needs to inherit from ResourceHandler
|
||||||
|
* Usage: Resource::registerHandler<MyResourceHandler>("myid");
|
||||||
|
*/
|
||||||
template<typename T>
|
template<typename T>
|
||||||
static void registerHandler(const QString &id)
|
static void registerHandler(const QString &id)
|
||||||
{
|
{
|
||||||
m_handlers.insert(id, [](const QString &res) { return std::make_shared<T>(res); });
|
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>
|
template<typename Func>
|
||||||
static void registerTransformer(Func &&func)
|
static void registerTransformer(Func &&func)
|
||||||
{
|
{
|
||||||
using Out = typename Detail::Function<Func>::ReturnType;
|
using Out = typename TypeMagic::Function<Func>::ReturnType;
|
||||||
using In = typename std::remove_cv<typename std::remove_reference<typename Detail::Function<Func>::Argument>::type>::type;
|
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");
|
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)
|
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;
|
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 reportResult();
|
||||||
void reportFailure(const QString &reason);
|
void reportFailure(const QString &reason);
|
||||||
void reportProgress(const int progress);
|
void reportProgress(const int progress);
|
||||||
|
|
||||||
friend class ResourceObserver;
|
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);
|
void notifyObserverDeleted(ResourceObserver *observer);
|
||||||
|
|
||||||
private:
|
private: // truly private
|
||||||
QList<ResourceObserver *> m_observers;
|
QList<ResourceObserver *> m_observers;
|
||||||
std::shared_ptr<ResourceHandler> m_handler = nullptr;
|
std::shared_ptr<ResourceHandler> m_handler = nullptr;
|
||||||
Ptr m_placeholder = 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
|
// a list of resource handler factories, registered using registerHandler
|
||||||
static QMap<QString, std::function<std::shared_ptr<ResourceHandler>(const QString &)>> m_handlers;
|
static QMap<QString, std::function<std::shared_ptr<ResourceHandler>(const QString &)>> m_handlers;
|
||||||
// a list of resource transformers, registered using registerTransformer
|
// a list of resource transformers, registered using registerTransformer
|
||||||
static QMap<QPair<int, int>, std::function<QVariant(QVariant)>> m_transfomers;
|
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;
|
static QMap<QString, std::weak_ptr<Resource>> m_resources;
|
||||||
};
|
};
|
||||||
|
@ -17,7 +17,8 @@ public:
|
|||||||
virtual ~ResourceHandler() {}
|
virtual ~ResourceHandler() {}
|
||||||
|
|
||||||
void setResource(Resource *resource) { m_resource = resource; }
|
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>&) {}
|
virtual void init(std::shared_ptr<ResourceHandler>&) {}
|
||||||
|
|
||||||
QVariant result() const { return m_result; }
|
QVariant result() const { return m_result; }
|
||||||
|
@ -51,6 +51,11 @@ private:
|
|||||||
QMetaProperty m_property;
|
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>
|
template <typename Ret, typename Arg, typename Func>
|
||||||
class FunctionResourceObserver : public ResourceObserver
|
class FunctionResourceObserver : public ResourceObserver
|
||||||
{
|
{
|
||||||
|
@ -5,8 +5,6 @@
|
|||||||
#include "Resource.h"
|
#include "Resource.h"
|
||||||
#include "ResourceObserver.h"
|
#include "ResourceObserver.h"
|
||||||
|
|
||||||
//Q_DECLARE_METATYPE(QVector<int>)
|
|
||||||
|
|
||||||
class ModelResourceObserver : public ResourceObserver
|
class ModelResourceObserver : public ResourceObserver
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -20,6 +18,7 @@ public:
|
|||||||
{
|
{
|
||||||
if (m_index.isValid())
|
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()),
|
QMetaObject::invokeMethod(const_cast<QAbstractItemModel *>(m_index.model()),
|
||||||
"dataChanged", Qt::QueuedConnection,
|
"dataChanged", Qt::QueuedConnection,
|
||||||
Q_ARG(QModelIndex, m_index), Q_ARG(QModelIndex, m_index), Q_ARG(QVector<int>, QVector<int>() << m_role));
|
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
|
QVariant ResourceProxyModel::data(const QModelIndex &proxyIndex, int role) const
|
||||||
{
|
{
|
||||||
const QModelIndex mapped = mapToSource(proxyIndex);
|
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())
|
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))
|
if (!m_resources.contains(mapped))
|
||||||
{
|
{
|
||||||
Resource::Ptr res = Resource::create(mapToSource(proxyIndex).data(role).toString())
|
Resource::Ptr placeholder;
|
||||||
->applyTo(new ModelResourceObserver(proxyIndex, role));
|
const QVariant placeholderIdentifier = mapped.data(PlaceholderRole);
|
||||||
|
if (!placeholderIdentifier.isNull() && placeholderIdentifier.type() == QVariant::String)
|
||||||
const QVariant placeholder = mapped.data(PlaceholderRole);
|
|
||||||
if (!placeholder.isNull() && placeholder.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);
|
m_resources.insert(mapped, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
return m_resources.value(mapped)->getResourceInternal(m_resultTypeId);
|
return m_resources.value(mapped)->getResourceInternal(m_resultTypeId);
|
||||||
}
|
}
|
||||||
|
// otherwise fall back to the source model
|
||||||
return mapped.data(role);
|
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)
|
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);
|
const QItemSelectionRange range(tl, br);
|
||||||
for (const QModelIndex &index : range.indexes())
|
for (const QModelIndex &index : range.indexes())
|
||||||
@ -78,25 +83,6 @@ void ResourceProxyModel::setSourceModel(QAbstractItemModel *model)
|
|||||||
m_resources.remove(index);
|
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);
|
QIdentityProxyModel::setSourceModel(model);
|
||||||
|
@ -20,6 +20,7 @@ public:
|
|||||||
QVariant data(const QModelIndex &proxyIndex, int role) const override;
|
QVariant data(const QModelIndex &proxyIndex, int role) const override;
|
||||||
void setSourceModel(QAbstractItemModel *model) override;
|
void setSourceModel(QAbstractItemModel *model) override;
|
||||||
|
|
||||||
|
/// Helper function, usage: m_view->setModel(ResourceProxyModel::mixin<QIcon>(m_model));
|
||||||
template <typename T>
|
template <typename T>
|
||||||
static QAbstractItemModel *mixin(QAbstractItemModel *model)
|
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(inifile tst_inifile.cpp)
|
||||||
add_unit_test(UpdateChecker tst_UpdateChecker.cpp)
|
add_unit_test(UpdateChecker tst_UpdateChecker.cpp)
|
||||||
add_unit_test(DownloadTask tst_DownloadTask.cpp)
|
add_unit_test(DownloadTask tst_DownloadTask.cpp)
|
||||||
|
add_unit_test(Resource tst_Resource.cpp)
|
||||||
|
|
||||||
# Tests END #
|
# Tests END #
|
||||||
|
|
||||||
|
@ -43,8 +43,8 @@ public:
|
|||||||
class ResourceTest : public QObject
|
class ResourceTest : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
private
|
private
|
||||||
slots:
|
slots:
|
||||||
void initTestCase()
|
void initTestCase()
|
||||||
{
|
{
|
||||||
Resource::registerHandler<DummyStringResourceHandler>("dummy");
|
Resource::registerHandler<DummyStringResourceHandler>("dummy");
|
||||||
@ -75,10 +75,9 @@ slots:
|
|||||||
|
|
||||||
void test_DontRequestPlaceholder()
|
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")); });
|
->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()
|
void test_MergedResources()
|
||||||
@ -94,6 +93,23 @@ slots:
|
|||||||
QVERIFY(r2 != r3);
|
QVERIFY(r2 != r3);
|
||||||
QVERIFY(r4 != 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)
|
QTEST_GUILESS_MAIN(ResourceTest)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user