1/*
2 SPDX-FileCopyrightText: 2014 Marco Martin <mart@kde.org>
3 SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
4
5 SPDX-License-Identifier: LGPL-2.0-only
6*/
7
8#include "kcmoduleqml_p.h"
9
10#include <QQuickItem>
11#include <QQuickWidget>
12#include <QQuickWindow>
13#include <QVBoxLayout>
14
15#include <KAboutData>
16#include <KLocalizedContext>
17#include <KPageWidget>
18#include <QQmlEngine>
19
20#include "qml/kquickconfigmodule.h"
21
22#include <kcmutils_debug.h>
23
24class QmlConfigModuleWidget;
25class KCModuleQmlPrivate
26{
27public:
28 KCModuleQmlPrivate(KQuickConfigModule *cm, KCModuleQml *qq)
29 : q(qq)
30 , configModule(std::move(cm))
31 {
32 }
33
34 ~KCModuleQmlPrivate()
35 {
36 }
37
38 void syncCurrentIndex()
39 {
40 if (!configModule || !pageRow) {
41 return;
42 }
43
44 configModule->setCurrentIndex(pageRow->property("currentIndex").toInt());
45 }
46
47 KCModuleQml *q;
48 QQuickWindow *quickWindow = nullptr;
49 QQuickWidget *quickWidget = nullptr;
50 QQuickItem *rootPlaceHolder = nullptr;
51 QQuickItem *pageRow = nullptr;
52 KQuickConfigModule *configModule;
53 QmlConfigModuleWidget *widget = nullptr;
54};
55
56class QmlConfigModuleWidget : public QWidget
57{
58 Q_OBJECT
59public:
60 QmlConfigModuleWidget(KCModuleQml *module, QWidget *parent)
61 : QWidget(parent)
62 , m_module(module)
63 {
64 setFocusPolicy(Qt::StrongFocus);
65 }
66
67 void focusInEvent(QFocusEvent *event) override
68 {
69 if (event->reason() == Qt::TabFocusReason) {
70 m_module->d->rootPlaceHolder->nextItemInFocusChain(true)->forceActiveFocus(Qt::TabFocusReason);
71 } else if (event->reason() == Qt::BacktabFocusReason) {
72 m_module->d->rootPlaceHolder->nextItemInFocusChain(false)->forceActiveFocus(Qt::BacktabFocusReason);
73 }
74 }
75
76 QSize sizeHint() const override
77 {
78 if (!m_module->d->rootPlaceHolder) {
79 return QSize();
80 }
81
82 return QSize(m_module->d->rootPlaceHolder->implicitWidth(), m_module->d->rootPlaceHolder->implicitHeight());
83 }
84
85 bool eventFilter(QObject *watched, QEvent *event) override
86 {
87 if (watched == m_module->d->rootPlaceHolder && event->type() == QEvent::FocusIn) {
88 auto focusEvent = static_cast<QFocusEvent *>(event);
89 if (focusEvent->reason() == Qt::TabFocusReason) {
90 QWidget *w = m_module->d->quickWidget->nextInFocusChain();
91 while (!w->isEnabled() || !(w->focusPolicy() & Qt::TabFocus)) {
92 w = w->nextInFocusChain();
93 }
94 w->setFocus(Qt::TabFocusReason); // allow tab navigation inside the qquickwidget
95 return true;
96 } else if (focusEvent->reason() == Qt::BacktabFocusReason) {
97 QWidget *w = m_module->d->quickWidget->previousInFocusChain();
98 while (!w->isEnabled() || !(w->focusPolicy() & Qt::TabFocus)) {
99 w = w->previousInFocusChain();
100 }
101 w->setFocus(Qt::BacktabFocusReason);
102 return true;
103 }
104 }
105 return QWidget::eventFilter(watched, event);
106 }
107
108private:
109 KCModuleQml *m_module;
110};
111
112KCModuleQml::KCModuleQml(KQuickConfigModule *configModule, QWidget *parent)
113 : KCModule(parent, configModule->metaData())
114 , d(new KCModuleQmlPrivate(configModule, this))
115{
116 d->widget = new QmlConfigModuleWidget(this, parent);
117 setButtons(d->configModule->buttons());
118 connect(d->configModule, &KQuickConfigModule::buttonsChanged, d->configModule, [this] {
119 setButtons(d->configModule->buttons());
120 });
121
122 setNeedsSave(d->configModule->needsSave());
123 connect(d->configModule, &KQuickConfigModule::needsSaveChanged, this, [this] {
124 setNeedsSave(d->configModule->needsSave());
125 });
126
127 setRepresentsDefaults(d->configModule->representsDefaults());
128 connect(d->configModule, &KQuickConfigModule::representsDefaultsChanged, this, [this] {
129 setRepresentsDefaults(d->configModule->representsDefaults());
130 });
131
132 setAuthActionName(d->configModule->authActionName());
133 connect(d->configModule, &KQuickConfigModule::authActionNameChanged, this, [=] {
134 setAuthActionName(d->configModule->authActionName());
135 });
136
137 connect(this, &KCModule::defaultsIndicatorsVisibleChanged, d->configModule, [this] {
138 d->configModule->setDefaultsIndicatorsVisible(defaultsIndicatorsVisible());
139 });
140
141 connect(this, &KAbstractConfigModule::activationRequested, d->configModule, &KQuickConfigModule::activationRequested);
142
143 // Build the UI
144 QVBoxLayout *layout = new QVBoxLayout(d->widget);
145 layout->setContentsMargins(0, 0, 0, 0);
146
147 d->quickWidget = new QQuickWidget(d->configModule->engine().get(), d->widget);
148 d->quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
149 d->quickWidget->setFocusPolicy(Qt::StrongFocus);
150 d->quickWidget->setAttribute(Qt::WA_AlwaysStackOnTop, true);
151 d->quickWidget->setAttribute(Qt::WA_NoMousePropagation, true); // Workaround for QTBUG-109861 to fix drag everywhere
152 d->quickWindow = d->quickWidget->quickWindow();
153 d->quickWindow->setColor(Qt::transparent);
154
155 QQmlComponent *component = new QQmlComponent(d->configModule->engine().get(), this);
156 // this has activeFocusOnTab to notice when the navigation wraps
157 // around, so when we need to go outside and inside
158 // pushPage/popPage are needed as push of StackView can't be directly invoked from c++
159 // because its parameters are QQmlV4Function which is not public.
160 // The managers of onEnter/ReturnPressed are a workaround of
161 // Qt bug https://bugreports.qt.io/browse/QTBUG-70934
162 // clang-format off
163 // TODO: move this in an instantiable component which would be used by the qml-only version as well
164 component->setData(QByteArrayLiteral(R"(
165import QtQuick
166import QtQuick.Controls as QQC2
167import org.kde.kirigami 2 as Kirigami
168import org.kde.kcmutils as KCMUtils
169
170Kirigami.ApplicationItem {
171 // force it to *never* try to resize itself
172 width: Window.width
173
174 implicitWidth: Math.max(pageStack.implicitWidth, Kirigami.Units.gridUnit * 36)
175 implicitHeight: Math.max(pageStack.implicitHeight, Kirigami.Units.gridUnit * 20)
176
177 activeFocusOnTab: true
178
179 property KCMUtils.ConfigModule kcm
180
181 QQC2.ToolButton {
182 id: toolButton
183 visible: false
184 icon.name: "go-previous"
185 }
186
187 pageStack.separatorVisible: pageStack.depth > 0 && pageStack.items[0].sidebarMode
188 pageStack.globalToolBar.preferredHeight: toolButton.implicitHeight + Kirigami.Units.smallSpacing * 2
189 pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.ToolBar
190 pageStack.globalToolBar.showNavigationButtons: pageStack.currentIndex > 0 ? Kirigami.ApplicationHeaderStyle.ShowBackButton : Kirigami.ApplicationHeaderStyle.NoNavigationButtons
191
192 pageStack.columnView.columnResizeMode: pageStack.items.length > 0 && (pageStack.items[0].Kirigami.ColumnView.fillWidth || pageStack.items.filter(item => item.visible).length === 1)
193 ? Kirigami.ColumnView.SingleColumn
194 : Kirigami.ColumnView.FixedColumns
195
196 pageStack.defaultColumnWidth: kcm && kcm.columnWidth > 0 ? kcm.columnWidth : Kirigami.Units.gridUnit * 15
197
198 footer: Rectangle {
199 visible: kcm && kcm.buttons !== KCMUtils.ConfigModule.NoAdditionalButton
200 height: pageStack.items.filter(item => item.extraFooterTopPadding).length > 0
201 ? Kirigami.Units.smallSpacing * 2
202 : 0
203 width: parent.width
204 color: Kirigami.Theme.backgroundColor
205 Kirigami.Separator {
206 visible: pageStack.separatorVisible && pageStack.columnView.visibleItems.length > 1
207 anchors {
208 left: parent.left
209 right: parent.right
210 bottom: parent.top
211 }
212 }
213 }
214 Keys.onReturnPressed: event => {
215 event.accepted = true
216 }
217 Keys.onEnterPressed: event => {
218 event.accepted = true
219 }
220
221 Window.onWindowChanged: {
222 if (Window.window) {
223 Window.window.LayoutMirroring.enabled = Qt.binding(() => Qt.application.layoutDirection === Qt.RightToLeft)
224 Window.window.LayoutMirroring.childrenInherit = true
225 }
226 }
227}
228 )"), QUrl(QStringLiteral("kcmutils/kcmmoduleqml.cpp")));
229 // clang-format on
230
231 d->rootPlaceHolder = qobject_cast<QQuickItem *>(component->create());
232 if (!d->rootPlaceHolder) {
233 qCCritical(KCMUTILS_LOG) << component->errors();
234 qFatal("Failed to initialize KCModuleQML");
235 }
236 d->rootPlaceHolder->setProperty("kcm", QVariant::fromValue(d->configModule));
237 d->rootPlaceHolder->installEventFilter(d->widget);
238 d->quickWidget->setContent(QUrl(), component, d->rootPlaceHolder);
239
240 d->pageRow = d->rootPlaceHolder->property("pageStack").value<QQuickItem *>();
241 if (d->pageRow) {
242 d->pageRow->setProperty("initialPage", QVariant::fromValue(d->configModule->mainUi()));
243
244 for (int i = 0; i < d->configModule->depth() - 1; i++) {
245 QMetaObject::invokeMethod(d->pageRow,
246 "push",
247 Qt::DirectConnection,
248 Q_ARG(QVariant, QVariant::fromValue(d->configModule->subPage(i))),
249 Q_ARG(QVariant, QVariant()));
250 if (d->configModule->mainUi()->property("sidebarMode").toBool()) {
251 d->pageRow->setProperty("currentIndex", 0);
252 d->configModule->setCurrentIndex(0);
253 }
254 }
255
256 connect(d->configModule, &KQuickConfigModule::pagePushed, this, [this](QQuickItem *page) {
257 QMetaObject::invokeMethod(d->pageRow, "push", Qt::DirectConnection, Q_ARG(QVariant, QVariant::fromValue(page)), Q_ARG(QVariant, QVariant()));
258 });
259 connect(d->configModule, &KQuickConfigModule::pageRemoved, this, [this]() {
260 QMetaObject::invokeMethod(d->pageRow, "pop", Qt::DirectConnection, Q_ARG(QVariant, QVariant()));
261 });
262 connect(d->configModule, &KQuickConfigModule::currentIndexChanged, this, [this]() {
263 d->pageRow->setProperty("currentIndex", d->configModule->currentIndex());
264 });
265 // New syntax cannot be used to connect to QML types
266 connect(d->pageRow, SIGNAL(currentIndexChanged()), this, SLOT(syncCurrentIndex()));
267 }
268
269 layout->addWidget(d->quickWidget);
270}
271
272KCModuleQml::~KCModuleQml() = default;
273
274void KCModuleQml::load()
275{
276 KCModule::load(); // calls setNeedsSave(false)
277 d->configModule->load();
278}
279
280void KCModuleQml::save()
281{
282 d->configModule->save();
283 d->configModule->setNeedsSave(false);
284}
285
286void KCModuleQml::defaults()
287{
288 d->configModule->defaults();
289}
290
291QWidget *KCModuleQml::widget()
292{
293 return d->widget;
294}
295
296#include "kcmoduleqml.moc"
297#include "moc_kcmoduleqml_p.cpp"
298

source code of kcmutils/src/kcmoduleqml.cpp