KCMUtils

kcmoduleqml.cpp
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 "quick/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{
59public:
60 QmlConfigModuleWidget(KCModuleQml *module, QWidget *parent)
62 , m_module(module)
63 {
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 }
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, [this] {
134 setAuthActionName(d->configModule->authActionName());
135 });
136
137 connect(this, &KCModule::defaultsIndicatorsVisibleChanged, d->configModule, [this] {
138 d->configModule->setDefaultsIndicatorsVisible(defaultsIndicatorsVisible());
139 });
140
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 ?? false)
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: null
199 Keys.onReturnPressed: event => {
200 event.accepted = true
201 }
202 Keys.onEnterPressed: event => {
203 event.accepted = true
204 }
205
206 Window.onWindowChanged: {
207 if (Window.window) {
208 Window.window.LayoutMirroring.enabled = Qt.binding(() => Qt.application.layoutDirection === Qt.RightToLeft)
209 Window.window.LayoutMirroring.childrenInherit = true
210 }
211 }
212}
213 )"), QUrl(QStringLiteral("kcmutils/kcmmoduleqml.cpp")));
214 // clang-format on
215
216 d->rootPlaceHolder = qobject_cast<QQuickItem *>(component->create());
217 if (!d->rootPlaceHolder) {
218 qCCritical(KCMUTILS_LOG) << component->errors();
219 qFatal("Failed to initialize KCModuleQML");
220 }
221 d->rootPlaceHolder->setProperty("kcm", QVariant::fromValue(d->configModule));
222 d->rootPlaceHolder->installEventFilter(d->widget);
223 d->quickWidget->setContent(QUrl(), component, d->rootPlaceHolder);
224
225 d->pageRow = d->rootPlaceHolder->property("pageStack").value<QQuickItem *>();
226 if (d->pageRow) {
227 d->pageRow->setProperty("initialPage", QVariant::fromValue(d->configModule->mainUi()));
228
229 for (int i = 0; i < d->configModule->depth() - 1; i++) {
230 QMetaObject::invokeMethod(d->pageRow,
231 "push",
233 Q_ARG(QVariant, QVariant::fromValue(d->configModule->subPage(i))),
234 Q_ARG(QVariant, QVariant()));
235 if (d->configModule->mainUi()->property("sidebarMode").toBool()) {
236 d->pageRow->setProperty("currentIndex", 0);
237 d->configModule->setCurrentIndex(0);
238 }
239 }
240
241 connect(d->configModule, &KQuickConfigModule::pagePushed, this, [this](QQuickItem *page) {
242 QMetaObject::invokeMethod(d->pageRow, "push", Qt::DirectConnection, Q_ARG(QVariant, QVariant::fromValue(page)), Q_ARG(QVariant, QVariant()));
243 });
244 connect(d->configModule, &KQuickConfigModule::pageRemoved, this, [this]() {
245 QMetaObject::invokeMethod(d->pageRow, "pop", Qt::DirectConnection, Q_ARG(QVariant, QVariant()));
246 });
247 connect(d->configModule, &KQuickConfigModule::currentIndexChanged, this, [this]() {
248 d->pageRow->setProperty("currentIndex", d->configModule->currentIndex());
249 });
250 // New syntax cannot be used to connect to QML types
251 connect(d->pageRow, SIGNAL(currentIndexChanged()), this, SLOT(syncCurrentIndex()));
252 }
253
254 layout->addWidget(d->quickWidget);
255}
256
257KCModuleQml::~KCModuleQml() = default;
258
259void KCModuleQml::load()
260{
261 KCModule::load(); // calls setNeedsSave(false)
262 d->configModule->load();
263}
264
265void KCModuleQml::save()
266{
267 d->configModule->save();
268 d->configModule->setNeedsSave(false);
269}
270
271void KCModuleQml::defaults()
272{
273 d->configModule->defaults();
274}
275
276QWidget *KCModuleQml::widget()
277{
278 return d->widget;
279}
280
281#include "kcmoduleqml.moc"
282#include "moc_kcmoduleqml_p.cpp"
Q_SIGNAL void representsDefaultsChanged()
Indicate that the state of the modules contents has changed in a way that it might represents the def...
Q_SIGNAL void defaultsIndicatorsVisibleChanged()
Emitted when kcm need to display indicators for field with non default value.
Q_SIGNAL void buttonsChanged()
Buttons to display changed.
Q_SIGNAL void activationRequested(const QVariantList &args)
This signal will be emited by a single-instance application (such as System Settings) to request acti...
Q_SIGNAL void needsSaveChanged()
Indicate that the state of the modules contents has changed.
Q_SIGNAL void authActionNameChanged()
The auth action name has changed.
The base class for QWidgets configuration modules.
Definition kcmodule.h:59
virtual QWidget * widget()
Get the associated widget that can be embedded The returned widget should be used as a parent for wid...
Definition kcmodule.cpp:109
void load() override
Load the configuration data into the module.
Definition kcmodule.cpp:84
The base class for QtQuick configuration modules.
void setCurrentIndex(int index)
Sets the current page index this kcm should display.
void currentIndexChanged(int index)
Emitted when the current page changed.
void pagePushed(QQuickItem *page)
Emitted when a new sub page is pushed.
void pageRemoved()
Emitted when a sub page is popped.
KIOCORE_EXPORT CopyJob * move(const QList< QUrl > &src, const QUrl &dest, JobFlags flags=DefaultFlags)
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
void setContentsMargins(const QMargins &margins)
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
Q_OBJECTQ_OBJECT
virtual bool eventFilter(QObject *watched, QEvent *event)
QObject * parent() const const
QVariant property(const char *name) const const
virtual QObject * create(QQmlContext *context)
QList< QQmlError > errors() const const
void setData(const QByteArray &data, const QUrl &url)
DirectConnection
StrongFocus
TabFocusReason
transparent
WA_AlwaysStackOnTop
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QVariant fromValue(T &&value)
int toInt(bool *ok) const const
bool isEnabled() const const
virtual bool event(QEvent *event) override
void setFocusPolicy(Qt::FocusPolicy policy)
QWidget * nextInFocusChain() const const
QWidget * previousInFocusChain() const const
void setFocus()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:15:20 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.