/* * SPDX-FileCopyrightText: 2019 Marco Martin * * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "pagepool.h" #include #include #include #include #include #include "loggingcategory.h" PagePool::PagePool(QObject *parent) : QObject(parent) { } PagePool::~PagePool() { } QUrl PagePool::lastLoadedUrl() const { return m_lastLoadedUrl; } QQuickItem *PagePool::lastLoadedItem() const { return m_lastLoadedItem; } QList PagePool::items() const { return m_itemForUrl.values(); } QList PagePool::urls() const { return m_urlForItem.values(); } void PagePool::setCachePages(bool cache) { if (cache == m_cachePages) { return; } if (cache) { clear(); } m_cachePages = cache; Q_EMIT cachePagesChanged(); } bool PagePool::cachePages() const { return m_cachePages; } QQuickItem *PagePool::loadPage(const QString &url, QJSValue callback) { return loadPageWithProperties(url, QVariantMap(), callback); } QQuickItem *PagePool::loadPageWithProperties(const QString &url, const QVariantMap &properties, QJSValue callback) { const auto engine = qmlEngine(this); Q_ASSERT(engine); const QUrl actualUrl = resolvedUrl(url); auto found = m_itemForUrl.find(actualUrl); if (found != m_itemForUrl.end()) { m_lastLoadedUrl = found.key(); m_lastLoadedItem = found.value(); if (callback.isCallable()) { QJSValueList args = {engine->newQObject(found.value())}; callback.call(args); Q_EMIT lastLoadedUrlChanged(); Q_EMIT lastLoadedItemChanged(); // We could return the item, but for api coherence return null return nullptr; } else { Q_EMIT lastLoadedUrlChanged(); Q_EMIT lastLoadedItemChanged(); return found.value(); } } QQmlComponent *component = m_componentForUrl.value(actualUrl); if (!component) { component = new QQmlComponent(engine, 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, engine, component, callback, properties](QQmlComponent::Status status) mutable { if (status != QQmlComponent::Ready) { qCWarning(KirigamiLog) << component->errors(); m_componentForUrl.remove(component->url()); component->deleteLater(); return; } QQuickItem *item = createFromComponent(component, properties); if (item) { QJSValueList args = {engine->newQObject(item)}; callback.call(args); } if (m_cachePages) { component->deleteLater(); } else { m_componentForUrl[component->url()] = component; } }); return nullptr; } else if (component->status() != QQmlComponent::Ready) { qCWarning(KirigamiLog) << component->errors(); return nullptr; } QQuickItem *item = createFromComponent(component, properties); if (!item) { return nullptr; } if (m_cachePages) { component->deleteLater(); QQmlEngine::setObjectOwnership(item, QQmlEngine::CppOwnership); m_itemForUrl[component->url()] = item; m_urlForItem[item] = component->url(); Q_EMIT itemsChanged(); Q_EMIT urlsChanged(); } else { m_componentForUrl[component->url()] = component; QQmlEngine::setObjectOwnership(item, QQmlEngine::JavaScriptOwnership); } m_lastLoadedUrl = actualUrl; m_lastLoadedItem = item; Q_EMIT lastLoadedUrlChanged(); Q_EMIT lastLoadedItemChanged(); if (callback.isCallable()) { QJSValueList args = {engine->newQObject(item)}; callback.call(args); // We could return the item, but for api coherence return null return nullptr; } return item; } QQuickItem *PagePool::createFromComponent(QQmlComponent *component, const QVariantMap &properties) { const auto ctx = qmlContext(this); Q_ASSERT(ctx); QObject *obj = component->createWithInitialProperties(properties, ctx); if (!obj || component->isError()) { qCWarning(KirigamiLog) << component->errors(); if (obj) { obj->deleteLater(); } return nullptr; } QQuickItem *item = qobject_cast(obj); if (!item) { qCWarning(KirigamiLog) << "Storing Non-QQuickItem in PagePool not supported"; obj->deleteLater(); return nullptr; } return item; } QUrl PagePool::resolvedUrl(const QString &stringUrl) const { const auto ctx = qmlContext(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); } QQuickItem *PagePool::pageForUrl(const QUrl &url) const { return m_itemForUrl.value(resolvedUrl(url.toString()), nullptr); } 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(); Q_EMIT itemsChanged(); Q_EMIT urlsChanged(); } void PagePool::clear() { for (const auto &component : std::as_const(m_componentForUrl)) { component->deleteLater(); } m_componentForUrl.clear(); for (const auto &item : std::as_const(m_itemForUrl)) { // items that had been deparented are safe to delete if (!item->parentItem()) { item->deleteLater(); } QQmlEngine::setObjectOwnership(item, QQmlEngine::JavaScriptOwnership); } m_itemForUrl.clear(); m_urlForItem.clear(); m_lastLoadedUrl = QUrl(); m_lastLoadedItem = nullptr; Q_EMIT lastLoadedUrlChanged(); Q_EMIT lastLoadedItemChanged(); Q_EMIT itemsChanged(); Q_EMIT urlsChanged(); } #include "moc_pagepool.cpp"