1/*
2 * SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7#include "pagepool.h"
8
9#include <QDebug>
10#include <QQmlComponent>
11#include <QQmlContext>
12#include <QQmlEngine>
13#include <QQmlProperty>
14
15#include "loggingcategory.h"
16
17PagePool::PagePool(QObject *parent)
18 : QObject(parent)
19{
20}
21
22PagePool::~PagePool()
23{
24}
25
26QUrl PagePool::lastLoadedUrl() const
27{
28 return m_lastLoadedUrl;
29}
30
31QQuickItem *PagePool::lastLoadedItem() const
32{
33 return m_lastLoadedItem;
34}
35
36QList<QQuickItem *> PagePool::items() const
37{
38 return m_itemForUrl.values();
39}
40
41QList<QUrl> PagePool::urls() const
42{
43 return m_urlForItem.values();
44}
45
46void PagePool::setCachePages(bool cache)
47{
48 if (cache == m_cachePages) {
49 return;
50 }
51
52 if (cache) {
53 clear();
54 }
55
56 m_cachePages = cache;
57 Q_EMIT cachePagesChanged();
58}
59
60bool PagePool::cachePages() const
61{
62 return m_cachePages;
63}
64
65QQuickItem *PagePool::loadPage(const QString &url, QJSValue callback)
66{
67 return loadPageWithProperties(url, properties: QVariantMap(), callback);
68}
69
70QQuickItem *PagePool::loadPageWithProperties(const QString &url, const QVariantMap &properties, QJSValue callback)
71{
72 const auto engine = qmlEngine(this);
73 Q_ASSERT(engine);
74
75 const QUrl actualUrl = resolvedUrl(file: url);
76
77 auto found = m_itemForUrl.find(key: actualUrl);
78 if (found != m_itemForUrl.end()) {
79 m_lastLoadedUrl = found.key();
80 m_lastLoadedItem = found.value();
81
82 if (callback.isCallable()) {
83 QJSValueList args = {engine->newQObject(object: found.value())};
84 callback.call(args);
85 Q_EMIT lastLoadedUrlChanged();
86 Q_EMIT lastLoadedItemChanged();
87 // We could return the item, but for api coherence return null
88 return nullptr;
89
90 } else {
91 Q_EMIT lastLoadedUrlChanged();
92 Q_EMIT lastLoadedItemChanged();
93 return found.value();
94 }
95 }
96
97 QQmlComponent *component = m_componentForUrl.value(key: actualUrl);
98
99 if (!component) {
100 component = new QQmlComponent(engine, actualUrl, QQmlComponent::PreferSynchronous);
101 }
102
103 if (component->status() == QQmlComponent::Loading) {
104 if (!callback.isCallable()) {
105 component->deleteLater();
106 m_componentForUrl.remove(key: actualUrl);
107 return nullptr;
108 }
109
110 connect(sender: component, signal: &QQmlComponent::statusChanged, context: this, slot: [this, engine, component, callback, properties](QQmlComponent::Status status) mutable {
111 if (status != QQmlComponent::Ready) {
112 qCWarning(KirigamiLog) << component->errors();
113 m_componentForUrl.remove(key: component->url());
114 component->deleteLater();
115 return;
116 }
117 QQuickItem *item = createFromComponent(component, properties);
118 if (item) {
119 QJSValueList args = {engine->newQObject(object: item)};
120 callback.call(args);
121 }
122
123 if (m_cachePages) {
124 component->deleteLater();
125 } else {
126 m_componentForUrl[component->url()] = component;
127 }
128 });
129
130 return nullptr;
131
132 } else if (component->status() != QQmlComponent::Ready) {
133 qCWarning(KirigamiLog) << component->errors();
134 return nullptr;
135 }
136
137 QQuickItem *item = createFromComponent(component, properties);
138 if (!item) {
139 return nullptr;
140 }
141
142 if (m_cachePages) {
143 component->deleteLater();
144 QQmlEngine::setObjectOwnership(item, QQmlEngine::CppOwnership);
145 m_itemForUrl[component->url()] = item;
146 m_urlForItem[item] = component->url();
147 Q_EMIT itemsChanged();
148 Q_EMIT urlsChanged();
149
150 } else {
151 m_componentForUrl[component->url()] = component;
152 QQmlEngine::setObjectOwnership(item, QQmlEngine::JavaScriptOwnership);
153 }
154
155 m_lastLoadedUrl = actualUrl;
156 m_lastLoadedItem = item;
157 Q_EMIT lastLoadedUrlChanged();
158 Q_EMIT lastLoadedItemChanged();
159
160 if (callback.isCallable()) {
161 QJSValueList args = {engine->newQObject(object: item)};
162 callback.call(args);
163 // We could return the item, but for api coherence return null
164 return nullptr;
165 }
166 return item;
167}
168
169QQuickItem *PagePool::createFromComponent(QQmlComponent *component, const QVariantMap &properties)
170{
171 const auto ctx = qmlContext(this);
172 Q_ASSERT(ctx);
173
174 QObject *obj = component->createWithInitialProperties(initialProperties: properties, context: ctx);
175
176 if (!obj || component->isError()) {
177 qCWarning(KirigamiLog) << component->errors();
178 if (obj) {
179 obj->deleteLater();
180 }
181 return nullptr;
182 }
183
184 QQuickItem *item = qobject_cast<QQuickItem *>(o: obj);
185 if (!item) {
186 qCWarning(KirigamiLog) << "Storing Non-QQuickItem in PagePool not supported";
187 obj->deleteLater();
188 return nullptr;
189 }
190
191 return item;
192}
193
194QUrl PagePool::resolvedUrl(const QString &stringUrl) const
195{
196 const auto ctx = qmlContext(this);
197 Q_ASSERT(ctx);
198
199 QUrl actualUrl(stringUrl);
200 if (actualUrl.scheme().isEmpty()) {
201 actualUrl = ctx->resolvedUrl(actualUrl);
202 }
203 return actualUrl;
204}
205
206bool PagePool::isLocalUrl(const QUrl &url)
207{
208 return url.isLocalFile() || url.scheme().isEmpty() || url.scheme() == QStringLiteral("qrc");
209}
210
211QUrl PagePool::urlForPage(QQuickItem *item) const
212{
213 return m_urlForItem.value(key: item);
214}
215
216QQuickItem *PagePool::pageForUrl(const QUrl &url) const
217{
218 return m_itemForUrl.value(key: resolvedUrl(stringUrl: url.toString()), defaultValue: nullptr);
219}
220
221bool PagePool::contains(const QVariant &page) const
222{
223 if (page.canConvert<QQuickItem *>()) {
224 return m_urlForItem.contains(key: page.value<QQuickItem *>());
225
226 } else if (page.canConvert<QString>()) {
227 const QUrl actualUrl = resolvedUrl(stringUrl: page.value<QString>());
228 return m_itemForUrl.contains(key: actualUrl);
229
230 } else {
231 return false;
232 }
233}
234
235void PagePool::deletePage(const QVariant &page)
236{
237 if (!contains(page)) {
238 return;
239 }
240
241 QQuickItem *item;
242 if (page.canConvert<QQuickItem *>()) {
243 item = page.value<QQuickItem *>();
244 } else if (page.canConvert<QString>()) {
245 QString url = page.value<QString>();
246 if (url.isEmpty()) {
247 return;
248 }
249 const QUrl actualUrl = resolvedUrl(stringUrl: page.value<QString>());
250
251 item = m_itemForUrl.value(key: actualUrl);
252 } else {
253 return;
254 }
255
256 if (!item) {
257 return;
258 }
259
260 const QUrl url = m_urlForItem.value(key: item);
261
262 if (url.isEmpty()) {
263 return;
264 }
265
266 m_itemForUrl.remove(key: url);
267 m_urlForItem.remove(key: item);
268 item->deleteLater();
269
270 Q_EMIT itemsChanged();
271 Q_EMIT urlsChanged();
272}
273
274void PagePool::clear()
275{
276 for (const auto &component : std::as_const(t&: m_componentForUrl)) {
277 component->deleteLater();
278 }
279 m_componentForUrl.clear();
280
281 for (const auto &item : std::as_const(t&: m_itemForUrl)) {
282 // items that had been deparented are safe to delete
283 if (!item->parentItem()) {
284 item->deleteLater();
285 }
286 QQmlEngine::setObjectOwnership(item, QQmlEngine::JavaScriptOwnership);
287 }
288 m_itemForUrl.clear();
289 m_urlForItem.clear();
290 m_lastLoadedUrl = QUrl();
291 m_lastLoadedItem = nullptr;
292
293 Q_EMIT lastLoadedUrlChanged();
294 Q_EMIT lastLoadedItemChanged();
295 Q_EMIT itemsChanged();
296 Q_EMIT urlsChanged();
297}
298
299#include "moc_pagepool.cpp"
300

source code of kirigami/src/pagepool.cpp