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"