1/*
2 SPDX-FileCopyrightText: 1999 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>
3 SPDX-FileCopyrightText: 2001 Michael Goffioul <kdeprint@swing.be>
4 SPDX-FileCopyrightText: 2004 Frans Englich <frans.englich@telia.com>
5 SPDX-FileCopyrightText: 2009 Dario Freddi <drf@kde.org>
6 SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
7 SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
8
9 SPDX-License-Identifier: LGPL-2.0-or-later
10*/
11
12#include "kquickconfigmodule.h"
13#include "kabstractconfigmodule.h"
14#include "kcmutils_debug.h"
15#include "sharedqmlengine_p.h"
16
17#include <QDebug>
18#include <QQmlContext>
19#include <QQmlEngine>
20#include <QQmlFileSelector>
21#include <QQuickItem>
22#include <QResource>
23#include <QUrl>
24
25#include <KRuntimePlatform>
26#include <KLocalizedContext>
27#include <KLocalizedString>
28
29#include <memory>
30
31class KQuickConfigModulePrivate
32{
33public:
34 KQuickConfigModulePrivate(KQuickConfigModule *module)
35 : q(module)
36 {
37 }
38
39 KQuickConfigModule *q;
40 SharedQmlEngine *engine = nullptr;
41 std::shared_ptr<QQmlEngine> passedInEngine;
42 QList<QQuickItem *> subPages;
43 int columnWidth = -1;
44 int currentIndex = 0;
45 QString errorString;
46
47 static QHash<QQmlContext *, KQuickConfigModule *> rootObjects;
48
49 QString getResourcePath(const QString &file)
50 {
51 return QLatin1String("/kcm/") + q->metaData().pluginId() + QLatin1String("/") + file;
52 }
53 QUrl getResourceUrl(const QString &resourcePath)
54 {
55 return QUrl(QLatin1String("qrc:") + resourcePath);
56 }
57};
58
59QHash<QQmlContext *, KQuickConfigModule *> KQuickConfigModulePrivate::rootObjects = QHash<QQmlContext *, KQuickConfigModule *>();
60
61KQuickConfigModule::KQuickConfigModule(QObject *parent, const KPluginMetaData &metaData)
62 : KAbstractConfigModule(parent, metaData)
63 , d(new KQuickConfigModulePrivate(this))
64{
65}
66
67void KQuickConfigModule::setInternalEngine(const std::shared_ptr<QQmlEngine> &engine)
68{
69 d->passedInEngine = engine;
70}
71
72KQuickConfigModule::~KQuickConfigModule()
73{
74 // in case mainUi was never called
75 if (d->engine) {
76 // delete the mainUi before removing the root object.
77 // Otherwise, we get lots of console errors about trying to read properties of null objects
78 delete d->engine->rootObject();
79 KQuickConfigModulePrivate::rootObjects.remove(key: d->engine->rootContext());
80 }
81}
82
83KQuickConfigModule *KQuickConfigModule::qmlAttachedProperties(QObject *object)
84{
85 // at the moment of the attached object creation, the root item is the only one that hasn't a parent
86 // only way to avoid creation of this attached for everybody but the root item
87 const QQmlEngine *engine = qmlEngine(object);
88 QQmlContext *ctx = qmlContext(object);
89
90 // Search the qml context that is the "root" for the sharedqmlobject,
91 // which is an ancestor of qmlContext(object) and the direct child of the
92 // engine's root context: we can do this assumption on the internals as
93 // we are distributed on the same repo.
94 while (ctx->parentContext() && ctx->parentContext() != engine->rootContext()) {
95 ctx = ctx->parentContext();
96 }
97
98 if (!object->parent() && KQuickConfigModulePrivate::rootObjects.contains(key: ctx)) {
99 return KQuickConfigModulePrivate::rootObjects.value(key: ctx);
100 } else {
101 return nullptr;
102 }
103}
104
105QQuickItem *KQuickConfigModule::mainUi()
106{
107 Q_ASSERT(d->passedInEngine);
108 if (d->engine) {
109 return qobject_cast<QQuickItem *>(o: d->engine->rootObject());
110 }
111
112 d->errorString.clear();
113 d->engine = new SharedQmlEngine(d->passedInEngine, this);
114
115 const QString componentName = metaData().pluginId();
116 KQuickConfigModulePrivate::rootObjects[d->engine->rootContext()] = this;
117 d->engine->setTranslationDomain(componentName);
118 d->engine->setInitializationDelayed(true);
119
120 QString resourcePath = d->getResourcePath(QStringLiteral("main.qml"));
121
122 qCDebug(KCMUTILS_LOG) << "Current platform is " << KRuntimePlatform::runtimePlatform();
123
124 const auto platforms = KRuntimePlatform::runtimePlatform();
125 for (const QString &platform : platforms) {
126 const QString platformResourcePath = d->getResourcePath(file: QString(QStringLiteral("main_%1.qml")).arg(a: platform));
127 if (QResource r(platformResourcePath); r.isValid()) {
128 qCDebug(KCMUTILS_LOG) << "Found platform-specific QML main file at" << platformResourcePath;
129 resourcePath = std::move(platformResourcePath);
130 break;
131 }
132 }
133
134 if (QResource r(resourcePath); !r.isValid()) {
135 d->errorString = i18n("Could not find resource '%1'", resourcePath);
136 qCWarning(KCMUTILS_LOG) << "Could not find resource" << resourcePath;
137 return nullptr;
138 }
139
140 new QQmlFileSelector(d->engine->engine().get(), this);
141 d->engine->setSource(d->getResourceUrl(resourcePath));
142 d->engine->rootContext()->setContextProperty(QStringLiteral("kcm"), this);
143 d->engine->completeInitialization();
144
145 if (d->engine->isError()) {
146 d->errorString = d->engine->errorString();
147 return nullptr;
148 }
149
150 Q_EMIT mainUiReady();
151
152 return qobject_cast<QQuickItem *>(o: d->engine->rootObject());
153}
154
155void KQuickConfigModule::push(const QString &fileName, const QVariantMap &initialProperties)
156{
157 // ensure main ui is created
158 if (!mainUi()) {
159 return;
160 }
161
162 const QString resourcePath = d->getResourcePath(file: fileName);
163 if (QResource r(resourcePath); !r.isValid()) {
164 qCWarning(KCMUTILS_LOG) << "Requested resource" << resourcePath << "does not exist";
165 }
166 QObject *object = d->engine->createObjectFromSource(source: d->getResourceUrl(resourcePath), context: d->engine->rootContext(), initialProperties);
167
168 QQuickItem *item = qobject_cast<QQuickItem *>(o: object);
169 if (!item) {
170 if (object) {
171 object->deleteLater();
172 }
173 return;
174 }
175
176 d->subPages << item;
177 Q_EMIT pagePushed(page: item);
178 Q_EMIT depthChanged(index: depth());
179 setCurrentIndex(d->currentIndex + 1);
180}
181
182void KQuickConfigModule::push(QQuickItem *item)
183{
184 // ensure main ui is created
185 if (!mainUi()) {
186 return;
187 }
188
189 d->subPages << item;
190 Q_EMIT pagePushed(page: item);
191 Q_EMIT depthChanged(index: depth());
192 setCurrentIndex(d->currentIndex + 1);
193}
194
195void KQuickConfigModule::pop()
196{
197 if (QQuickItem *page = takeLast()) {
198 page->deleteLater();
199 }
200}
201
202QQuickItem *KQuickConfigModule::takeLast()
203{
204 if (d->subPages.isEmpty()) {
205 return nullptr;
206 }
207 QQuickItem *page = d->subPages.takeLast();
208 Q_EMIT pageRemoved();
209 Q_EMIT depthChanged(index: depth());
210 setCurrentIndex(qMin(a: d->currentIndex, b: depth() - 1));
211 return page;
212}
213
214int KQuickConfigModule::columnWidth() const
215{
216 return d->columnWidth;
217}
218
219void KQuickConfigModule::setColumnWidth(int width)
220{
221 if (d->columnWidth == width) {
222 return;
223 }
224
225 d->columnWidth = width;
226 Q_EMIT columnWidthChanged(width);
227}
228
229int KQuickConfigModule::depth() const
230{
231 return d->subPages.count() + 1;
232}
233
234void KQuickConfigModule::setCurrentIndex(int index)
235{
236 if (index < 0 || index > d->subPages.count() || index == d->currentIndex) {
237 return;
238 }
239
240 d->currentIndex = index;
241
242 Q_EMIT currentIndexChanged(index);
243}
244
245int KQuickConfigModule::currentIndex() const
246{
247 return d->currentIndex;
248}
249
250std::shared_ptr<QQmlEngine> KQuickConfigModule::engine() const
251{
252 return d->engine->engine();
253}
254
255QString KQuickConfigModule::errorString() const
256{
257 return d->errorString;
258}
259
260QQuickItem *KQuickConfigModule::subPage(int index) const
261{
262 return d->subPages[index];
263}
264
265#include "moc_kquickconfigmodule.cpp"
266

source code of kcmutils/src/quick/kquickconfigmodule.cpp