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 | |
22 | using namespace Qt::StringLiterals; |
23 | |
24 | class SharedQmlEnginePrivate |
25 | { |
26 | public: |
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 | |
69 | void 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 | |
87 | void 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 | |
104 | void 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 | |
115 | SharedQmlEngine::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 | |
126 | SharedQmlEngine::~SharedQmlEngine() = default; |
127 | |
128 | void SharedQmlEngine::setTranslationDomain(const QString &translationDomain) |
129 | { |
130 | d->context->setTranslationDomain(translationDomain); |
131 | } |
132 | |
133 | QString SharedQmlEngine::translationDomain() const |
134 | { |
135 | return d->context->translationDomain(); |
136 | } |
137 | |
138 | void SharedQmlEngine::setSource(const QUrl &source) |
139 | { |
140 | d->source = source; |
141 | d->execute(source); |
142 | } |
143 | |
144 | QUrl SharedQmlEngine::source() const |
145 | { |
146 | return d->source; |
147 | } |
148 | |
149 | void SharedQmlEngine::setInitializationDelayed(const bool delay) |
150 | { |
151 | d->delay = delay; |
152 | } |
153 | |
154 | bool SharedQmlEngine::isInitializationDelayed() const |
155 | { |
156 | return d->delay; |
157 | } |
158 | |
159 | std::shared_ptr<QQmlEngine> SharedQmlEngine::engine() |
160 | { |
161 | return d->m_engine; |
162 | } |
163 | |
164 | QObject *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 | |
173 | QQmlComponent *SharedQmlEngine::mainComponent() const |
174 | { |
175 | return d->component; |
176 | } |
177 | |
178 | QQmlContext *SharedQmlEngine::rootContext() const |
179 | { |
180 | return d->rootContext; |
181 | } |
182 | |
183 | bool SharedQmlEngine::isError() const |
184 | { |
185 | return !d->m_engine || !d->component || d->component->isError() || d->incubator.isError(); |
186 | } |
187 | |
188 | static 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 | |
197 | QString 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 | |
208 | void 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 | |
224 | void 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 | |
256 | QObject *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 | |
264 | QObject *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 | |