File indexing completed on 2024-04-28 15:27:43

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