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 | |
17 | PagePool::PagePool(QObject *parent) |
18 | : QObject(parent) |
19 | { |
20 | } |
21 | |
22 | PagePool::~PagePool() |
23 | { |
24 | } |
25 | |
26 | QUrl PagePool::lastLoadedUrl() const |
27 | { |
28 | return m_lastLoadedUrl; |
29 | } |
30 | |
31 | QQuickItem *PagePool::lastLoadedItem() const |
32 | { |
33 | return m_lastLoadedItem; |
34 | } |
35 | |
36 | QList<QQuickItem *> PagePool::items() const |
37 | { |
38 | return m_itemForUrl.values(); |
39 | } |
40 | |
41 | QList<QUrl> PagePool::urls() const |
42 | { |
43 | return m_urlForItem.values(); |
44 | } |
45 | |
46 | void 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 | |
60 | bool PagePool::cachePages() const |
61 | { |
62 | return m_cachePages; |
63 | } |
64 | |
65 | QQuickItem *PagePool::loadPage(const QString &url, QJSValue callback) |
66 | { |
67 | return loadPageWithProperties(url, properties: QVariantMap(), callback); |
68 | } |
69 | |
70 | QQuickItem *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 | |
169 | QQuickItem *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 | |
194 | QUrl 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 | |
206 | bool PagePool::isLocalUrl(const QUrl &url) |
207 | { |
208 | return url.isLocalFile() || url.scheme().isEmpty() || url.scheme() == QStringLiteral("qrc" ); |
209 | } |
210 | |
211 | QUrl PagePool::urlForPage(QQuickItem *item) const |
212 | { |
213 | return m_urlForItem.value(key: item); |
214 | } |
215 | |
216 | QQuickItem *PagePool::pageForUrl(const QUrl &url) const |
217 | { |
218 | return m_itemForUrl.value(key: resolvedUrl(stringUrl: url.toString()), defaultValue: nullptr); |
219 | } |
220 | |
221 | bool 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 | |
235 | void 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 | |
274 | void 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 | |