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

source code of kirigami/src/pagepool.cpp