KCMUtils

kcmultidialog.cpp
1/*
2 SPDX-FileCopyrightText: 2000 Matthias Elter <elter@kde.org>
3 SPDX-FileCopyrightText: 2003 Daniel Molkentin <molkentin@kde.org>
4 SPDX-FileCopyrightText: 2003, 2006 Matthias Kretz <kretz@kde.org>
5 SPDX-FileCopyrightText: 2004 Frans Englich <frans.englich@telia.com>
6 SPDX-FileCopyrightText: 2006 Tobias Koenig <tokoe@kde.org>
7 SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
8
9 SPDX-License-Identifier: LGPL-2.0-or-later
10*/
11
12#include "kcmultidialog.h"
13#include "kcmoduleloader.h"
14#include "kcmoduleqml_p.h"
15#include "kcmultidialog_p.h"
16#include <kcmutils_debug.h>
17
18#include <QApplication>
19#include <QDesktopServices>
20#include <QJsonArray>
21#include <QLayout>
22#include <QProcess>
23#include <QPushButton>
24#include <QScreen>
25#include <QStandardPaths>
26#include <QStringList>
27#include <QStyle>
28#include <QTimer>
29#include <QUrl>
30
31#include <KGuiItem>
32#include <KIconUtils>
33#include <KLocalizedString>
34#include <KMessageBox>
35#include <KPageWidgetModel>
36
37bool KCMultiDialogPrivate::resolveChanges(KCModule *module)
38{
39 if (!module || !module->needsSave()) {
40 return true;
41 }
42
43 // Let the user decide
44 const int queryUser = KMessageBox::warningTwoActionsCancel(q,
45 i18n("The settings of the current module have changed.\n"
46 "Do you want to apply the changes or discard them?"),
47 i18n("Apply Settings"),
51
52 switch (queryUser) {
54 return moduleSave(module);
55
57 module->load();
58 return true;
59
61 return false;
62
63 default:
64 Q_ASSERT(false);
65 return false;
66 }
67}
68
69void KCMultiDialogPrivate::slotCurrentPageChanged(KPageWidgetItem *current, KPageWidgetItem *previous)
70{
71 KCModule *previousModule = nullptr;
72 for (int i = 0; i < modules.count(); ++i) {
73 if (modules[i].item == previous) {
74 previousModule = modules[i].kcm;
75 }
76 }
77
78 // Delete global margins and spacing, since we want the contents to
79 // be able to touch the edges of the window
80 q->layout()->setContentsMargins(0, 0, 0, 0);
81
82 const KPageWidget *pageWidget = q->pageWidget();
83 pageWidget->layout()->setSpacing(0);
84
85 // Then, we set the margins for the title header and the buttonBox footer
86 const QStyle *style = q->style();
87 const QMargins layoutMargins = QMargins(style->pixelMetric(QStyle::PM_LayoutLeftMargin),
91
92 if (pageWidget->pageHeader()) {
93 pageWidget->pageHeader()->setContentsMargins(layoutMargins);
94 }
95
96 q->buttonBox()->setContentsMargins(layoutMargins.left(), layoutMargins.top(), layoutMargins.right(), layoutMargins.bottom());
97
98 q->blockSignals(true);
99 q->setCurrentPage(previous);
100
101 if (resolveChanges(previousModule)) {
102 q->setCurrentPage(current);
103 }
104 q->blockSignals(false);
105
106 // We need to get the state of the now active module
107 clientChanged();
108}
109
110void KCMultiDialogPrivate::clientChanged()
111{
112 // Get the current module
113 KCModule *activeModule = nullptr;
114 bool scheduleFirstShow = false;
115 for (int i = 0; i < modules.count(); ++i) {
116 if (modules[i].item == q->currentPage()) {
117 activeModule = modules[i].kcm;
118 scheduleFirstShow = activeModule && modules[i].firstShow;
119 break;
120 }
121 }
122
123 // When we first show a module, we call the load method
124 // Just in case we have multiple loadModule calls in a row, the current module could change
125 // Meaning we wait for the next tick, check the active module and call load if needed
126 if (scheduleFirstShow) {
127 QTimer::singleShot(0, q, [this]() {
128 for (int i = 0; i < modules.count(); ++i) {
129 if (modules[i].firstShow && modules[i].kcm && modules[i].item == q->currentPage()) {
130 modules[i].kcm->load();
131 modules[i].firstShow = false;
132 }
133 }
134 });
135 }
136
137 const bool change = activeModule && activeModule->needsSave();
138 const bool defaulted = activeModule && activeModule->representsDefaults();
139 const auto buttons = activeModule ? activeModule->buttons() : KCModule::NoAdditionalButton;
140
141 QPushButton *resetButton = q->buttonBox()->button(QDialogButtonBox::Reset);
142 if (resetButton) {
143 resetButton->setVisible(buttons & KCModule::Apply);
144 resetButton->setEnabled(change);
145 }
146
147 QPushButton *applyButton = q->buttonBox()->button(QDialogButtonBox::Apply);
148 if (applyButton) {
149 applyButton->setVisible(buttons & KCModule::Apply);
150 applyButton->setEnabled(change);
151 }
152
153 QPushButton *cancelButton = q->buttonBox()->button(QDialogButtonBox::Cancel);
154 if (cancelButton) {
155 cancelButton->setVisible(buttons & KCModule::Apply);
156 }
157
158 QPushButton *okButton = q->buttonBox()->button(QDialogButtonBox::Ok);
159 if (okButton) {
160 okButton->setVisible(buttons & KCModule::Apply);
161 }
162
163 QPushButton *closeButton = q->buttonBox()->button(QDialogButtonBox::Close);
164 if (closeButton) {
165 closeButton->setHidden(buttons & KCModule::Apply);
166 }
167
168 QPushButton *helpButton = q->buttonBox()->button(QDialogButtonBox::Help);
169 if (helpButton) {
170 helpButton->setVisible(buttons & KCModule::Help);
171 }
172
173 QPushButton *defaultButton = q->buttonBox()->button(QDialogButtonBox::RestoreDefaults);
174 if (defaultButton) {
175 defaultButton->setVisible(buttons & KCModule::Default);
176 defaultButton->setEnabled(!defaulted);
177 }
178}
179
180void KCMultiDialogPrivate::updateHeader(bool use, const QString &message)
181{
182 KPageWidgetItem *item = q->currentPage();
183 const auto findIt = std::find_if(modules.cbegin(), modules.cend(), [item](const CreatedModule &module) {
184 return module.item == item;
185 });
186 Q_ASSERT(findIt != modules.cend());
187
188 KCModule *kcm = findIt->kcm;
189 const QString moduleName = kcm->metaData().name();
190 const QString icon = kcm->metaData().iconName();
191
192 if (use) {
193 item->setHeader(QStringLiteral("<b>") + moduleName + QStringLiteral("</b><br><i>") + message + QStringLiteral("</i>"));
194 item->setIcon(KIconUtils::addOverlay(QIcon::fromTheme(icon), QIcon::fromTheme(QStringLiteral("dialog-warning")), Qt::BottomRightCorner));
195 } else {
196 item->setHeader(moduleName);
197 item->setIcon(QIcon::fromTheme(icon));
198 }
199}
200
201void KCMultiDialogPrivate::init()
202{
203 q->setFaceType(KPageDialog::Auto);
204 q->setWindowTitle(i18n("Configure"));
205 q->setModal(false);
206
207 QDialogButtonBox *buttonBox = new QDialogButtonBox(q);
217 buttonBox->button(QDialogButtonBox::Close)->setVisible(false);
218 buttonBox->button(QDialogButtonBox::Reset)->setEnabled(false);
219 buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
220
226
227 q->setButtonBox(buttonBox);
228 q->connect(q, &KPageDialog::currentPageChanged, q, [this](KPageWidgetItem *current, KPageWidgetItem *before) {
229 slotCurrentPageChanged(current, before);
230 });
231}
232
234 : KPageDialog(parent)
235 , d(new KCMultiDialogPrivate(this))
236{
237 d->init();
238}
239
241
243{
245 adjustSize();
246 /**
247 * adjustSize() relies on sizeHint but is limited to 2/3 of the desktop size
248 * Workaround for https://bugreports.qt.io/browse/QTBUG-3459
249 *
250 * We adjust the size after passing the show event
251 * because otherwise window pos is set to (0,0)
252 */
253
254 const QSize maxSize = screen()->availableGeometry().size();
255 resize(qMin(sizeHint().width(), maxSize.width()), qMin(sizeHint().height(), maxSize.height()));
256}
257
259{
260 const KPageWidgetItem *item = currentPage();
261 if (!item) {
262 return;
263 }
264
265 for (int i = 0; i < d->modules.count(); ++i) {
266 if (d->modules[i].item == item) {
267 d->modules[i].kcm->defaults();
268 d->clientChanged();
269 return;
270 }
271 }
272}
273
275{
276 const KPageWidgetItem *item = currentPage();
277 if (!item) {
278 return;
279 }
280
281 for (int i = 0; i < d->modules.count(); ++i) {
282 if (d->modules[i].item == item) {
283 d->modules[i].kcm->load();
284 d->clientChanged();
285 return;
286 }
287 }
288}
289
290bool KCMultiDialogPrivate::moduleSave(KCModule *module)
291{
292 if (!module) {
293 return false;
294 }
295
296 module->save();
297 return true;
298}
299
300void KCMultiDialogPrivate::apply()
301{
302 for (const CreatedModule &module : std::as_const(modules)) {
303 KCModule *kcm = module.kcm;
304
305 if (kcm->needsSave()) {
306 kcm->save();
307 }
308 }
309
310 Q_EMIT q->configCommitted();
311}
312
314{
316 applyButton->setFocus();
317
318 d->apply();
319}
320
322{
324 okButton->setFocus();
325
326 d->apply();
327 accept();
328}
329
331{
332 const KPageWidgetItem *item = currentPage();
333 if (!item) {
334 return;
335 }
336
337 QString docPath;
338 for (int i = 0; i < d->modules.count(); ++i) {
339 if (d->modules[i].item == item) {
340 if (docPath.isEmpty()) {
341 docPath = d->modules[i].kcm->metaData().value(QStringLiteral("X-DocPath"));
342 }
343 break;
344 }
345 }
346
347 const QUrl docUrl = QUrl(QStringLiteral("help:/")).resolved(QUrl(docPath)); // same code as in KHelpClient::invokeHelp
348 const QString docUrlScheme = docUrl.scheme();
349 const QString helpExec = QStandardPaths::findExecutable(QStringLiteral("khelpcenter"));
350 const bool foundExec = !helpExec.isEmpty();
351 if (!foundExec) {
352 qCDebug(KCMUTILS_LOG) << "Couldn't find khelpcenter executable in PATH.";
353 }
354 if (foundExec && (docUrlScheme == QLatin1String("man") || docUrlScheme == QLatin1String("info"))) {
356 } else {
358 }
359}
360
361void KCMultiDialog::closeEvent(QCloseEvent *event)
362{
364
365 for (auto &module : d->modules) {
366 delete module.kcm;
367 module.kcm = nullptr;
368 }
369}
370
371KPageWidgetItem *KCMultiDialog::addModule(const KPluginMetaData &metaData, const QVariantList &args)
372{
373 // Create the scroller
374 auto *moduleScroll = new UnboundScrollArea(this);
375 // Prepare the scroll area
376 moduleScroll->setWidgetResizable(true);
377 moduleScroll->setFrameStyle(QFrame::NoFrame);
378 moduleScroll->viewport()->setAutoFillBackground(false);
379
381 moduleScroll->setWidget(kcm->widget());
382
383 KPageWidgetItem *item = new KPageWidgetItem(moduleScroll, metaData.name());
384
385 KCMultiDialogPrivate::CreatedModule createdModule;
386 createdModule.kcm = kcm;
387 createdModule.item = item;
388 d->modules.append(createdModule);
389
391 item->setHeaderVisible(false);
392 }
393
394 item->setHeader(metaData.name());
395 item->setIcon(QIcon::fromTheme(metaData.iconName()));
396 const int weight = metaData.rawData().value(QStringLiteral("X-KDE-Weight")).toInt();
397 item->setProperty("_k_weight", weight);
398
399 bool updateCurrentPage = false;
401 Q_ASSERT(model);
402 const int siblingCount = model->rowCount();
403 int row = 0;
404 for (; row < siblingCount; ++row) {
405 KPageWidgetItem *siblingItem = model->item(model->index(row, 0));
406 if (siblingItem->property("_k_weight").toInt() > weight) {
407 // the item we found is heavier than the new module
408 // qDebug() << "adding KCM " << item->name() << " before " << siblingItem->name();
409 insertPage(siblingItem, item);
410 if (siblingItem == currentPage()) {
411 updateCurrentPage = true;
412 }
413
414 break;
415 }
416 }
417 if (row == siblingCount) {
418 // the new module is either the first or the heaviest item
419 // qDebug() << "adding KCM " << item->name() << " at the top level";
420 addPage(item);
421 }
422
423 connect(kcm, &KCModule::needsSaveChanged, this, [this]() {
424 d->clientChanged();
425 });
426
427 if (d->modules.count() == 1 || updateCurrentPage) {
428 setCurrentPage(item);
429 d->clientChanged();
430 }
431 return item;
432}
433
435{
436 for (int i = 0; i < d->modules.count(); ++i) {
437 removePage(d->modules[i].item);
438 }
439
440 d->modules.clear();
441
442 d->clientChanged();
443}
444
446{
447 for (const auto &module : std::as_const(d->modules)) {
448 module.kcm->setDefaultsIndicatorsVisible(show);
449 }
450}
451
452#include "moc_kcmultidialog.cpp"
KPluginMetaData metaData() const
Returns the metaData that was used when instantiating the plugin.
Q_SIGNAL void needsSaveChanged()
Indicate that the state of the modules contents has changed.
The base class for QWidgets configuration modules.
Definition kcmodule.h:59
void save() override
The save method stores the config information as shown in the user interface in the config files.
Definition kcmodule.cpp:93
void slotApplyClicked()
This slot is called when the user presses the "Apply" Button.
void slotOkClicked()
This slot is called when the user presses the "OK" Button.
~KCMultiDialog() override
Destructor.
KCMultiDialog(QWidget *parent=nullptr)
Constructs a new KCMultiDialog.
void clear()
Removes all modules from the dialog.
void slotUser1Clicked()
This slot is called when the user presses the "Reset" Button.
KPageWidgetItem * addModule(const KPluginMetaData &metaData, const QVariantList &args={})
Add a module to the dialog.
void setDefaultsIndicatorsVisible(bool show)
Show or hide an indicator when settings have changed from their default value.
void slotDefaultClicked()
This slot is called when the user presses the "Default" Button.
void showEvent(QShowEvent *event) override
void slotHelpClicked()
This slot is called when the user presses the "Help" Button.
static void assign(QPushButton *button, const KGuiItem &item)
QDialogButtonBox * buttonBox()
KPageWidget * pageWidget()
void currentPageChanged(KPageWidgetItem *current, KPageWidgetItem *before)
void setCurrentPage(KPageWidgetItem *item)
KPageWidgetItem * currentPage() const
void removePage(KPageWidgetItem *item)
void insertPage(KPageWidgetItem *before, KPageWidgetItem *item)
void addPage(KPageWidgetItem *item)
QWidget * pageHeader() const
void setHeaderVisible(bool visible)
void setIcon(const QIcon &icon)
void setHeader(const QString &header)
KPageWidgetItem * item(const QModelIndex &index) const
QModelIndex index(const KPageWidgetItem *item) const
QJsonObject rawData() const
QString iconName() const
QString name() const
Custom QScrollArea class that doesn't limit its size hint.
QString i18n(const char *text, const TYPE &arg...)
KCMUTILS_EXPORT KCModule * loadModule(const KPluginMetaData &metaData, QWidget *parent=nullptr, const QVariantList &args={}, const std::shared_ptr< QQmlEngine > &engine={})
Loads a KCModule.
QIcon addOverlay(const QIcon &icon, const QIcon &overlay, Qt::Corner position)
ButtonCode warningTwoActionsCancel(QWidget *parent, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const KGuiItem &cancelAction=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Options(Notify|Dangerous))
KGuiItem reset()
KGuiItem cancel()
KGuiItem defaults()
KGuiItem apply()
KGuiItem discard()
KGuiItem help()
KGuiItem close()
void clicked(bool checked)
virtual int rowCount(const QModelIndex &parent) const const=0
bool openUrl(const QUrl &url)
virtual void accept()
virtual void closeEvent(QCloseEvent *e) override
virtual void showEvent(QShowEvent *event) override
virtual QSize sizeHint() const const override
QPushButton * button(StandardButton which) const const
void setStandardButtons(StandardButtons buttons)
QIcon fromTheme(const QString &name)
QJsonValue value(QLatin1StringView key) const const
int toInt(int defaultValue) const const
virtual void setSpacing(int)
bool isEmpty() const const
int bottom() const const
int left() const const
int right() const const
int top() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QList< T > findChildren(Qt::FindChildOptions options) const const
bool setProperty(const char *name, QVariant &&value)
bool startDetached(const QString &program, const QStringList &arguments, const QString &workingDirectory, qint64 *pid)
QString findExecutable(const QString &executableName, const QStringList &paths)
bool isEmpty() const const
PM_LayoutLeftMargin
virtual int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const const=0
BottomRightCorner
QUrl resolved(const QUrl &relative) const const
void adjustSize()
void setEnabled(bool)
virtual bool event(QEvent *event) override
QLayout * layout() const const
QScreen * screen() const const
void setContentsMargins(const QMargins &margins)
void setFocus()
void setHidden(bool hidden)
void resize(const QSize &)
virtual void setVisible(bool visible)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:17:56 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.