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

KDE's Doxygen guidelines are available online.