diff --git a/src/pagepool.cpp b/src/pagepool.cpp index e51e7fdd..a3d18170 100644 --- a/src/pagepool.cpp +++ b/src/pagepool.cpp @@ -1,256 +1,266 @@ /* * Copyright 2019 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "pagepool.h" #include #include #include #include PagePool::PagePool(QObject *parent) : QObject(parent) { } PagePool::~PagePool() { } QUrl PagePool::lastLoadedUrl() const { return m_lastLoadedUrl; } void PagePool::setCachePages(bool cache) { if (cache == m_cachePages) { return; } if (cache) { for (auto *c : m_componentForUrl.values()) { c->deleteLater(); } m_componentForUrl.clear(); for (auto *i : m_itemForUrl.values()) { // items that had been deparented are safe to delete if (!i->parentItem()) { i->deleteLater(); } QQmlEngine::setObjectOwnership(i, QQmlEngine::JavaScriptOwnership); } m_itemForUrl.clear(); } m_cachePages = cache; emit cachePagesChanged(); } bool PagePool::cachePages() const { return m_cachePages; } QQuickItem *PagePool::loadPage(const QString &url, QJSValue callback) { Q_ASSERT(qmlEngine(this)); QQmlContext *ctx = QQmlEngine::contextForObject(this); Q_ASSERT(ctx); const QUrl actualUrl = resolvedUrl(url); - if (m_itemForUrl.contains(actualUrl)) { + QQuickItem *foundItem = nullptr; + if (actualUrl == m_lastLoadedUrl && m_lastLoadedItem) { + foundItem = m_lastLoadedItem; + } else if (m_itemForUrl.contains(actualUrl)) { + foundItem = m_itemForUrl[actualUrl]; + } + + if (foundItem) { if (callback.isCallable()) { - QJSValueList args = {qmlEngine(this)->newQObject(m_itemForUrl[actualUrl])}; + QJSValueList args = {qmlEngine(this)->newQObject(foundItem)}; callback.call(args); m_lastLoadedUrl = actualUrl; emit lastLoadedUrlChanged(); // We could return the item, but for api coherence return null return nullptr; } else { m_lastLoadedUrl = actualUrl; emit lastLoadedUrlChanged(); - return m_itemForUrl[actualUrl]; + return foundItem; } } QQmlComponent *component = m_componentForUrl.value(actualUrl); if (!component) { component = new QQmlComponent(qmlEngine(this), actualUrl, QQmlComponent::PreferSynchronous); } if (component->status() == QQmlComponent::Loading) { if (!callback.isCallable()) { component->deleteLater(); m_componentForUrl.remove(actualUrl); return nullptr; } connect(component, &QQmlComponent::statusChanged, this, [this, component, callback] (QQmlComponent::Status status) mutable { if (status != QQmlComponent::Ready) { qWarning() << component->errors(); m_componentForUrl.remove(component->url()); component->deleteLater(); return; } QQuickItem *item = createFromComponent(component); if (item) { QJSValueList args = {qmlEngine(this)->newQObject(item)}; callback.call(args); } if (m_cachePages) { component->deleteLater(); } else { m_componentForUrl[component->url()] = component; } }); return nullptr; } else if (component->status() != QQmlComponent::Ready) { qWarning() << component->errors(); return nullptr; } QQuickItem *item = createFromComponent(component); if (m_cachePages) { component->deleteLater(); } else { m_componentForUrl[component->url()] = component; } if (callback.isCallable()) { QJSValueList args = {qmlEngine(this)->newQObject(item)}; callback.call(args); m_lastLoadedUrl = actualUrl; emit lastLoadedUrlChanged(); // We could return the item, but for api coherence return null return nullptr; } else { m_lastLoadedUrl = actualUrl; emit lastLoadedUrlChanged(); return item; } } QQuickItem *PagePool::createFromComponent(QQmlComponent *component) { QQmlContext *ctx = QQmlEngine::contextForObject(this); Q_ASSERT(ctx); QObject *obj = component->create(ctx); // Error? if (!obj) { return nullptr; } QQuickItem *item = qobject_cast(obj); if (!item) { obj->deleteLater(); return nullptr; } + // Always cache just the last one + m_lastLoadedItem = item; + if (m_cachePages) { QQmlEngine::setObjectOwnership(item, QQmlEngine::CppOwnership); m_itemForUrl[component->url()] = item; } else { QQmlEngine::setObjectOwnership(item, QQmlEngine::JavaScriptOwnership); } return item; } QUrl PagePool::resolvedUrl(const QString &stringUrl) const { Q_ASSERT(qmlEngine(this)); QQmlContext *ctx = QQmlEngine::contextForObject(this); Q_ASSERT(ctx); QUrl actualUrl(stringUrl); if (actualUrl.scheme().isEmpty()) { actualUrl = ctx->resolvedUrl(actualUrl); } return actualUrl; } bool PagePool::isLocalUrl(const QUrl &url) { return url.isLocalFile() || url.scheme().isEmpty() || url.scheme() == QStringLiteral("qrc"); } QUrl PagePool::urlForPage(QQuickItem *item) const { return m_urlForItem.value(item); } bool PagePool::contains(const QVariant &page) const { if (page.canConvert()) { return m_urlForItem.contains(page.value()); } else if (page.canConvert()) { const QUrl actualUrl = resolvedUrl(page.value()); return m_itemForUrl.contains(actualUrl); } else { return false; } } void PagePool::deletePage(const QVariant &page) { if (!contains(page)) { return; } QQuickItem *item; if (page.canConvert()) { item = page.value(); } else if (page.canConvert()) { QString url = page.value(); if (url.isEmpty()) { return; } const QUrl actualUrl = resolvedUrl(page.value()); item = m_itemForUrl.value(actualUrl); } else { return; } if (!item) { return; } const QUrl url = m_urlForItem.value(item); if (url.isEmpty()) { return; } m_itemForUrl.remove(url); m_urlForItem.remove(item); item->deleteLater(); } #include "moc_pagepool.cpp" diff --git a/src/pagepool.h b/src/pagepool.h index cb48de7b..298345c3 100644 --- a/src/pagepool.h +++ b/src/pagepool.h @@ -1,113 +1,115 @@ /* * Copyright 2019 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #pragma once #include #include +#include /** * A Pool of Page items, pages will be unique per url and the items * will be kept around unless explicitly deleted. * Instaces are C++ owned and can be deleted only manually using deletePage() * Instance are unique per url: if you need 2 different instance for a page * url, you should instantiate them in the traditional way * or use a different PagePool instance. */ class PagePool : public QObject { Q_OBJECT /** * The last url that was loaded with @loadPage. Useful if you need * to have a "checked" state to buttons or list items that * load the page when clicked. */ Q_PROPERTY(QUrl lastLoadedUrl READ lastLoadedUrl NOTIFY lastLoadedUrlChanged) /** * If true (default) the pages will be kept around, will have C++ ownership and only one instance per page will be created. * If false the pages will have Javascript ownership (thus deleted on pop by the page stacks) and each call to loadPage will create a new page instance. When cachePages is false, Components gets cached never the less */ Q_PROPERTY(bool cachePages READ cachePages WRITE setCachePages NOTIFY cachePagesChanged) public: PagePool(QObject *parent = nullptr); ~PagePool(); QUrl lastLoadedUrl() const; void setCachePages(bool cache); bool cachePages() const; /** * Returns the instance of the item defined in the QML file identified * by url, only one instance will be made per url if cachePAges is true. If the url is remote (i.e. http) don't rely on the return value but us the async callback instead * @param url full url of the item: it can be a well formed Url, * an absolute path * or a relative one to the path of the qml file the PagePool is instantiated from * @param callback If we are loading a remote url, we can't have the item immediately but will be passed as a parameter to the provided callback. * Normally, don't set a callback, use it only in case of remote urls. * @returns the page instance that will have been created if necessary. * If the url is remote it will return null, * as well will return null if the callback has been provided */ Q_INVOKABLE QQuickItem *loadPage(const QString &url, QJSValue callback = QJSValue()); /** * @returns The url of the page for the given instance, empty if there is no correspondence */ Q_INVOKABLE QUrl urlForPage(QQuickItem *item) const; /** * @returns true if the is managed by the PagePool * @param the page can be either a QQuickItem or an url */ Q_INVOKABLE bool contains(const QVariant &page) const; /** * Deletes the page (only if is managed by the pool. * @param page either the url or the instance of the page */ Q_INVOKABLE void deletePage(const QVariant &page); /** * @returns full url from an absolute or relative path */ Q_INVOKABLE QUrl resolvedUrl(const QString &file) const; /** * @returns true if the url identifies a local resource (local file or a file inside Qt's resource system). * False if the url points to a network location */ Q_INVOKABLE bool isLocalUrl(const QUrl &url); Q_SIGNALS: void lastLoadedUrlChanged(); void cachePagesChanged(); private: QQuickItem *createFromComponent(QQmlComponent *component); QUrl m_lastLoadedUrl; + QPointer m_lastLoadedItem; QHash m_itemForUrl; QHash m_componentForUrl; QHash m_urlForItem; bool m_cachePages = true; };