KCMUtils

kcmoduleqml.cpp
1 /*
2  This file is part of the KDE Project
3  SPDX-FileCopyrightText: 2014 Marco Martin <[email protected]>
4 
5  SPDX-License-Identifier: LGPL-2.0-only
6 */
7 
8 #include "kcmoduleqml_p.h"
9 
10 #include <kcmutils_debug.h>
11 
12 #include <QQuickItem>
13 #include <QQuickWidget>
14 #include <QQuickWindow>
15 #include <QVBoxLayout>
16 
17 #include <KAboutData>
18 #include <KPageWidget>
19 #include <kdeclarative/kdeclarative.h>
20 #include <kdeclarative/qmlobjectsharedengine.h>
21 #include <kquickaddons/configmodule.h>
22 
23 class KCModuleQmlPrivate
24 {
25 public:
26  KCModuleQmlPrivate(std::unique_ptr<KQuickAddons::ConfigModule> cm, KCModuleQml *qq)
27  : q(qq)
28  , configModule(std::move(cm))
29  {
30  }
31 
32  ~KCModuleQmlPrivate()
33  {
34  }
35 
36  void syncCurrentIndex()
37  {
38  if (!configModule || !pageRow) {
39  return;
40  }
41 
42  configModule->setCurrentIndex(pageRow->property("currentIndex").toInt());
43  }
44 
45  KCModuleQml *q;
46  QQuickWindow *quickWindow = nullptr;
47  QQuickWidget *quickWidget = nullptr;
48  QQuickItem *rootPlaceHolder = nullptr;
49  QQuickItem *pageRow = nullptr;
50  std::unique_ptr<KQuickAddons::ConfigModule> configModule;
51  KDeclarative::QmlObjectSharedEngine *qmlObject = nullptr;
52 };
53 
54 KCModuleQml::KCModuleQml(std::unique_ptr<KQuickAddons::ConfigModule> configModule, QWidget *parent, const QVariantList &args)
55  : KCModule(parent, args)
56  , d(new KCModuleQmlPrivate(std::move(configModule), this))
57 {
58  connect(d->configModule.get(), &KQuickAddons::ConfigModule::quickHelpChanged, this, &KCModuleQml::quickHelpChanged);
59  // HACK:Here is important those two enums keep having the exact same values
60  // but the kdeclarative one can't use the KCModule's enum
61  setButtons((KCModule::Buttons)(int)d->configModule->buttons());
62  connect(d->configModule.get(), &KQuickAddons::ConfigModule::buttonsChanged, this, [=] {
63  setButtons((KCModule::Buttons)(int)d->configModule->buttons());
64  });
65 
66  if (d->configModule->needsSave()) {
67  Q_EMIT changed(true);
68  }
69  connect(d->configModule.get(), &KQuickAddons::ConfigModule::needsSaveChanged, this, [=] {
70  Q_EMIT changed(d->configModule->needsSave());
71  });
72  connect(d->configModule.get(), &KQuickAddons::ConfigModule::representsDefaultsChanged, this, [=] {
73  Q_EMIT defaulted(d->configModule->representsDefaults());
74  });
75 
76  setNeedsAuthorization(d->configModule->needsAuthorization());
77  connect(d->configModule.get(), &KQuickAddons::ConfigModule::needsAuthorizationChanged, this, [=] {
78  setNeedsAuthorization(d->configModule->needsAuthorization());
79  });
80 
81  setRootOnlyMessage(d->configModule->rootOnlyMessage());
82  setUseRootOnlyMessage(d->configModule->useRootOnlyMessage());
83  connect(d->configModule.get(), &KQuickAddons::ConfigModule::rootOnlyMessageChanged, this, [=] {
84  setRootOnlyMessage(d->configModule->rootOnlyMessage());
85  });
86  connect(d->configModule.get(), &KQuickAddons::ConfigModule::useRootOnlyMessageChanged, this, [=] {
87  setUseRootOnlyMessage(d->configModule->useRootOnlyMessage());
88  });
89 
90 #ifndef KCONFIGWIDGETS_NO_KAUTH
91  if (!d->configModule->authActionName().isEmpty()) {
92  setAuthAction(KAuth::Action(d->configModule->authActionName()));
93  }
94  connect(d->configModule.get(), &KQuickAddons::ConfigModule::authActionNameChanged, this, [=] {
95  setAuthAction(d->configModule->authActionName());
96  });
97 #endif
98 
100 #if KDECLARATIVE_BUILD_DEPRECATED_SINCE(5, 90)
101  // KCModule takes ownership of the kabout data so we need to force a copy
102  QT_WARNING_PUSH
103  QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations")
104  QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations")
105  setAboutData(new KAboutData(*d->configModule->aboutData()));
106  QT_WARNING_POP
107 #endif
109 
110  // Build the UI
111  QVBoxLayout *layout = new QVBoxLayout(this);
112  layout->setContentsMargins(0, 0, 0, 0);
113 
114  d->qmlObject = new KDeclarative::QmlObjectSharedEngine(this);
115  d->quickWidget = new QQuickWidget(d->qmlObject->engine(), this);
116  d->quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
117  d->quickWidget->setFocusPolicy(Qt::StrongFocus);
118  d->quickWidget->setAttribute(Qt::WA_AlwaysStackOnTop, true);
119  d->quickWindow = d->quickWidget->quickWindow();
120  d->quickWindow->setColor(Qt::transparent);
121 
122  QQmlComponent *component = new QQmlComponent(d->qmlObject->engine(), this);
123  // this has activeFocusOnTab to notice when the navigation wraps
124  // around, so when we need to go outside and inside
125  // pushPage/popPage are needed as push of StackView can't be directly invoked from c++
126  // because its parameters are QQmlV4Function which is not public
127  // the managers of onEnter/ReturnPressed are a workaround of
128  // Qt bug https://bugreports.qt.io/browse/QTBUG-70934
129  // clang-format off
130  component->setData(QByteArrayLiteral(R"(
131 import QtQuick 2.3
132 import QtQuick.Window 2.2
133 import QtQuick.Controls 2.2
134 import org.kde.kirigami 2.14 as Kirigami
135 
136 Kirigami.ApplicationItem {
137  //force it to *never* try to resize itself
138  width: Window.width
139 
140  implicitWidth: pageStack.implicitWidth
141  implicitHeight: pageStack.implicitHeight
142 
143  activeFocusOnTab: true
144  controlsVisible: false
145 
146  property QtObject kcm
147 
148  ToolButton {
149  id:toolButton
150  visible: false
151  icon.name: "go-previous"
152  }
153 
154  pageStack.separatorVisible: false
155  pageStack.globalToolBar.preferredHeight: toolButton.implicitHeight + Kirigami.Units.smallSpacing * 2
156  pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.ToolBar
157  pageStack.globalToolBar.showNavigationButtons: pageStack.currentIndex > 0 ? Kirigami.ApplicationHeaderStyle.ShowBackButton : Kirigami.ApplicationHeaderStyle.NoNavigationButtons
158 
159  pageStack.columnView.columnResizeMode: pageStack.items.length > 0 && pageStack.items[0].Kirigami.ColumnView.fillWidth
160  ? Kirigami.ColumnView.SingleColumn
161  : Kirigami.ColumnView.FixedColumns
162 
163  pageStack.defaultColumnWidth: kcm && kcm.columnWidth > 0 ? kcm.columnWidth : Kirigami.Units.gridUnit * 20
164 
165  Keys.onReturnPressed: {
166  event.accepted = true
167  }
168  Keys.onEnterPressed: {
169  event.accepted = true
170  }
171 }
172  )"), QUrl());
173  // clang-format on
174 
175  d->rootPlaceHolder = qobject_cast<QQuickItem *>(component->create());
176  if (!d->rootPlaceHolder) {
177  qCCritical(KCMUTILS_LOG) << component->errors();
178  qFatal("Failed to initialize KCModuleQML");
179  }
180  d->rootPlaceHolder->setProperty("kcm", QVariant::fromValue(d->configModule.get()));
181  d->rootPlaceHolder->installEventFilter(this);
182  d->quickWidget->setContent(QUrl(), component, d->rootPlaceHolder);
183 
184  d->pageRow = d->rootPlaceHolder->property("pageStack").value<QQuickItem *>();
185  if (d->pageRow) {
186  d->pageRow->setProperty("initialPage", QVariant::fromValue(d->configModule->mainUi()));
187 
188  for (int i = 0; i < d->configModule->depth() - 1; i++) {
189  QMetaObject::invokeMethod(d->pageRow,
190  "push",
192  Q_ARG(QVariant, QVariant::fromValue(d->configModule->subPage(i))),
193  Q_ARG(QVariant, QVariant()));
194  }
195 
196  connect(d->configModule.get(), &KQuickAddons::ConfigModule::pagePushed, this, [this](QQuickItem *page) {
197  QMetaObject::invokeMethod(d->pageRow, "push", Qt::DirectConnection, Q_ARG(QVariant, QVariant::fromValue(page)), Q_ARG(QVariant, QVariant()));
198  });
199  connect(d->configModule.get(), &KQuickAddons::ConfigModule::pageRemoved, this, [this]() {
200  QMetaObject::invokeMethod(d->pageRow, "pop", Qt::DirectConnection, Q_ARG(QVariant, QVariant()));
201  });
202  connect(d->configModule.get(), &KQuickAddons::ConfigModule::currentIndexChanged, this, [this]() {
203  d->pageRow->setProperty("currentIndex", d->configModule->currentIndex());
204  });
205  connect(d->configModule.get(),
207  this,
208  [this](const QString &message, const QVariant &timeout, const QString &actionText, const QJSValue &callBack) {
209  d->rootPlaceHolder->metaObject()->invokeMethod(d->rootPlaceHolder,
210  "showPassiveNotification",
211  Q_ARG(QVariant, message),
212  Q_ARG(QVariant, timeout),
213  Q_ARG(QVariant, actionText),
214  Q_ARG(QVariant, QVariant::fromValue(callBack)));
215  });
216  // New syntax cannot be used to connect to QML types
217  connect(d->pageRow, SIGNAL(currentIndexChanged()), this, SLOT(syncCurrentIndex()));
218  }
219 
220  layout->addWidget(d->quickWidget);
221  layout->setContentsMargins(0, 0, 0, 0);
222 }
223 
224 KCModuleQml::~KCModuleQml()
225 {
226  delete d;
227 }
228 
229 bool KCModuleQml::eventFilter(QObject *watched, QEvent *event)
230 {
231  if (watched == d->rootPlaceHolder && event->type() == QEvent::FocusIn) {
232  auto focusEvent = static_cast<QFocusEvent *>(event);
233  if (focusEvent->reason() == Qt::TabFocusReason) {
234  QWidget *w = d->quickWidget->nextInFocusChain();
235  while (!w->isEnabled() || !(w->focusPolicy() & Qt::TabFocus)) {
236  w = w->nextInFocusChain();
237  }
238  w->setFocus(Qt::TabFocusReason); // allow tab navigation inside the qquickwidget
239  return true;
240  } else if (focusEvent->reason() == Qt::BacktabFocusReason) {
241  QWidget *w = d->quickWidget->previousInFocusChain();
242  while (!w->isEnabled() || !(w->focusPolicy() & Qt::TabFocus)) {
243  w = w->previousInFocusChain();
244  }
246  return true;
247  }
248  }
249  return KCModule::eventFilter(watched, event);
250 }
251 
252 void KCModuleQml::focusInEvent(QFocusEvent *event)
253 {
254  Q_UNUSED(event)
255 
256  if (event->reason() == Qt::TabFocusReason) {
257  d->rootPlaceHolder->nextItemInFocusChain(true)->forceActiveFocus(Qt::TabFocusReason);
258  } else if (event->reason() == Qt::BacktabFocusReason) {
259  d->rootPlaceHolder->nextItemInFocusChain(false)->forceActiveFocus(Qt::BacktabFocusReason);
260  }
261 }
262 
263 QSize KCModuleQml::sizeHint() const
264 {
265  if (!d->configModule->mainUi()) {
266  return QSize();
267  }
268 
269  return QSize(d->rootPlaceHolder->implicitWidth(), d->rootPlaceHolder->implicitHeight());
270 }
271 
272 QString KCModuleQml::quickHelp() const
273 {
274  return d->configModule->quickHelp();
275 }
276 
277 #if KDECLARATIVE_BUILD_DEPRECATED_SINCE(5, 90)
278 const KAboutData *KCModuleQml::aboutData() const
279 {
280  QT_WARNING_PUSH
281  QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations")
282  QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations")
283  return d->configModule->aboutData();
284  QT_WARNING_POP
285 }
286 #endif
287 
288 void KCModuleQml::load()
289 {
290  d->configModule->load();
291  Q_EMIT defaulted(d->configModule->representsDefaults());
292 }
293 
294 void KCModuleQml::save()
295 {
296  d->configModule->save();
297  d->configModule->setNeedsSave(false);
298 }
299 
300 void KCModuleQml::defaults()
301 {
302  d->configModule->defaults();
303 }
304 
305 #include "moc_kcmoduleqml_p.cpp"
void setFocusPolicy(Qt::FocusPolicy policy)
QVariant fromValue(const T &value)
Q_EMITQ_EMIT
void pagePushed(QQuickItem *page)
QLayout * layout() const const
QWidget * previousInFocusChain() const const
void setDefaultsIndicatorsVisible(bool visible)
const KAboutData * aboutData() const
virtual QObject * create(QQmlContext *context)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void passiveNotificationRequested(const QString &message, const QVariant &timeout, const QString &actionText, const QJSValue &callBack)
virtual bool eventFilter(QObject *watched, QEvent *event)
bool changed() const
TabFocusReason
void setData(const QByteArray &data, const QUrl &url)
QWidget * nextInFocusChain() const const
void currentIndexChanged(int index)
void defaultsIndicatorsVisibleChanged(bool show)
DirectConnection
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
bool isEnabled() const const
bool setProperty(const char *name, const QVariant &value)
KIOCORE_EXPORT CopyJob * move(const QList< QUrl > &src, const QUrl &dest, JobFlags flags=DefaultFlags)
void addWidget(QWidget *w)
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
void setContentsMargins(int left, int top, int right, int bottom)
StrongFocus
void setFocus()
QString message
WA_AlwaysStackOnTop
transparent
QList< QQmlError > errors() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Sun Jun 26 2022 03:51:23 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.