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 "kcmultidialog_p.h"
14 
15 #include "kcmoduleproxy.h"
16 #include "kcmoduleqml_p.h"
17 #include <kcmutils_debug.h>
18 
19 #include <QApplication>
20 #include <QDesktopServices>
21 #include <QDesktopWidget>
22 #include <QJsonArray>
23 #include <QLayout>
24 #include <QProcess>
25 #include <QPushButton>
26 #include <QScreen>
27 #include <QStringList>
28 #include <QStyle>
29 #include <QUrl>
30 
31 #ifndef KCONFIGWIDGETS_NO_KAUTH
32 #include <KAuthAction>
33 #include <KAuthObjectDecorator>
34 #endif
35 #include <KGuiItem>
36 #include <KIconUtils>
37 #include <KLocalizedString>
38 #include <KMessageBox>
39 #include <KPageWidgetModel>
40 #include <KPluginInfo>
41 #include <KPluginMetaData>
42 
resolveChanges(KCModuleProxy * currentProxy)43 bool KCMultiDialogPrivate::resolveChanges(KCModuleProxy *currentProxy)
44 {
45     Q_Q(KCMultiDialog);
46     if (!currentProxy || !currentProxy->isChanged()) {
47         return true;
48     }
49 
50     // Let the user decide
51     const int queryUser = KMessageBox::warningYesNoCancel(q,
52                                                           i18n("The settings of the current module have changed.\n"
53                                                                "Do you want to apply the changes or discard them?"),
54                                                           i18n("Apply Settings"),
55                                                           KStandardGuiItem::apply(),
56                                                           KStandardGuiItem::discard(),
57                                                           KStandardGuiItem::cancel());
58 
59     switch (queryUser) {
60     case KMessageBox::Yes:
61         return moduleSave(currentProxy);
62 
63     case KMessageBox::No:
64         currentProxy->load();
65         return true;
66 
67     case KMessageBox::Cancel:
68         return false;
69 
70     default:
71         Q_ASSERT(false);
72         return false;
73     }
74 }
75 
_k_slotCurrentPageChanged(KPageWidgetItem * current,KPageWidgetItem * previous)76 void KCMultiDialogPrivate::_k_slotCurrentPageChanged(KPageWidgetItem *current, KPageWidgetItem *previous)
77 {
78     Q_Q(KCMultiDialog);
79 
80     KCModuleProxy *previousModule = nullptr;
81     for (int i = 0; i < modules.count(); ++i) {
82         if (modules[i].item == previous) {
83             previousModule = modules[i].kcm;
84         }
85     }
86 
87     // Delete global margins and spacing, since we want the contents to
88     // be able to touch the edges of the window
89     q->layout()->setContentsMargins(0, 0, 0, 0);
90 
91     const KPageWidget *pageWidget = q->pageWidget();
92     pageWidget->layout()->setSpacing(0);
93 
94     // Then, we set the margins for the title header and the buttonBox footer
95     const QStyle *style = q->style();
96     const QMargins layoutMargins = QMargins(style->pixelMetric(QStyle::PM_LayoutLeftMargin),
97                                             style->pixelMetric(QStyle::PM_LayoutTopMargin),
98                                             style->pixelMetric(QStyle::PM_LayoutRightMargin),
99                                             style->pixelMetric(QStyle::PM_LayoutBottomMargin));
100 
101     if (pageWidget->pageHeader()) {
102         pageWidget->pageHeader()->setContentsMargins(layoutMargins);
103     }
104 
105     // Do not set buttonBox's top margin as that space will be covered by the content's bottom margin
106     q->buttonBox()->setContentsMargins(layoutMargins.left(), 0, layoutMargins.right(), layoutMargins.bottom());
107 
108     q->blockSignals(true);
109     q->setCurrentPage(previous);
110 
111     if (resolveChanges(previousModule)) {
112         q->setCurrentPage(current);
113     }
114     q->blockSignals(false);
115 
116     // We need to get the state of the now active module
117     _k_clientChanged();
118 }
119 
_k_clientChanged()120 void KCMultiDialogPrivate::_k_clientChanged()
121 {
122     Q_Q(KCMultiDialog);
123     // qDebug();
124     // Get the current module
125     KCModuleProxy *activeModule = nullptr;
126     for (int i = 0; i < modules.count(); ++i) {
127         if (modules[i].item == q->currentPage()) {
128             activeModule = modules[i].kcm;
129             break;
130         }
131     }
132 
133     bool change = false;
134     bool defaulted = false;
135     if (activeModule) {
136         change = activeModule->isChanged();
137         defaulted = activeModule->defaulted();
138 
139         QPushButton *applyButton = q->buttonBox()->button(QDialogButtonBox::Apply);
140         if (applyButton) {
141             q->disconnect(applyButton, &QAbstractButton::clicked, q, &KCMultiDialog::slotApplyClicked);
142 #ifndef KCONFIGWIDGETS_NO_KAUTH
143             delete applyButton->findChild<KAuth::ObjectDecorator *>();
144 #endif
145         }
146 
147         QPushButton *okButton = q->buttonBox()->button(QDialogButtonBox::Ok);
148         if (okButton) {
149             q->disconnect(okButton, &QAbstractButton::clicked, q, &KCMultiDialog::slotOkClicked);
150 #ifndef KCONFIGWIDGETS_NO_KAUTH
151             delete okButton->findChild<KAuth::ObjectDecorator *>();
152 #endif
153         }
154 
155 #ifndef KCONFIGWIDGETS_NO_KAUTH
156         if (activeModule->realModule()->needsAuthorization()) {
157             if (applyButton) {
158                 KAuth::ObjectDecorator *decorator = new KAuth::ObjectDecorator(applyButton);
159                 decorator->setAuthAction(activeModule->realModule()->authAction());
160                 activeModule->realModule()->authAction().setParentWidget(activeModule->realModule());
161                 q->connect(decorator, &KAuth::ObjectDecorator::authorized, q, &KCMultiDialog::slotApplyClicked);
162             }
163 
164             if (okButton) {
165                 KAuth::ObjectDecorator *decorator = new KAuth::ObjectDecorator(okButton);
166                 decorator->setAuthAction(activeModule->realModule()->authAction());
167                 activeModule->realModule()->authAction().setParentWidget(activeModule->realModule());
168                 q->connect(decorator, &KAuth::ObjectDecorator::authorized, q, &KCMultiDialog::slotOkClicked);
169             }
170         } else {
171             if (applyButton) {
172                 q->connect(applyButton, &QAbstractButton::clicked, q, &KCMultiDialog::slotApplyClicked);
173                 delete applyButton->findChild<KAuth::ObjectDecorator *>();
174             }
175 
176             if (okButton) {
177                 q->connect(okButton, &QAbstractButton::clicked, q, &KCMultiDialog::slotOkClicked);
178                 delete okButton->findChild<KAuth::ObjectDecorator *>();
179             }
180         }
181 #endif
182     }
183 
184     auto buttons = activeModule ? activeModule->buttons() : KCModule::NoAdditionalButton;
185 
186     QPushButton *resetButton = q->buttonBox()->button(QDialogButtonBox::Reset);
187     if (resetButton) {
188         resetButton->setVisible(buttons & KCModule::Apply);
189         resetButton->setEnabled(change);
190     }
191 
192     QPushButton *applyButton = q->buttonBox()->button(QDialogButtonBox::Apply);
193     if (applyButton) {
194         applyButton->setVisible(buttons & KCModule::Apply);
195         applyButton->setEnabled(change);
196     }
197 
198     QPushButton *cancelButton = q->buttonBox()->button(QDialogButtonBox::Cancel);
199     if (cancelButton) {
200         cancelButton->setVisible(buttons & KCModule::Apply);
201     }
202 
203     QPushButton *okButton = q->buttonBox()->button(QDialogButtonBox::Ok);
204     if (okButton) {
205         okButton->setVisible(buttons & KCModule::Apply);
206     }
207 
208     QPushButton *closeButton = q->buttonBox()->button(QDialogButtonBox::Close);
209     if (closeButton) {
210         closeButton->setHidden(buttons & KCModule::Apply);
211     }
212 
213     QPushButton *helpButton = q->buttonBox()->button(QDialogButtonBox::Help);
214     if (helpButton) {
215         helpButton->setVisible(buttons & KCModule::Help);
216     }
217 
218     QPushButton *defaultButton = q->buttonBox()->button(QDialogButtonBox::RestoreDefaults);
219     if (defaultButton) {
220         defaultButton->setVisible(buttons & KCModule::Default);
221         defaultButton->setEnabled(!defaulted);
222     }
223 }
224 
_k_updateHeader(bool use,const QString & message)225 void KCMultiDialogPrivate::_k_updateHeader(bool use, const QString &message)
226 {
227     Q_Q(KCMultiDialog);
228     KPageWidgetItem *item = q->currentPage();
229     KCModuleProxy *kcm = qobject_cast<KCModuleProxy *>(item->widget());
230 
231     QString moduleName;
232     QString icon;
233 
234     if (kcm->metaData().isValid()) {
235         moduleName = kcm->metaData().name();
236         icon = kcm->metaData().iconName();
237     }
238 
239 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 88)
240     if (kcm->moduleInfo().isValid()) {
241         moduleName = kcm->moduleInfo().moduleName();
242         icon = kcm->moduleInfo().icon();
243     }
244 #endif
245 
246     if (use) {
247         item->setHeader(QStringLiteral("<b>") + moduleName + QStringLiteral("</b><br><i>") + message + QStringLiteral("</i>"));
248         item->setIcon(KIconUtils::addOverlay(QIcon::fromTheme(icon), QIcon::fromTheme(QStringLiteral("dialog-warning")), Qt::BottomRightCorner));
249     } else {
250         item->setHeader(moduleName);
251         item->setIcon(QIcon::fromTheme(icon));
252     }
253 }
254 
init()255 void KCMultiDialogPrivate::init()
256 {
257     Q_Q(KCMultiDialog);
258     q->setFaceType(KPageDialog::Auto);
259     q->setWindowTitle(i18n("Configure"));
260     q->setModal(false);
261 
262     QDialogButtonBox *buttonBox = new QDialogButtonBox(q);
263     buttonBox->setStandardButtons(QDialogButtonBox::Help | QDialogButtonBox::RestoreDefaults | QDialogButtonBox::Cancel | QDialogButtonBox::Apply
264                                   | QDialogButtonBox::Close | QDialogButtonBox::Ok | QDialogButtonBox::Reset);
265     KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::ok());
266     KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel());
267     KGuiItem::assign(buttonBox->button(QDialogButtonBox::RestoreDefaults), KStandardGuiItem::defaults());
268     KGuiItem::assign(buttonBox->button(QDialogButtonBox::Apply), KStandardGuiItem::apply());
269     KGuiItem::assign(buttonBox->button(QDialogButtonBox::Close), KStandardGuiItem::close());
270     KGuiItem::assign(buttonBox->button(QDialogButtonBox::Reset), KStandardGuiItem::reset());
271     KGuiItem::assign(buttonBox->button(QDialogButtonBox::Help), KStandardGuiItem::help());
272     buttonBox->button(QDialogButtonBox::Close)->setVisible(false);
273     buttonBox->button(QDialogButtonBox::Reset)->setEnabled(false);
274     buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
275 
276     q->connect(buttonBox->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, q, &KCMultiDialog::slotApplyClicked);
277     q->connect(buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, q, &KCMultiDialog::slotOkClicked);
278     q->connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, q, &KCMultiDialog::slotDefaultClicked);
279     q->connect(buttonBox->button(QDialogButtonBox::Help), &QAbstractButton::clicked, q, &KCMultiDialog::slotHelpClicked);
280     q->connect(buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, q, &KCMultiDialog::slotUser1Clicked);
281 
282     q->setButtonBox(buttonBox);
283     q->connect(q, &KPageDialog::currentPageChanged, q, [this](KPageWidgetItem *current, KPageWidgetItem *before) {
284         _k_slotCurrentPageChanged(current, before);
285     });
286 }
287 
KCMultiDialog(QWidget * parent)288 KCMultiDialog::KCMultiDialog(QWidget *parent)
289     : KPageDialog(parent)
290     , d_ptr(new KCMultiDialogPrivate(this))
291 {
292     d_func()->init();
293 }
294 
KCMultiDialog(KPageWidget * pageWidget,QWidget * parent,Qt::WindowFlags flags)295 KCMultiDialog::KCMultiDialog(KPageWidget *pageWidget, QWidget *parent, Qt::WindowFlags flags)
296     : KPageDialog(pageWidget, parent, flags)
297     , d_ptr(new KCMultiDialogPrivate(this))
298 {
299     d_func()->init();
300 }
301 
KCMultiDialog(KCMultiDialogPrivate & dd,KPageWidget * pageWidget,QWidget * parent,Qt::WindowFlags flags)302 KCMultiDialog::KCMultiDialog(KCMultiDialogPrivate &dd, KPageWidget *pageWidget, QWidget *parent, Qt::WindowFlags flags)
303     : KPageDialog(pageWidget, parent, flags)
304     , d_ptr(&dd)
305 {
306     d_func()->init();
307 }
308 
~KCMultiDialog()309 KCMultiDialog::~KCMultiDialog()
310 {
311     delete d_ptr;
312 }
313 
showEvent(QShowEvent * ev)314 void KCMultiDialog::showEvent(QShowEvent *ev)
315 {
316     KPageDialog::showEvent(ev);
317     adjustSize();
318     /**
319      * adjustSize() relies on sizeHint but is limited to 2/3 of the desktop size
320      * Workaround for https://bugreports.qt.io/browse/QTBUG-3459
321      *
322      * We adjust the size after passing the show event
323      * because otherwise window pos is set to (0,0)
324      */
325     QScreen *screen = QApplication::screenAt(pos());
326     if (screen) {
327         const QSize maxSize = screen->availableGeometry().size();
328         resize(qMin(sizeHint().width(), maxSize.width()), qMin(sizeHint().height(), maxSize.height()));
329     }
330 }
331 
slotDefaultClicked()332 void KCMultiDialog::slotDefaultClicked()
333 {
334     Q_D(KCMultiDialog);
335     const KPageWidgetItem *item = currentPage();
336     if (!item) {
337         return;
338     }
339 
340     for (int i = 0; i < d->modules.count(); ++i) {
341         if (d->modules[i].item == item) {
342             d->modules[i].kcm->defaults();
343             d->_k_clientChanged();
344             return;
345         }
346     }
347 }
348 
slotUser1Clicked()349 void KCMultiDialog::slotUser1Clicked()
350 {
351     const KPageWidgetItem *item = currentPage();
352     if (!item) {
353         return;
354     }
355 
356     Q_D(KCMultiDialog);
357     for (int i = 0; i < d->modules.count(); ++i) {
358         if (d->modules[i].item == item) {
359             d->modules[i].kcm->load();
360             d->_k_clientChanged();
361             return;
362         }
363     }
364 }
365 
moduleSave(KCModuleProxy * module)366 bool KCMultiDialogPrivate::moduleSave(KCModuleProxy *module)
367 {
368     if (!module) {
369         return false;
370     }
371 
372     module->save();
373     return true;
374 }
375 
apply()376 void KCMultiDialogPrivate::apply()
377 {
378     Q_Q(KCMultiDialog);
379     QStringList updatedComponents;
380 
381     for (const CreatedModule &module : std::as_const(modules)) {
382         KCModuleProxy *proxy = module.kcm;
383 
384         if (proxy->isChanged()) {
385             proxy->save();
386 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 85)
387             /**
388              * Add name of the components the kcm belongs to the list
389              * of updated components.
390              */
391             const QStringList componentNames = module.componentNames;
392             for (const QString &componentName : componentNames) {
393                 if (!updatedComponents.contains(componentName)) {
394                     updatedComponents.append(componentName);
395                 }
396             }
397 #endif
398         }
399     }
400 
401 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 85)
402     // Send the configCommitted signal for every updated component.
403     for (const QString &name : std::as_const(updatedComponents)) {
404         Q_EMIT q->configCommitted(name.toLatin1());
405     }
406 #endif
407 
408     Q_EMIT q->configCommitted();
409 }
410 
slotApplyClicked()411 void KCMultiDialog::slotApplyClicked()
412 {
413     QPushButton *applyButton = buttonBox()->button(QDialogButtonBox::Apply);
414     applyButton->setFocus();
415 
416     d_func()->apply();
417 }
418 
slotOkClicked()419 void KCMultiDialog::slotOkClicked()
420 {
421     QPushButton *okButton = buttonBox()->button(QDialogButtonBox::Ok);
422     okButton->setFocus();
423 
424     d_func()->apply();
425     accept();
426 }
427 
slotHelpClicked()428 void KCMultiDialog::slotHelpClicked()
429 {
430     const KPageWidgetItem *item = currentPage();
431     if (!item) {
432         return;
433     }
434 
435     Q_D(KCMultiDialog);
436     QString docPath;
437     for (int i = 0; i < d->modules.count(); ++i) {
438         if (d->modules[i].item == item) {
439 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 88)
440             docPath = d->modules[i].kcm->moduleInfo().docPath();
441 #endif
442             if (docPath.isEmpty()) {
443                 docPath = d->modules[i].kcm->metaData().value(QStringLiteral("X-DocPath"));
444             }
445             break;
446         }
447     }
448 
449     const QUrl docUrl = QUrl(QStringLiteral("help:/")).resolved(QUrl(docPath)); // same code as in KHelpClient::invokeHelp
450     const QString docUrlScheme = docUrl.scheme();
451     if (docUrlScheme == QLatin1String("man") || docUrlScheme == QLatin1String("info")) {
452         QProcess::startDetached(QStringLiteral("khelpcenter"), QStringList() << docUrl.toString());
453     } else {
454         QDesktopServices::openUrl(docUrl);
455     }
456 }
457 
closeEvent(QCloseEvent * event)458 void KCMultiDialog::closeEvent(QCloseEvent *event)
459 {
460     Q_D(KCMultiDialog);
461     KPageDialog::closeEvent(event);
462 
463     /**
464      * If we don't delete them, the DBUS registration stays, and trying to load the KCMs
465      * in other situations will lead to "module already loaded in Foo," while to the user
466      * doesn't appear so(the dialog is hidden)
467      */
468     for (auto &proxy : std::as_const(d->modules)) {
469         proxy.kcm->deleteClient();
470     }
471 }
472 
addModule(const KPluginMetaData & metaData)473 KPageWidgetItem *KCMultiDialog::addModule(const KPluginMetaData &metaData)
474 {
475     return addModule(metaData, QStringList());
476 }
477 
addModule(const KPluginMetaData & metaData,const QStringList & args)478 KPageWidgetItem *KCMultiDialog::addModule(const KPluginMetaData &metaData, const QStringList &args)
479 {
480     Q_D(KCMultiDialog);
481     // Create the scroller
482     auto *moduleScroll = new UnboundScrollArea(this);
483     // Prepare the scroll area
484     moduleScroll->setWidgetResizable(true);
485     moduleScroll->setFrameStyle(QFrame::NoFrame);
486     moduleScroll->viewport()->setAutoFillBackground(false);
487 
488     KCModuleProxy *kcm = new KCModuleProxy(metaData, moduleScroll, args);
489     moduleScroll->setWidget(kcm);
490 
491     KPageWidgetItem *item = new KPageWidgetItem(moduleScroll, metaData.name());
492 
493     KCMultiDialogPrivate::CreatedModule createdModule;
494     createdModule.kcm = kcm;
495     createdModule.item = item;
496     d->modules.append(createdModule);
497 
498     if (qobject_cast<KCModuleQml *>(kcm->realModule())) {
499         item->setHeaderVisible(false);
500     }
501 
502     if (kcm->realModule() && kcm->realModule()->useRootOnlyMessage()) {
503         item->setHeader(QStringLiteral("<b>%1</b><br><i>%2</i>").arg(metaData.name(), kcm->realModule()->rootOnlyMessage()));
504         item->setIcon(KIconUtils::addOverlay(QIcon::fromTheme(metaData.iconName()), QIcon::fromTheme(QStringLiteral("dialog-warning")), Qt::BottomRightCorner));
505     } else {
506         item->setHeader(metaData.name());
507         item->setIcon(QIcon::fromTheme(metaData.iconName()));
508     }
509     const int weight = metaData.rawData().value(QStringLiteral("X-KDE-Weight")).toInt();
510     item->setProperty("_k_weight", weight);
511 
512     bool updateCurrentPage = false;
513     const KPageWidgetModel *model = qobject_cast<const KPageWidgetModel *>(pageWidget()->model());
514     Q_ASSERT(model);
515     const int siblingCount = model->rowCount();
516     int row = 0;
517     for (; row < siblingCount; ++row) {
518         KPageWidgetItem *siblingItem = model->item(model->index(row, 0));
519         if (siblingItem->property("_k_weight").toInt() > weight) {
520             // the item we found is heavier than the new module
521             // qDebug() << "adding KCM " << item->name() << " before " << siblingItem->name();
522             insertPage(siblingItem, item);
523             if (siblingItem == currentPage()) {
524                 updateCurrentPage = true;
525             }
526 
527             break;
528         }
529     }
530     if (row == siblingCount) {
531         // the new module is either the first or the heaviest item
532         // qDebug() << "adding KCM " << item->name() << " at the top level";
533         addPage(item);
534     }
535 
536     QObject::connect(kcm, qOverload<bool>(&KCModuleProxy::changed), this, [d]() {
537         d->_k_clientChanged();
538     });
539 
540     QObject::connect(kcm->realModule(), &KCModule::rootOnlyMessageChanged, this, [d](bool use, const QString &message) {
541         d->_k_updateHeader(use, message);
542     });
543 
544     if (d->modules.count() == 1 || updateCurrentPage) {
545         setCurrentPage(item);
546         d->_k_clientChanged();
547     }
548     return item;
549 }
550 
551 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 85)
addModule(const QString & path,const QStringList & args)552 KPageWidgetItem *KCMultiDialog::addModule(const QString &path, const QStringList &args)
553 {
554     QString complete = path;
555 
556     if (!path.endsWith(QLatin1String(".desktop"))) {
557         complete += QStringLiteral(".desktop");
558     }
559 
560     KService::Ptr service = KService::serviceByStorageId(complete);
561 
562     return addModule(KCModuleInfo(service), nullptr, args);
563 }
564 #endif
565 
566 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 85)
addModule(const KCModuleInfo & moduleInfo,KPageWidgetItem * parentItem,const QStringList & args)567 KPageWidgetItem *KCMultiDialog::addModule(const KCModuleInfo &moduleInfo, KPageWidgetItem *parentItem, const QStringList &args)
568 {
569     Q_D(KCMultiDialog);
570     if (!moduleInfo.isValid()) {
571         return nullptr;
572     }
573 
574     // KAuthorized::authorizeControlModule( moduleInfo.service()->menuId() ) is
575     // checked in noDisplay already
576     if (moduleInfo.service() && moduleInfo.service()->noDisplay()) {
577         return nullptr;
578     }
579 
580     // Create the scroller
581     auto *moduleScroll = new UnboundScrollArea(this);
582     // Prepare the scroll area
583     moduleScroll->setWidgetResizable(true);
584     moduleScroll->setFrameStyle(QFrame::NoFrame);
585     moduleScroll->viewport()->setAutoFillBackground(false);
586 
587     KCModuleProxy *kcm = new KCModuleProxy(moduleInfo, moduleScroll, args);
588     moduleScroll->setWidget(kcm);
589 
590     // qDebug() << moduleInfo.moduleName();
591     KPageWidgetItem *item = new KPageWidgetItem(moduleScroll, moduleInfo.moduleName());
592 
593     KCMultiDialogPrivate::CreatedModule cm;
594     cm.kcm = kcm;
595     cm.item = item;
596     cm.componentNames = moduleInfo.property(QStringLiteral("X-KDE-ParentComponents")).toStringList();
597     d->modules.append(cm);
598 
599     if (qobject_cast<KCModuleQml *>(kcm->realModule())) {
600         item->setHeaderVisible(false);
601     }
602 
603     if (kcm->realModule() && kcm->realModule()->useRootOnlyMessage()) {
604         item->setHeader(QStringLiteral("<b>") + moduleInfo.moduleName() + QStringLiteral("</b><br><i>") + kcm->realModule()->rootOnlyMessage()
605                         + QStringLiteral("</i>"));
606         item->setIcon(KIconUtils::addOverlay(QIcon::fromTheme(moduleInfo.icon()), QIcon::fromTheme(QStringLiteral("dialog-warning")), Qt::BottomRightCorner));
607     } else {
608         item->setHeader(moduleInfo.moduleName());
609         item->setIcon(QIcon::fromTheme(moduleInfo.icon()));
610     }
611     item->setProperty("_k_weight", moduleInfo.weight());
612 
613     bool updateCurrentPage = false;
614     const KPageWidgetModel *model = qobject_cast<const KPageWidgetModel *>(pageWidget()->model());
615     Q_ASSERT(model);
616     if (parentItem) {
617         const QModelIndex parentIndex = model->index(parentItem);
618         const int siblingCount = model->rowCount(parentIndex);
619         int row = 0;
620         for (; row < siblingCount; ++row) {
621             KPageWidgetItem *siblingItem = model->item(model->index(row, 0, parentIndex));
622             if (siblingItem->property("_k_weight").toInt() > moduleInfo.weight()) {
623                 // the item we found is heavier than the new module
624                 // qDebug() << "adding KCM " << item->name() << " before " << siblingItem->name();
625                 insertPage(siblingItem, item);
626                 break;
627             }
628         }
629         if (row >= siblingCount) {
630             // the new module is either the first or the heaviest item
631             // qDebug() << "adding KCM " << item->name() << " with parent " << parentItem->name();
632             addSubPage(parentItem, item);
633         }
634     } else {
635         const int siblingCount = model->rowCount();
636         int row = 0;
637         for (; row < siblingCount; ++row) {
638             KPageWidgetItem *siblingItem = model->item(model->index(row, 0));
639             if (siblingItem->property("_k_weight").toInt() > moduleInfo.weight()) {
640                 // the item we found is heavier than the new module
641                 // qDebug() << "adding KCM " << item->name() << " before " << siblingItem->name();
642                 insertPage(siblingItem, item);
643                 if (siblingItem == currentPage()) {
644                     updateCurrentPage = true;
645                 }
646 
647                 break;
648             }
649         }
650         if (row == siblingCount) {
651             // the new module is either the first or the heaviest item
652             // qDebug() << "adding KCM " << item->name() << " at the top level";
653             addPage(item);
654         }
655     }
656 
657     QObject::connect(kcm, qOverload<bool>(&KCModuleProxy::changed), this, [d]() {
658         d->_k_clientChanged();
659     });
660 
661     QObject::connect(kcm->realModule(), &KCModule::rootOnlyMessageChanged, this, [d](bool use, QString message) {
662         d->_k_updateHeader(use, message);
663     });
664 
665     if (d->modules.count() == 1 || updateCurrentPage) {
666         setCurrentPage(item);
667         d->_k_clientChanged();
668     }
669     return item;
670 }
671 #endif
672 
clear()673 void KCMultiDialog::clear()
674 {
675     Q_D(KCMultiDialog);
676     // qDebug() ;
677 
678     for (int i = 0; i < d->modules.count(); ++i) {
679         removePage(d->modules[i].item);
680         delete d->modules[i].kcm;
681     }
682 
683     d->modules.clear();
684 
685     d->_k_clientChanged();
686 }
687 
688 #include "moc_kcmultidialog.cpp"
689