From 3a8b238052163952831fb5924b2483a375e86ebd Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Thu, 28 May 2015 19:38:29 +0200 Subject: NOISSUE Various changes from multiauth that are unrelated to it --- logic/resources/IconResourceHandler.cpp | 60 ++++++++++++++++ logic/resources/IconResourceHandler.h | 22 ++++++ logic/resources/Resource.cpp | 121 ++++++++++++++++++++++++++++++++ logic/resources/Resource.h | 116 ++++++++++++++++++++++++++++++ logic/resources/ResourceHandler.cpp | 28 ++++++++ logic/resources/ResourceHandler.h | 33 +++++++++ logic/resources/ResourceObserver.cpp | 55 +++++++++++++++ logic/resources/ResourceObserver.h | 67 ++++++++++++++++++ logic/resources/ResourceProxyModel.cpp | 103 +++++++++++++++++++++++++++ logic/resources/ResourceProxyModel.h | 36 ++++++++++ logic/resources/WebResourceHandler.cpp | 67 ++++++++++++++++++ logic/resources/WebResourceHandler.h | 23 ++++++ 12 files changed, 731 insertions(+) create mode 100644 logic/resources/IconResourceHandler.cpp create mode 100644 logic/resources/IconResourceHandler.h create mode 100644 logic/resources/Resource.cpp create mode 100644 logic/resources/Resource.h create mode 100644 logic/resources/ResourceHandler.cpp create mode 100644 logic/resources/ResourceHandler.h create mode 100644 logic/resources/ResourceObserver.cpp create mode 100644 logic/resources/ResourceObserver.h create mode 100644 logic/resources/ResourceProxyModel.cpp create mode 100644 logic/resources/ResourceProxyModel.h create mode 100644 logic/resources/WebResourceHandler.cpp create mode 100644 logic/resources/WebResourceHandler.h (limited to 'logic/resources') diff --git a/logic/resources/IconResourceHandler.cpp b/logic/resources/IconResourceHandler.cpp new file mode 100644 index 00000000..d47dcc3d --- /dev/null +++ b/logic/resources/IconResourceHandler.cpp @@ -0,0 +1,60 @@ +#include "IconResourceHandler.h" + +#include +#include + +QString IconResourceHandler::m_theme = "multimc"; +QList> IconResourceHandler::m_iconHandlers; + +IconResourceHandler::IconResourceHandler(const QString &key) + : m_key(key) +{ +} + +void IconResourceHandler::setTheme(const QString &theme) +{ + m_theme = theme; + + for (auto handler : m_iconHandlers) + { + std::shared_ptr ptr = handler.lock(); + if (ptr) + { + ptr->setResult(ptr->get()); + } + } +} + +void IconResourceHandler::init(std::shared_ptr &ptr) +{ + m_iconHandlers.append(std::dynamic_pointer_cast(ptr)); + setResult(get()); +} + +QVariant IconResourceHandler::get() const +{ + const QDir iconsDir = QDir(":/icons/" + m_theme); + + QVariantMap out; + for (const QFileInfo &sizeInfo : iconsDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) + { + const QDir dir = QDir(sizeInfo.absoluteFilePath()); + const QString dirName = sizeInfo.fileName(); + const int size = dirName.left(dirName.indexOf('x')).toInt(); + if (dir.exists(m_key + ".png") && dirName != "scalable") + { + out.insert(dir.absoluteFilePath(m_key + ".png"), size); + } + else if (dir.exists(m_key + ".svg") && dirName == "scalable") + { + out.insert(dir.absoluteFilePath(m_key + ".svg"), size); + } + } + + if (out.isEmpty()) + { + qWarning() << "Couldn't find any icons for" << m_key; + } + + return out; +} diff --git a/logic/resources/IconResourceHandler.h b/logic/resources/IconResourceHandler.h new file mode 100644 index 00000000..dedfecb2 --- /dev/null +++ b/logic/resources/IconResourceHandler.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include "ResourceHandler.h" + +class IconResourceHandler : public ResourceHandler +{ +public: + explicit IconResourceHandler(const QString &key); + + static void setTheme(const QString &theme); + +private: + void init(std::shared_ptr &ptr) override; + + QString m_key; + static QString m_theme; + static QList> m_iconHandlers; + + QVariant get() const; +}; diff --git a/logic/resources/Resource.cpp b/logic/resources/Resource.cpp new file mode 100644 index 00000000..16ed3d2d --- /dev/null +++ b/logic/resources/Resource.cpp @@ -0,0 +1,121 @@ +#include "Resource.h" + +#include + +#include "WebResourceHandler.h" +#include "IconResourceHandler.h" +#include "ResourceObserver.h" + +QMap(const QString &)>> Resource::m_handlers; +QMap, std::function> Resource::m_transfomers; +QMap> Resource::m_resources; + +Resource::Resource(const QString &resource) +{ + if (!m_handlers.contains("web")) + { + registerHandler("web"); + } + if (!m_handlers.contains("icon")) + { + registerHandler("icon"); + } + + Q_ASSERT(resource.contains(':')); + const QString resourceId = resource.left(resource.indexOf(':')); + Q_ASSERT(m_handlers.contains(resourceId)); + m_handler = m_handlers.value(resourceId)(resource.mid(resource.indexOf(':') + 1)); + 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 ptr = m_resources.contains(resource) + ? m_resources.value(resource).lock() + : nullptr; + if (!ptr) + { + struct ConstructableResource : public Resource + { + explicit ConstructableResource(const QString &resource) + : Resource(resource) {} + }; + ptr = std::make_shared(resource); + m_resources.insert(resource, 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(); + return shared_from_this(); +} +Resource::Ptr Resource::applyTo(QObject *target, const char *property) +{ + // the cast to ResourceObserver* is required to ensure the right overload gets choosen + return applyTo(static_cast(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 +{ + 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); + if (m_transfomers.contains(typePair)) + { + return m_transfomers.value(typePair)(variant); + } + else + { + 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); +} diff --git a/logic/resources/Resource.h b/logic/resources/Resource.h new file mode 100644 index 00000000..d566b2a2 --- /dev/null +++ b/logic/resources/Resource.h @@ -0,0 +1,116 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "ResourceObserver.h" + +class ResourceHandler; + +namespace Detail +{ +template struct Function : public Function {}; +template struct Function : public Function {}; +template struct Function +{ + using ReturnType = Ret; + using Argument = Arg; +}; +template struct Function : public Function {}; +template struct Function : public Function {}; +template struct Function : public Function {}; +template struct Function : public Function {}; +} + +/** 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 ResourcePtr! Copy and move constructors are disabled for a reason. + */ +class Resource : public std::enable_shared_from_this +{ + explicit Resource(const QString &resource); + Resource(const Resource &) = delete; + Resource(Resource &&) = delete; +public: + using Ptr = std::shared_ptr; + + ~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); + + /// 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 + Ptr then(Func &&func) + { + using Arg = typename std::remove_cv< + typename std::remove_reference::Argument>::type + >::type; + return applyTo(new FunctionResourceObserver< + typename Detail::Function::ReturnType, + Arg, Func + >(std::forward(func))); + } + + /// Retrieve the currently active resource. If it's type is different from T a conversion will be attempted. + template + T getResource() const { return getResourceInternal(qMetaTypeId()).template value(); } + QVariant getResourceInternal(const int typeId) const; + + template + static void registerHandler(const QString &id) + { + m_handlers.insert(id, [](const QString &res) { return std::make_shared(res); }); + } + template + static void registerTransformer(Func &&func) + { + using Out = typename Detail::Function::ReturnType; + using In = typename std::remove_cv::Argument>::type>::type; + static_assert(!std::is_same::value, "It does not make sense to transform a value to itself"); + m_transfomers.insert(qMakePair(qMetaTypeId(), qMetaTypeId()), [func](const QVariant &in) + { + return QVariant::fromValue(func(in.value())); + }); + } + +private: + friend class ResourceHandler; + void reportResult(); + void reportFailure(const QString &reason); + void reportProgress(const int progress); + + friend class ResourceObserver; + void notifyObserverDeleted(ResourceObserver *observer); + +private: + QList m_observers; + std::shared_ptr m_handler = nullptr; + Ptr m_placeholder = nullptr; + + // a list of resource handler factories, registered using registerHandler + static QMap(const QString &)>> m_handlers; + // a list of resource transformers, registered using registerTransformer + static QMap, std::function> m_transfomers; + static QMap> m_resources; +}; diff --git a/logic/resources/ResourceHandler.cpp b/logic/resources/ResourceHandler.cpp new file mode 100644 index 00000000..46a4422c --- /dev/null +++ b/logic/resources/ResourceHandler.cpp @@ -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); + } +} diff --git a/logic/resources/ResourceHandler.h b/logic/resources/ResourceHandler.h new file mode 100644 index 00000000..c1105efc --- /dev/null +++ b/logic/resources/ResourceHandler.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +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(""), where is the + * prefix of the resource ("web", "icon", etc.) + */ +class 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 + virtual void init(std::shared_ptr&) {} + + 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; +}; diff --git a/logic/resources/ResourceObserver.cpp b/logic/resources/ResourceObserver.cpp new file mode 100644 index 00000000..4f168fd2 --- /dev/null +++ b/logic/resources/ResourceObserver.cpp @@ -0,0 +1,55 @@ +#include "ResourceObserver.h" + +#include + +#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); +} diff --git a/logic/resources/ResourceObserver.h b/logic/resources/ResourceObserver.h new file mode 100644 index 00000000..27430d42 --- /dev/null +++ b/logic/resources/ResourceObserver.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include + +#include +#include + +class QVariant; +class Resource; + +/// Base class for things that can use a resource +class 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) { m_resource = resource; } + +protected: + template + T get() const { return getInternal(qMetaTypeId()).template value(); } + QVariant getInternal(const int typeId) const; + +private: + std::shared_ptr 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 QObjectResourceObserver : public QObject, public ResourceObserver +{ +public: + explicit QObjectResourceObserver(QObject *target, const char *property = nullptr); + + void resourceUpdated() override; + +private: + QObject *m_target; + QMetaProperty m_property; +}; + +template +class FunctionResourceObserver : public ResourceObserver +{ + std::function m_function; +public: + template + explicit FunctionResourceObserver(T &&func) + : m_function(std::forward(func)) {} + + void resourceUpdated() override + { + m_function(get()); + } +}; diff --git a/logic/resources/ResourceProxyModel.cpp b/logic/resources/ResourceProxyModel.cpp new file mode 100644 index 00000000..6ff11367 --- /dev/null +++ b/logic/resources/ResourceProxyModel.cpp @@ -0,0 +1,103 @@ +#include "ResourceProxyModel.h" + +#include + +#include "Resource.h" +#include "ResourceObserver.h" + +//Q_DECLARE_METATYPE(QVector) + +class ModelResourceObserver : public ResourceObserver +{ +public: + explicit ModelResourceObserver(const QModelIndex &index, const int role) + : m_index(index), m_role(role) + { + qRegisterMetaType>("QVector"); + } + + void resourceUpdated() override + { + if (m_index.isValid()) + { + QMetaObject::invokeMethod(const_cast(m_index.model()), + "dataChanged", Qt::QueuedConnection, + Q_ARG(QModelIndex, m_index), Q_ARG(QModelIndex, m_index), Q_ARG(QVector, QVector() << 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); + if (mapped.isValid() && role == Qt::DecorationRole && !mapToSource(proxyIndex).data(role).toString().isEmpty()) + { + 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) + { + res->placeholder(Resource::create(placeholder.toString())); + } + + m_resources.insert(mapped, res); + } + + return m_resources.value(mapped)->getResourceInternal(m_resultTypeId); + } + 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 &roles) + { + if (roles.contains(Qt::DecorationRole) || roles.isEmpty()) + { + const QItemSelectionRange range(tl, br); + for (const QModelIndex &index : range.indexes()) + { + 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); +} diff --git a/logic/resources/ResourceProxyModel.h b/logic/resources/ResourceProxyModel.h new file mode 100644 index 00000000..9db09545 --- /dev/null +++ b/logic/resources/ResourceProxyModel.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +/// Convenience proxy model that transforms resource identifiers (strings) for Qt::DecorationRole into other types. +class ResourceProxyModel : public QIdentityProxyModel +{ + Q_OBJECT +public: + // resultTypeId is found using qMetaTypeId() + 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; + + template + static QAbstractItemModel *mixin(QAbstractItemModel *model) + { + ResourceProxyModel *proxy = new ResourceProxyModel(qMetaTypeId(), model); + proxy->setSourceModel(model); + return proxy; + } + +private: + // mutable because it needs to be available from the const data() + mutable QMap> m_resources; + + const int m_resultTypeId; +}; diff --git a/logic/resources/WebResourceHandler.cpp b/logic/resources/WebResourceHandler.cpp new file mode 100644 index 00000000..7ced5bc6 --- /dev/null +++ b/logic/resources/WebResourceHandler.cpp @@ -0,0 +1,67 @@ +#include "WebResourceHandler.h" + +#include "net/CacheDownload.h" +#include "net/HttpMetaCache.h" +#include "net/NetJob.h" +#include "FileSystem.h" +#include "Env.h" + +QMap WebResourceHandler::m_activeDownloads; + +WebResourceHandler::WebResourceHandler(const QString &url) + : QObject(), m_url(url) +{ + MetaEntryPtr entry = ENV.metacache()->resolveEntry("icons", url); + if (!entry->stale) + { + setResultFromFile(entry->getFullPath()); + } + else if (m_activeDownloads.contains(url)) + { + NetJob *job = m_activeDownloads.value(url); + connect(job, &NetJob::succeeded, this, &WebResourceHandler::succeeded); + connect(job, &NetJob::failed, this, [job, this]() {setFailure(job->failReason());}); + connect(job, &NetJob::progress, this, &WebResourceHandler::progress); + } + else + { + NetJob *job = new NetJob("Icon download"); + job->addNetAction(CacheDownload::make(QUrl(url), entry)); + connect(job, &NetJob::succeeded, this, &WebResourceHandler::succeeded); + connect(job, &NetJob::failed, this, [job, this]() {setFailure(job->failReason());}); + connect(job, &NetJob::progress, this, &WebResourceHandler::progress); + connect(job, &NetJob::finished, job, [job](){m_activeDownloads.remove(m_activeDownloads.key(job));job->deleteLater();}); + m_activeDownloads.insert(url, job); + job->start(); + } +} + +void WebResourceHandler::succeeded() +{ + MetaEntryPtr entry = ENV.metacache()->resolveEntry("icons", m_url); + setResultFromFile(entry->getFullPath()); + m_activeDownloads.remove(m_activeDownloads.key(qobject_cast(sender()))); +} +void WebResourceHandler::progress(qint64 current, qint64 total) +{ + if (total == 0) + { + setProgress(101); + } + else + { + setProgress(current / total); + } +} + +void WebResourceHandler::setResultFromFile(const QString &file) +{ + try + { + setResult(FS::read(file)); + } + catch (Exception &e) + { + setFailure(e.cause()); + } +} diff --git a/logic/resources/WebResourceHandler.h b/logic/resources/WebResourceHandler.h new file mode 100644 index 00000000..88804af3 --- /dev/null +++ b/logic/resources/WebResourceHandler.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include "ResourceHandler.h" + +class NetJob; + +class WebResourceHandler : public QObject, public ResourceHandler +{ +public: + explicit WebResourceHandler(const QString &url); + +private slots: + void succeeded(); + void progress(qint64 current, qint64 total); + +private: + static QMap m_activeDownloads; + + QString m_url; + + void setResultFromFile(const QString &file); +}; -- cgit v1.2.3