File indexing completed on 2024-04-14 03:53:48

0001 /*
0002  *  SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
0003  *
0004  *  SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "pagepool.h"
0008 
0009 #include <QDebug>
0010 #include <QQmlComponent>
0011 #include <QQmlContext>
0012 #include <QQmlEngine>
0013 #include <QQmlProperty>
0014 
0015 #include "loggingcategory.h"
0016 
0017 PagePool::PagePool(QObject *parent)
0018     : QObject(parent)
0019 {
0020 }
0021 
0022 PagePool::~PagePool()
0023 {
0024 }
0025 
0026 QUrl PagePool::lastLoadedUrl() const
0027 {
0028     return m_lastLoadedUrl;
0029 }
0030 
0031 QQuickItem *PagePool::lastLoadedItem() const
0032 {
0033     return m_lastLoadedItem;
0034 }
0035 
0036 QList<QQuickItem *> PagePool::items() const
0037 {
0038     return m_itemForUrl.values();
0039 }
0040 
0041 QList<QUrl> PagePool::urls() const
0042 {
0043     return m_urlForItem.values();
0044 }
0045 
0046 void PagePool::setCachePages(bool cache)
0047 {
0048     if (cache == m_cachePages) {
0049         return;
0050     }
0051 
0052     if (cache) {
0053         clear();
0054     }
0055 
0056     m_cachePages = cache;
0057     Q_EMIT cachePagesChanged();
0058 }
0059 
0060 bool PagePool::cachePages() const
0061 {
0062     return m_cachePages;
0063 }
0064 
0065 QQuickItem *PagePool::loadPage(const QString &url, QJSValue callback)
0066 {
0067     return loadPageWithProperties(url, QVariantMap(), callback);
0068 }
0069 
0070 QQuickItem *PagePool::loadPageWithProperties(const QString &url, const QVariantMap &properties, QJSValue callback)
0071 {
0072     const auto engine = qmlEngine(this);
0073     Q_ASSERT(engine);
0074 
0075     const QUrl actualUrl = resolvedUrl(url);
0076 
0077     auto found = m_itemForUrl.find(actualUrl);
0078     if (found != m_itemForUrl.end()) {
0079         m_lastLoadedUrl = found.key();
0080         m_lastLoadedItem = found.value();
0081 
0082         if (callback.isCallable()) {
0083             QJSValueList args = {engine->newQObject(found.value())};
0084             callback.call(args);
0085             Q_EMIT lastLoadedUrlChanged();
0086             Q_EMIT lastLoadedItemChanged();
0087             // We could return the item, but for api coherence return null
0088             return nullptr;
0089 
0090         } else {
0091             Q_EMIT lastLoadedUrlChanged();
0092             Q_EMIT lastLoadedItemChanged();
0093             return found.value();
0094         }
0095     }
0096 
0097     QQmlComponent *component = m_componentForUrl.value(actualUrl);
0098 
0099     if (!component) {
0100         component = new QQmlComponent(engine, actualUrl, QQmlComponent::PreferSynchronous);
0101     }
0102 
0103     if (component->status() == QQmlComponent::Loading) {
0104         if (!callback.isCallable()) {
0105             component->deleteLater();
0106             m_componentForUrl.remove(actualUrl);
0107             return nullptr;
0108         }
0109 
0110         connect(component, &QQmlComponent::statusChanged, this, [this, engine, component, callback, properties](QQmlComponent::Status status) mutable {
0111             if (status != QQmlComponent::Ready) {
0112                 qCWarning(KirigamiLog) << component->errors();
0113                 m_componentForUrl.remove(component->url());
0114                 component->deleteLater();
0115                 return;
0116             }
0117             QQuickItem *item = createFromComponent(component, properties);
0118             if (item) {
0119                 QJSValueList args = {engine->newQObject(item)};
0120                 callback.call(args);
0121             }
0122 
0123             if (m_cachePages) {
0124                 component->deleteLater();
0125             } else {
0126                 m_componentForUrl[component->url()] = component;
0127             }
0128         });
0129 
0130         return nullptr;
0131 
0132     } else if (component->status() != QQmlComponent::Ready) {
0133         qCWarning(KirigamiLog) << component->errors();
0134         return nullptr;
0135     }
0136 
0137     QQuickItem *item = createFromComponent(component, properties);
0138     if (!item) {
0139         return nullptr;
0140     }
0141 
0142     if (m_cachePages) {
0143         component->deleteLater();
0144         QQmlEngine::setObjectOwnership(item, QQmlEngine::CppOwnership);
0145         m_itemForUrl[component->url()] = item;
0146         m_urlForItem[item] = component->url();
0147         Q_EMIT itemsChanged();
0148         Q_EMIT urlsChanged();
0149 
0150     } else {
0151         m_componentForUrl[component->url()] = component;
0152         QQmlEngine::setObjectOwnership(item, QQmlEngine::JavaScriptOwnership);
0153     }
0154 
0155     m_lastLoadedUrl = actualUrl;
0156     m_lastLoadedItem = item;
0157     Q_EMIT lastLoadedUrlChanged();
0158     Q_EMIT lastLoadedItemChanged();
0159 
0160     if (callback.isCallable()) {
0161         QJSValueList args = {engine->newQObject(item)};
0162         callback.call(args);
0163         // We could return the item, but for api coherence return null
0164         return nullptr;
0165     }
0166     return item;
0167 }
0168 
0169 QQuickItem *PagePool::createFromComponent(QQmlComponent *component, const QVariantMap &properties)
0170 {
0171     const auto ctx = qmlContext(this);
0172     Q_ASSERT(ctx);
0173 
0174     QObject *obj = component->createWithInitialProperties(properties, ctx);
0175 
0176     if (!obj || component->isError()) {
0177         qCWarning(KirigamiLog) << component->errors();
0178         if (obj) {
0179             obj->deleteLater();
0180         }
0181         return nullptr;
0182     }
0183 
0184     QQuickItem *item = qobject_cast<QQuickItem *>(obj);
0185     if (!item) {
0186         qCWarning(KirigamiLog) << "Storing Non-QQuickItem in PagePool not supported";
0187         obj->deleteLater();
0188         return nullptr;
0189     }
0190 
0191     return item;
0192 }
0193 
0194 QUrl PagePool::resolvedUrl(const QString &stringUrl) const
0195 {
0196     const auto ctx = qmlContext(this);
0197     Q_ASSERT(ctx);
0198 
0199     QUrl actualUrl(stringUrl);
0200     if (actualUrl.scheme().isEmpty()) {
0201         actualUrl = ctx->resolvedUrl(actualUrl);
0202     }
0203     return actualUrl;
0204 }
0205 
0206 bool PagePool::isLocalUrl(const QUrl &url)
0207 {
0208     return url.isLocalFile() || url.scheme().isEmpty() || url.scheme() == QStringLiteral("qrc");
0209 }
0210 
0211 QUrl PagePool::urlForPage(QQuickItem *item) const
0212 {
0213     return m_urlForItem.value(item);
0214 }
0215 
0216 QQuickItem *PagePool::pageForUrl(const QUrl &url) const
0217 {
0218     return m_itemForUrl.value(resolvedUrl(url.toString()), nullptr);
0219 }
0220 
0221 bool PagePool::contains(const QVariant &page) const
0222 {
0223     if (page.canConvert<QQuickItem *>()) {
0224         return m_urlForItem.contains(page.value<QQuickItem *>());
0225 
0226     } else if (page.canConvert<QString>()) {
0227         const QUrl actualUrl = resolvedUrl(page.value<QString>());
0228         return m_itemForUrl.contains(actualUrl);
0229 
0230     } else {
0231         return false;
0232     }
0233 }
0234 
0235 void PagePool::deletePage(const QVariant &page)
0236 {
0237     if (!contains(page)) {
0238         return;
0239     }
0240 
0241     QQuickItem *item;
0242     if (page.canConvert<QQuickItem *>()) {
0243         item = page.value<QQuickItem *>();
0244     } else if (page.canConvert<QString>()) {
0245         QString url = page.value<QString>();
0246         if (url.isEmpty()) {
0247             return;
0248         }
0249         const QUrl actualUrl = resolvedUrl(page.value<QString>());
0250 
0251         item = m_itemForUrl.value(actualUrl);
0252     } else {
0253         return;
0254     }
0255 
0256     if (!item) {
0257         return;
0258     }
0259 
0260     const QUrl url = m_urlForItem.value(item);
0261 
0262     if (url.isEmpty()) {
0263         return;
0264     }
0265 
0266     m_itemForUrl.remove(url);
0267     m_urlForItem.remove(item);
0268     item->deleteLater();
0269 
0270     Q_EMIT itemsChanged();
0271     Q_EMIT urlsChanged();
0272 }
0273 
0274 void PagePool::clear()
0275 {
0276     for (const auto &component : std::as_const(m_componentForUrl)) {
0277         component->deleteLater();
0278     }
0279     m_componentForUrl.clear();
0280 
0281     for (const auto &item : std::as_const(m_itemForUrl)) {
0282         // items that had been deparented are safe to delete
0283         if (!item->parentItem()) {
0284             item->deleteLater();
0285         }
0286         QQmlEngine::setObjectOwnership(item, QQmlEngine::JavaScriptOwnership);
0287     }
0288     m_itemForUrl.clear();
0289     m_urlForItem.clear();
0290     m_lastLoadedUrl = QUrl();
0291     m_lastLoadedItem = nullptr;
0292 
0293     Q_EMIT lastLoadedUrlChanged();
0294     Q_EMIT lastLoadedItemChanged();
0295     Q_EMIT itemsChanged();
0296     Q_EMIT urlsChanged();
0297 }
0298 
0299 #include "moc_pagepool.cpp"