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

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