NOISSUE reorganize and document libraries

This commit is contained in:
Petr Mrázek
2016-04-10 15:53:05 +02:00
parent 47e37635f5
commit b6d455a02b
368 changed files with 159 additions and 275 deletions

View File

@ -0,0 +1,155 @@
#include "Resource.h"
#include <QDebug>
#include "ResourceObserver.h"
#include "ResourceHandler.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;
struct NullResourceResult {};
Q_DECLARE_METATYPE(NullResourceResult)
class NullResourceHandler : public ResourceHandler
{
public:
explicit NullResourceHandler()
{
setResult(QVariant::fromValue<NullResourceResult>(NullResourceResult()));
}
};
Resource::Resource(const QString &resource)
: m_resource(resource)
{
if (!resource.isEmpty())
{
// 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)(resourceData);
}
else
{
m_handler = std::make_shared<NullResourceHandler>();
}
Q_ASSERT(m_handler);
m_handler->init(m_handler);
m_handler->setResource(this);
}
Resource::~Resource()
{
qDeleteAll(m_observers);
}
Resource::Ptr Resource::create(const QString &resource, Ptr placeholder)
{
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);
ptr->m_placeholder = placeholder;
m_resources.insert(storageId, ptr);
}
return ptr;
}
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(); // 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,
// since QObjectResourceObserver also inherits from QObject
return applyTo(static_cast<ResourceObserver *>(new QObjectResourceObserver(target, property)));
}
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;
}
}
void Resource::reportResult()
{
for (ResourceObserver *observer : m_observers)
{
observer->resourceUpdated();
}
}
void Resource::reportFailure(const QString &reason)
{
for (ResourceObserver *observer : m_observers)
{
observer->setFailure(reason);
}
}
void Resource::reportProgress(const int progress)
{
for (ResourceObserver *observer : m_observers)
{
observer->setProgress(progress);
}
}
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;
}
}

View File

@ -0,0 +1,132 @@
#pragma once
#include <QString>
#include <QMap>
#include <QVariant>
#include <functional>
#include <memory>
#include "ResourceObserver.h"
#include "TypeMagic.h"
#include "multimc_logic_export.h"
class ResourceHandler;
/** Frontend class for resources
*
* Usage:
* Resource::create("icon:noaccount")->applyTo(accountsAction);
* Resource::create("web:http://asdf.com/image.png")->applyTo(imageLbl)->placeholder(Resource::create("icon:loading"));
*
* Memory management:
* Resource caches ResourcePtrs using weak pointers, so while a resource is still existing
* when a new resource is created the resources will be the same (including the same handler).
*
* ResourceObservers keep a shared pointer to the resource, as does the Resource itself to it's
* 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 Resource::Ptr! Copy and move constructors are disabled for a reason.
*/
class MULTIMC_LOGIC_EXPORT 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;
public:
using Ptr = std::shared_ptr<Resource>;
~Resource();
/// 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);
Ptr applyTo(QObject *target, const char *property = nullptr);
template<typename Func>
Ptr then(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 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)
{
return QVariant::fromValue<Out>(func(in.value<In>()));
});
}
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: // 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;
};

View File

@ -0,0 +1,28 @@
#include "ResourceHandler.h"
#include "Resource.h"
void ResourceHandler::setResult(const QVariant &result)
{
m_result = result;
if (m_resource)
{
m_resource->reportResult();
}
}
void ResourceHandler::setFailure(const QString &reason)
{
if (m_resource)
{
m_resource->reportFailure(reason);
}
}
void ResourceHandler::setProgress(const int progress)
{
if (m_resource)
{
m_resource->reportProgress(progress);
}
}

View File

@ -0,0 +1,36 @@
#pragma once
#include <QVariant>
#include <memory>
#include "multimc_logic_export.h"
class Resource;
/** Base class for things that can retrieve a resource.
*
* Subclass, provide a constructor that takes a single QString as argument, and
* call Resource::registerHandler<MyResourceHandler>("<id>"), where <id> is the
* prefix of the resource ("web", "icon", etc.)
*/
class MULTIMC_LOGIC_EXPORT ResourceHandler
{
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
// 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; }
protected: // use these methods to notify the resource of changes
void setResult(const QVariant &result);
void setFailure(const QString &reason);
void setProgress(const int progress);
private:
QVariant m_result;
Resource *m_resource = nullptr;
};

View File

@ -0,0 +1,55 @@
#include "ResourceObserver.h"
#include <QDebug>
#include "Resource.h"
static const char *defaultPropertyForTarget(QObject *target)
{
if (target->inherits("QLabel"))
{
return "pixmap";
}
else if (target->inherits("QAction") ||
target->inherits("QMenu") ||
target->inherits("QAbstractButton"))
{
return "icon";
}
// for unit tests
else if (target->inherits("DummyObserverObject"))
{
return "property";
}
else
{
Q_ASSERT_X(false, "ResourceObserver.cpp: defaultPropertyForTarget", "Unrecognized QObject subclass");
return nullptr;
}
}
QObjectResourceObserver::QObjectResourceObserver(QObject *target, const char *property)
: QObject(target), m_target(target)
{
const QMetaObject *mo = m_target->metaObject();
m_property = mo->property(mo->indexOfProperty(
property ?
property
: defaultPropertyForTarget(target)));
}
void QObjectResourceObserver::resourceUpdated()
{
m_property.write(m_target, getInternal(m_property.type()));
}
ResourceObserver::~ResourceObserver()
{
m_resource->notifyObserverDeleted(this);
}
QVariant ResourceObserver::getInternal(const int typeId) const
{
Q_ASSERT(m_resource);
return m_resource->getResourceInternal(typeId);
}

View File

@ -0,0 +1,73 @@
#pragma once
#include <memory>
#include <functional>
#include <QObject>
#include <QMetaProperty>
#include "multimc_logic_export.h"
class QVariant;
class Resource;
/// Base class for things that can use a resource
class MULTIMC_LOGIC_EXPORT ResourceObserver
{
public:
virtual ~ResourceObserver();
protected: // these methods are called by the Resource when something changes
virtual void resourceUpdated() = 0;
virtual void setFailure(const QString &) {}
virtual void setProgress(const int) {}
private:
friend class Resource;
void setSource(std::shared_ptr<Resource> resource) { m_resource = resource; }
protected:
template<typename T>
T get() const { return getInternal(qMetaTypeId<T>()).template value<T>(); }
QVariant getInternal(const int typeId) const;
private:
std::shared_ptr<Resource> m_resource;
};
/** Observer for QObject properties
*
* Give it a target and the name of a property, and that property will be set when the resource changes.
*
* If no name is given an attempt to find a default property for some common classes is done.
*/
class MULTIMC_LOGIC_EXPORT QObjectResourceObserver : public QObject, public ResourceObserver
{
public:
explicit QObjectResourceObserver(QObject *target, const char *property = nullptr);
void resourceUpdated() override;
private:
QObject *m_target;
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
{
std::function<Ret(Arg)> m_function;
public:
template <typename T>
explicit FunctionResourceObserver(T &&func)
: m_function(std::forward<Func>(func)) {}
void resourceUpdated() override
{
m_function(get<Arg>());
}
};

View File

@ -0,0 +1,89 @@
#include "ResourceProxyModel.h"
#include <QItemSelectionRange>
#include "Resource.h"
#include "ResourceObserver.h"
class ModelResourceObserver : public ResourceObserver
{
public:
explicit ModelResourceObserver(const QModelIndex &index, const int role)
: m_index(index), m_role(role)
{
qRegisterMetaType<QVector<int>>("QVector<int>");
}
void resourceUpdated() override
{
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));
}
}
private:
QPersistentModelIndex m_index;
int m_role;
};
ResourceProxyModel::ResourceProxyModel(const int resultTypeId, QObject *parent)
: QIdentityProxyModel(parent), m_resultTypeId(resultTypeId)
{
}
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 placeholder;
const QVariant placeholderIdentifier = mapped.data(PlaceholderRole);
if (!placeholderIdentifier.isNull() && placeholderIdentifier.type() == QVariant::String)
{
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);
}
void ResourceProxyModel::setSourceModel(QAbstractItemModel *model)
{
if (sourceModel())
{
disconnect(sourceModel(), 0, this, 0);
}
if (model)
{
connect(model, &QAbstractItemModel::dataChanged, this, [this](const QModelIndex &tl, const QModelIndex &br, const QVector<int> &roles)
{
// 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())
{
m_resources.remove(index);
}
}
});
}
QIdentityProxyModel::setSourceModel(model);
}

View File

@ -0,0 +1,39 @@
#pragma once
#include <QIdentityProxyModel>
#include <memory>
#include "multimc_logic_export.h"
/// Convenience proxy model that transforms resource identifiers (strings) for Qt::DecorationRole into other types.
class MULTIMC_LOGIC_EXPORT ResourceProxyModel : public QIdentityProxyModel
{
Q_OBJECT
public:
// resultTypeId is found using qMetaTypeId<T>()
explicit ResourceProxyModel(const int resultTypeId, QObject *parent = nullptr);
enum
{
// provide this role from your model if you want to show a placeholder
PlaceholderRole = Qt::UserRole + 0xabc // some random offset to not collide with other stuff
};
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)
{
ResourceProxyModel *proxy = new ResourceProxyModel(qMetaTypeId<T>(), model);
proxy->setSourceModel(model);
return proxy;
}
private:
// mutable because it needs to be available from the const data()
mutable QMap<QPersistentModelIndex, std::shared_ptr<class Resource>> m_resources;
const int m_resultTypeId;
};