1/*
2 SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
3 SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "sharedqmlengine_p.h"
9
10#include <KLocalizedContext>
11#include <QDebug>
12#include <QQmlContext>
13#include <QQmlEngine>
14#include <QQmlIncubator>
15#include <QQmlNetworkAccessManagerFactory>
16#include <QQuickItem>
17#include <QResource>
18#include <QTimer>
19
20#include "kcmutils_debug.h"
21
22using namespace Qt::StringLiterals;
23
24class SharedQmlEnginePrivate
25{
26public:
27 SharedQmlEnginePrivate(const std::shared_ptr<QQmlEngine> &engine, SharedQmlEngine *parent)
28 : q(parent)
29 , component(nullptr)
30 , delay(false)
31 , m_engine(engine)
32 {
33 executionEndTimer.setInterval(0);
34 executionEndTimer.setSingleShot(true);
35 QObject::connect(sender: &executionEndTimer, signal: &QTimer::timeout, context: q, slot: [this]() {
36 scheduleExecutionEnd();
37 });
38 }
39
40 ~SharedQmlEnginePrivate()
41 {
42 delete incubator.object();
43 }
44
45 void errorPrint(QQmlComponent *component, QQmlIncubator *incubator = nullptr);
46 void execute(const QUrl &source);
47 void scheduleExecutionEnd();
48 void minimumWidthChanged();
49 void minimumHeightChanged();
50 void maximumWidthChanged();
51 void maximumHeightChanged();
52 void preferredWidthChanged();
53 void preferredHeightChanged();
54 void checkInitializationCompleted();
55
56 SharedQmlEngine *q;
57
58 QUrl source;
59
60 QQmlIncubator incubator;
61 QQmlComponent *component;
62 QTimer executionEndTimer;
63 KLocalizedContext *context{nullptr};
64 QQmlContext *rootContext;
65 bool delay;
66 std::shared_ptr<QQmlEngine> m_engine;
67};
68
69void SharedQmlEnginePrivate::errorPrint(QQmlComponent *component, QQmlIncubator *incubator)
70{
71 QList<QQmlError> errors;
72 if (component && component->isError()) {
73 errors = component->errors();
74 } else if (incubator && incubator->isError()) {
75 errors = incubator->errors();
76 } else {
77 return;
78 }
79
80 qCWarning(KCMUTILS_LOG).noquote() << "Error loading QML file" << component->url().toString();
81 for (const auto &error : errors) {
82 constexpr const QLatin1String indent(" ");
83 qCWarning(KCMUTILS_LOG).noquote().nospace() << indent << error;
84 }
85}
86
87void SharedQmlEnginePrivate::execute(const QUrl &source)
88{
89 Q_ASSERT(!source.isEmpty());
90 delete component;
91 component = new QQmlComponent(m_engine.get(), q);
92 delete incubator.object();
93
94 m_engine->addImportPath(QStringLiteral("qrc:/"));
95 component->loadUrl(url: source);
96
97 if (delay) {
98 executionEndTimer.start(msec: 0);
99 } else {
100 scheduleExecutionEnd();
101 }
102}
103
104void SharedQmlEnginePrivate::scheduleExecutionEnd()
105{
106 if (component->isReady() || component->isError()) {
107 q->completeInitialization();
108 } else {
109 QObject::connect(sender: component, signal: &QQmlComponent::statusChanged, context: q, slot: [this]() {
110 q->completeInitialization();
111 });
112 }
113}
114
115SharedQmlEngine::SharedQmlEngine(const std::shared_ptr<QQmlEngine> &engine, QObject *parent)
116 : QObject(parent)
117 , d(new SharedQmlEnginePrivate(engine, this))
118{
119 d->rootContext = new QQmlContext(engine.get());
120 d->rootContext->setParent(this); // Delete the context when deleting the shared engine
121
122 d->context = new KLocalizedContext(d->rootContext);
123 d->rootContext->setContextObject(d->context);
124}
125
126SharedQmlEngine::~SharedQmlEngine() = default;
127
128void SharedQmlEngine::setTranslationDomain(const QString &translationDomain)
129{
130 d->context->setTranslationDomain(translationDomain);
131}
132
133QString SharedQmlEngine::translationDomain() const
134{
135 return d->context->translationDomain();
136}
137
138void SharedQmlEngine::setSource(const QUrl &source)
139{
140 d->source = source;
141 d->execute(source);
142}
143
144QUrl SharedQmlEngine::source() const
145{
146 return d->source;
147}
148
149void SharedQmlEngine::setInitializationDelayed(const bool delay)
150{
151 d->delay = delay;
152}
153
154bool SharedQmlEngine::isInitializationDelayed() const
155{
156 return d->delay;
157}
158
159std::shared_ptr<QQmlEngine> SharedQmlEngine::engine()
160{
161 return d->m_engine;
162}
163
164QObject *SharedQmlEngine::rootObject() const
165{
166 if (d->incubator.isLoading()) {
167 qCWarning(KCMUTILS_LOG) << "Trying to use rootObject before initialization is completed, whilst using setInitializationDelayed. Forcing completion";
168 d->incubator.forceCompletion();
169 }
170 return d->incubator.object();
171}
172
173QQmlComponent *SharedQmlEngine::mainComponent() const
174{
175 return d->component;
176}
177
178QQmlContext *SharedQmlEngine::rootContext() const
179{
180 return d->rootContext;
181}
182
183bool SharedQmlEngine::isError() const
184{
185 return !d->m_engine || !d->component || d->component->isError() || d->incubator.isError();
186}
187
188static QString qmlErrorsToString(const QList<QQmlError> &errors)
189{
190 QString ret;
191 for (const auto &e : errors) {
192 ret += e.url().toString() + QLatin1Char(':') + QString::number(e.line()) + QLatin1Char(' ') + e.description() + QLatin1Char('\n');
193 }
194 return ret;
195}
196
197QString SharedQmlEngine::errorString() const
198{
199 if (d->component && d->component->isError()) {
200 return d->component->errorString();
201 } else if (d->incubator.isError()) {
202 return qmlErrorsToString(errors: d->incubator.errors());
203 } else {
204 return {};
205 }
206}
207
208void SharedQmlEnginePrivate::checkInitializationCompleted()
209{
210 if (!incubator.isReady() && !incubator.isError()) {
211 QTimer::singleShot(interval: 0, receiver: q, slot: [this]() {
212 checkInitializationCompleted();
213 });
214 return;
215 }
216
217 if (!incubator.object()) {
218 errorPrint(component, incubator: &incubator);
219 }
220
221 Q_EMIT q->finished();
222}
223
224void SharedQmlEngine::completeInitialization(const QVariantMap &initialProperties)
225{
226 d->executionEndTimer.stop();
227 if (d->incubator.object()) {
228 return;
229 }
230
231 if (!d->component) {
232 qCWarning(KCMUTILS_LOG) << "No component for" << source();
233 return;
234 }
235
236 if (!d->component->isReady()) {
237 d->errorPrint(component: d->component);
238 return;
239 }
240
241 d->incubator.setInitialProperties(initialProperties);
242 d->component->create(d->incubator, context: d->rootContext);
243
244 if (d->delay) {
245 d->checkInitializationCompleted();
246 } else {
247 d->incubator.forceCompletion();
248
249 if (!d->incubator.object()) {
250 d->errorPrint(component: d->component, incubator: &d->incubator);
251 }
252 Q_EMIT finished();
253 }
254}
255
256QObject *SharedQmlEngine::createObjectFromSource(const QUrl &source, QQmlContext *context, const QVariantMap &initialProperties)
257{
258 QQmlComponent *component = new QQmlComponent(d->m_engine.get(), this);
259 component->loadUrl(url: source);
260
261 return createObjectFromComponent(component, context, initialProperties);
262}
263
264QObject *SharedQmlEngine::createObjectFromComponent(QQmlComponent *component, QQmlContext *context, const QVariantMap &initialProperties)
265{
266 QObject *object = component->createWithInitialProperties(initialProperties, context: context ? context : d->rootContext);
267
268 if (!component->isError() && object) {
269 // memory management
270 const auto root = rootObject();
271 object->setParent(root);
272 component->setParent(object);
273
274 // visually reparent to root object if wasn't specified otherwise by initialProperties
275 if (!initialProperties.contains(key: QLatin1String("parent")) && root && root->isQuickItemType()) {
276 object->setProperty(name: "parent", value: QVariant::fromValue(value: root));
277 }
278 return object;
279 } else {
280 d->errorPrint(component);
281 delete object;
282 return nullptr;
283 }
284}
285
286#include "moc_sharedqmlengine_p.cpp"
287

source code of kcmutils/src/qml/sharedqmlengine.cpp