File indexing completed on 2023-09-24 09:41:13

0001 /*
0002  *   SPDX-FileCopyrightText: 2009 Ben Cooksley <bcooksley@kde.org>
0003  *   SPDX-FileCopyrightText: 2009 Mathias Soeken <msoeken@informatik.uni-bremen.de>
0004  *   SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org>
0005  *
0006  *   SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "ModuleView.h"
0010 #include "ExternalAppModule.h"
0011 
0012 #include <QDesktopServices>
0013 #include <QDialogButtonBox>
0014 #include <QKeyEvent>
0015 #include <QList>
0016 #include <QLoggingCategory>
0017 #include <QMap>
0018 #include <QPainter>
0019 #include <QProcess>
0020 #include <QPushButton>
0021 #include <QScrollArea>
0022 #include <QStyle>
0023 #include <QVBoxLayout>
0024 #include <QWhatsThis>
0025 
0026 #include <KAboutData>
0027 #include <kauth_version.h>
0028 #if KAUTH_VERSION >= QT_VERSION_CHECK(5, 92, 0)
0029 #include <KAuth/Action>
0030 #include <KAuth/ObjectDecorator>
0031 #else
0032 #include <KAuthAction>
0033 #include <KAuthObjectDecorator>
0034 #endif
0035 #include <KAuthorized>
0036 #include <KCModuleInfo>
0037 #include <KCModuleProxy>
0038 #include <KColorScheme>
0039 #include <KMessageDialog>
0040 #include <KPageWidget>
0041 #include <KSharedConfig>
0042 #include <KStandardGuiItem>
0043 #include <KTitleWidget>
0044 #include <Kirigami/Units>
0045 
0046 #include <KActivities/ResourceInstance>
0047 
0048 #include <cmath>
0049 #include <kpluginmetadata.h>
0050 
0051 #include "MenuItem.h"
0052 
0053 class CustomTitle : public KTitleWidget
0054 {
0055 public:
0056     explicit CustomTitle(QWidget *parent = nullptr);
0057 
0058 protected:
0059     void paintEvent(QPaintEvent *event) override;
0060     void colorsChanged();
0061 };
0062 
0063 CustomTitle::CustomTitle(QWidget *parent)
0064     : KTitleWidget(parent)
0065 {
0066     // Use the same left margin as QML titles for consistency (Kirigami/AbstractPageHeader.qml)
0067     setContentsMargins(Kirigami::Units().gridUnit(),
0068                        style()->pixelMetric(QStyle::PM_LayoutTopMargin),
0069                        style()->pixelMetric(QStyle::PM_LayoutRightMargin),
0070                        style()->pixelMetric(QStyle::PM_LayoutBottomMargin));
0071 
0072     colorsChanged();
0073     connect(qApp, &QApplication::paletteChanged, this, &CustomTitle::colorsChanged);
0074 }
0075 
0076 void CustomTitle::colorsChanged()
0077 {
0078     auto config = KSharedConfig::openConfig();
0079     auto active = KColorScheme(QPalette::Active, KColorScheme::Header, config);
0080     auto inactive = KColorScheme(QPalette::Inactive, KColorScheme::Header, config);
0081     auto disabled = KColorScheme(QPalette::Disabled, KColorScheme::Header, config);
0082 
0083     QPalette palette = KColorScheme::createApplicationPalette(config);
0084 
0085     palette.setBrush(QPalette::Active, QPalette::Window, active.background());
0086     palette.setBrush(QPalette::Active, QPalette::WindowText, active.foreground());
0087     palette.setBrush(QPalette::Disabled, QPalette::Window, disabled.background());
0088     palette.setBrush(QPalette::Disabled, QPalette::WindowText, disabled.foreground());
0089     palette.setBrush(QPalette::Inactive, QPalette::Window, inactive.background());
0090     palette.setBrush(QPalette::Inactive, QPalette::WindowText, inactive.foreground());
0091 
0092     setPalette(palette);
0093 }
0094 
0095 void CustomTitle::paintEvent(QPaintEvent *event)
0096 {
0097     KTitleWidget::paintEvent(event);
0098 
0099     auto linearlyInterpolateDouble = [](double one, double two, double factor) {
0100         return one + (two - one) * factor;
0101     };
0102 
0103     QPainter p(this);
0104 
0105     const QColor window = palette().color(QPalette::Window);
0106     const QColor text = palette().color(QPalette::Text);
0107     const qreal balance = 0.2;
0108 
0109     const QColor separator = QColor::fromHsv(std::fmod(linearlyInterpolateDouble(window.hue(), text.hue(), balance), 360.0),
0110                                              qBound(0.0, linearlyInterpolateDouble(window.saturation(), text.saturation(), balance), 255.0),
0111                                              qBound(0.0, linearlyInterpolateDouble(window.value(), text.value(), balance), 255.0),
0112                                              qBound(0.0, linearlyInterpolateDouble(window.alpha(), text.alpha(), balance), 255.0));
0113     p.fillRect(event->rect(), window);
0114     p.fillRect(QRect(QPoint(0, height() - 1), QSize(width(), 1)), separator);
0115 }
0116 
0117 class ModuleView::Private
0118 {
0119 public:
0120     Private()
0121     {
0122     }
0123     QMap<KPageWidgetItem *, KCModuleProxy *> mPages;
0124     QMap<KPageWidgetItem *, QString> mPagesPluginIdMap;
0125     KPageWidget *mPageWidget = nullptr;
0126     CustomTitle *mCustomHeader = nullptr;
0127     QVBoxLayout *mLayout = nullptr;
0128     QDialogButtonBox *mButtons = nullptr;
0129     KAuth::ObjectDecorator *mApplyAuthorize = nullptr;
0130     QPushButton *mApply = nullptr;
0131     QPushButton *mReset = nullptr;
0132     QPushButton *mDefault = nullptr;
0133     QPushButton *mHelp = nullptr;
0134     KMessageDialog *mResolvingChangesDialog = nullptr;
0135     bool pageChangeSupressed = false;
0136     bool mSaveStatistics = true;
0137     bool mDefaultsIndicatorsVisible = false;
0138     KCModule::Buttons mButtonMask = ~KCModule::Buttons(KCModule::NoAdditionalButton);
0139 };
0140 
0141 ModuleView::ModuleView(QWidget *parent)
0142     : QWidget(parent)
0143     , d(new Private())
0144 {
0145     QVBoxLayout *rootLayout = new QVBoxLayout(this);
0146     rootLayout->setContentsMargins(0, 0, 0, 0);
0147     rootLayout->setSpacing(0);
0148     // Configure a layout first
0149     d->mLayout = new QVBoxLayout();
0150     // Create the Page Widget
0151     d->mPageWidget = new KPageWidget(this);
0152     d->mCustomHeader = new CustomTitle(this);
0153     d->mCustomHeader->setVisible(false);
0154 
0155     rootLayout->addWidget(d->mCustomHeader);
0156     rootLayout->addItem(d->mLayout);
0157     // d->mPageWidget->setPageHeader(d->mCustomHeader);
0158     d->mPageWidget->layout()->setContentsMargins(0, 0, 0, 0);
0159 
0160     // Zero out only the horizontal spacing (the vertical spacing is fine)
0161     QGridLayout *gridLayout = static_cast<QGridLayout *>(d->mPageWidget->layout());
0162 
0163     gridLayout->setHorizontalSpacing(0);
0164 
0165     d->mLayout->addWidget(d->mPageWidget);
0166     // Create the dialog
0167     d->mButtons = new QDialogButtonBox(Qt::Horizontal, this);
0168     d->mLayout->addWidget(d->mButtons);
0169 
0170     // Create the buttons in it
0171     d->mApply = d->mButtons->addButton(QDialogButtonBox::Apply);
0172     KGuiItem::assign(d->mApply, KStandardGuiItem::apply());
0173     d->mDefault = d->mButtons->addButton(QDialogButtonBox::RestoreDefaults);
0174     KGuiItem::assign(d->mDefault, KStandardGuiItem::defaults());
0175     d->mReset = d->mButtons->addButton(QDialogButtonBox::Reset);
0176     KGuiItem::assign(d->mReset, KStandardGuiItem::reset());
0177     d->mHelp = d->mButtons->addButton(QDialogButtonBox::Help);
0178     KGuiItem::assign(d->mHelp, KStandardGuiItem::help());
0179     // Set some more sensible tooltips
0180     d->mReset->setToolTip(i18n("Reset all current changes to previous values"));
0181     // Set Auto-Default mode ( KDE Bug #211187 )
0182     d->mApply->setAutoDefault(true);
0183     d->mDefault->setAutoDefault(true);
0184     d->mReset->setAutoDefault(true);
0185     d->mHelp->setAutoDefault(true);
0186     // Prevent the buttons from being used
0187     d->mApply->setEnabled(false);
0188     d->mDefault->setEnabled(false);
0189     d->mReset->setEnabled(false);
0190     d->mHelp->setEnabled(false);
0191     // Connect up the buttons
0192     connect(d->mApply, SIGNAL(clicked()), this, SLOT(moduleSave()));
0193     connect(d->mReset, &QAbstractButton::clicked, this, &ModuleView::moduleLoad);
0194     connect(d->mHelp, &QAbstractButton::clicked, this, &ModuleView::moduleHelp);
0195     connect(d->mDefault, &QAbstractButton::clicked, this, &ModuleView::moduleDefaults);
0196     // clang-format off
0197     connect(d->mPageWidget, SIGNAL(currentPageChanged(KPageWidgetItem*,KPageWidgetItem*)),
0198              this, SLOT(activeModuleChanged(KPageWidgetItem*,KPageWidgetItem*)));
0199     // clang-format on
0200     d->mApplyAuthorize = new KAuth::ObjectDecorator(d->mApply);
0201     d->mApplyAuthorize->setAuthAction(KAuth::Action());
0202 }
0203 
0204 ModuleView::~ModuleView()
0205 {
0206     delete d;
0207 }
0208 
0209 QString ModuleView::activeModuleName() const
0210 {
0211     return d->mPageWidget->currentPage() ? d->mPageWidget->currentPage()->name() : QString();
0212 }
0213 
0214 void ModuleView::loadModule(const QModelIndex &menuItem, const QStringList &args)
0215 {
0216     if (!menuItem.isValid()) {
0217         return;
0218     }
0219 
0220     MenuItem *item = menuItem.data(Qt::UserRole).value<MenuItem *>();
0221 
0222     // if module has a main page (like in Appearance > Global Theme) we'll load that
0223     if (item->isLibrary() || item->isExternalAppModule()) {
0224         addModule(item, args);
0225     }
0226     // if module doesn't have a main page, we'll load the first subpage
0227     else if (menuItem.model()->rowCount(menuItem) > 0) {
0228         MenuItem *subpageItem = menuItem.model()->index(0, 0, menuItem).data(Qt::UserRole).value<MenuItem *>();
0229         addModule(subpageItem, args);
0230     }
0231 }
0232 
0233 void ModuleView::addModule(MenuItem *item, const QStringList &args)
0234 {
0235     const KPluginMetaData data = item->metaData();
0236     if (!KAuthorized::authorizeControlModule(data.pluginId())) {
0237         qWarning() << "Not authorised to load module";
0238         return;
0239     }
0240     if (data.isHidden()) {
0241         return;
0242     }
0243 
0244     if (KPageWidgetItem *page = d->mPagesPluginIdMap.key(data.name())) {
0245         activeModuleChanged(page, d->mPageWidget->currentPage());
0246         return;
0247     }
0248 
0249     // Create the scroller
0250     auto *moduleScroll = new QScrollArea(this);
0251     // Prepare the scroll area
0252     moduleScroll->setWidgetResizable(true);
0253     moduleScroll->setFrameStyle(QFrame::NoFrame);
0254     moduleScroll->viewport()->setAutoFillBackground(false);
0255     // Create the page
0256     auto *page = new KPageWidgetItem(moduleScroll, data.name());
0257     // Provide information to the users
0258 
0259     if (item->isExternalAppModule()) {
0260         auto *externalWidget = new ExternalAppModule(this, KService::Ptr(new KService(item->metaData().metaDataFileName())));
0261         moduleScroll->setWidget(externalWidget);
0262         d->mCustomHeader->setText(item->metaData().name()); // We have to set this manually, BUG: 448672
0263         page->setName(QString());
0264     } else { // It must be a normal module then
0265         auto *moduleProxy = new KCModuleProxy(data, moduleScroll, args);
0266         moduleScroll->setWidget(moduleProxy);
0267         moduleProxy->setAutoFillBackground(false);
0268         connect(moduleProxy, &KCModuleProxy::changed, this, &ModuleView::stateChanged);
0269         d->mPages.insert(page, moduleProxy);
0270     }
0271 
0272     d->mPagesPluginIdMap.insert(page, data.name());
0273     updatePageIconHeader(page);
0274     // Add the new page
0275     d->mPageWidget->addPage(page);
0276 }
0277 
0278 void ModuleView::updatePageIconHeader(KPageWidgetItem *page)
0279 {
0280     if (!page) {
0281         // Page is invalid. Probably means we have a race condition during closure of everyone so do nothing
0282         return;
0283     }
0284 
0285     KCModuleProxy *moduleProxy = d->mPages.value(page);
0286     if (!moduleProxy || !moduleProxy->metaData().isValid()) {
0287         // Seems like we have some form of a race condition going on here...
0288         return;
0289     }
0290 
0291     const QString moduleName = moduleProxy->metaData().name();
0292     page->setHeader(moduleName);
0293     page->setIcon(QIcon::fromTheme(moduleProxy->metaData().iconName()));
0294 
0295     const bool isQml = moduleProxy->realModule() && moduleProxy->realModule()->inherits("KCModuleQml");
0296     const bool isSidebar = faceType() == KPageView::Plain;
0297 
0298     // Use the module's header only for QWidgets KCMs on Icons mode
0299     page->setHeaderVisible(!isQml && !isSidebar);
0300 
0301     // Use the custom header only for QWidgets KCMs on Sidebar mode
0302     // Only affect visibility if it's the current page
0303     if (d->mPageWidget->currentPage() == page) {
0304         if (!isQml && isSidebar) {
0305             d->mCustomHeader->setText(moduleName); // also includes show()
0306         } else {
0307             d->mCustomHeader->hide();
0308         }
0309     }
0310 }
0311 
0312 bool ModuleView::resolveChanges()
0313 {
0314     KCModuleProxy *currentProxy = d->mPages.value(d->mPageWidget->currentPage());
0315     return resolveChanges(currentProxy);
0316 }
0317 
0318 bool ModuleView::resolveChanges(KCModuleProxy *currentProxy)
0319 {
0320     if (!currentProxy || !currentProxy->isChanged()) {
0321         return true;
0322     }
0323 
0324     // if we are already resolving changes handle it like a cancel
0325     if (d->mResolvingChangesDialog) {
0326         d->mResolvingChangesDialog->reject();
0327     }
0328 
0329     // Let the user decide
0330     d->mResolvingChangesDialog = new KMessageDialog(KMessageDialog::WarningTwoActionsCancel,
0331                                                     i18n("The settings of the current module have changed.\n"
0332                                                          "Do you want to apply the changes or discard them?"),
0333                                                     this);
0334     d->mResolvingChangesDialog->setAttribute(Qt::WA_DeleteOnClose);
0335     d->mResolvingChangesDialog->setButtons(KStandardGuiItem::apply(), KStandardGuiItem::discard(), KStandardGuiItem::cancel());
0336     d->mResolvingChangesDialog->setCaption(i18n("Apply Settings"));
0337     int result = d->mResolvingChangesDialog->exec();
0338     d->mResolvingChangesDialog = nullptr;
0339 
0340     switch (result) {
0341     case KMessageDialog::PrimaryAction:
0342         return moduleSave(currentProxy);
0343     case KMessageDialog::SecondaryAction:
0344         currentProxy->load();
0345         return true;
0346     case KMessageDialog::Cancel:
0347         return false;
0348     default:
0349         Q_ASSERT(false);
0350         return false;
0351     }
0352 }
0353 
0354 void ModuleView::closeModules()
0355 {
0356     d->pageChangeSupressed = true;
0357     d->mApplyAuthorize->setAuthAction(KAuth::Action()); // Ensure KAuth knows that authentication is now pointless...
0358     for (auto page = d->mPagesPluginIdMap.cbegin(); page != d->mPagesPluginIdMap.cend(); ++page) {
0359         d->mPageWidget->removePage(page.key());
0360     }
0361 
0362     d->mPages.clear();
0363     d->mPagesPluginIdMap.clear();
0364     d->pageChangeSupressed = false;
0365 }
0366 
0367 bool ModuleView::moduleSave()
0368 {
0369     KCModuleProxy *moduleProxy = d->mPages.value(d->mPageWidget->currentPage());
0370     return moduleSave(moduleProxy);
0371 }
0372 
0373 bool ModuleView::moduleSave(KCModuleProxy *module)
0374 {
0375     if (!module) {
0376         return false;
0377     }
0378 
0379     module->save();
0380     Q_EMIT moduleSaved();
0381     return true;
0382 }
0383 
0384 void ModuleView::moduleLoad()
0385 {
0386     KCModuleProxy *activeModule = d->mPages.value(d->mPageWidget->currentPage());
0387     if (activeModule) {
0388         activeModule->load();
0389     }
0390 }
0391 
0392 void ModuleView::moduleDefaults()
0393 {
0394     KCModuleProxy *activeModule = d->mPages.value(d->mPageWidget->currentPage());
0395     if (activeModule) {
0396         activeModule->defaults();
0397     }
0398 }
0399 
0400 void ModuleView::moduleHelp()
0401 {
0402     KCModuleProxy *activeModule = d->mPages.value(d->mPageWidget->currentPage());
0403     if (!activeModule) {
0404         return;
0405     }
0406 
0407     const QString docPath = activeModule->metaData().value(QStringLiteral("X-DocPath"));
0408     if (docPath.isEmpty()) {
0409         return;
0410     }
0411 
0412     // UrlHandler from KGUIAddons sets a handler for help:/ urls, which opens khelpcenter
0413     // if it's available or falls back to opening the relevant page at docs.kde.org
0414     QDesktopServices::openUrl(QUrl(QStringLiteral("help:/") + docPath));
0415 }
0416 
0417 void ModuleView::activeModuleChanged(KPageWidgetItem *current, KPageWidgetItem *previous)
0418 {
0419     d->mPageWidget->blockSignals(true);
0420     d->mPageWidget->setCurrentPage(previous);
0421     KCModuleProxy *previousModule = d->mPages.value(previous);
0422     if (resolveChanges(previousModule)) {
0423         d->mPageWidget->setCurrentPage(current);
0424     }
0425     d->mPageWidget->blockSignals(false);
0426     if (d->pageChangeSupressed) {
0427         return;
0428     }
0429 
0430     // We need to get the state of the now active module
0431     stateChanged();
0432 
0433     KCModuleProxy *activeModule = d->mPages.value(d->mPageWidget->currentPage());
0434     if (!activeModule) {
0435         return;
0436     }
0437 
0438     // TODO: if we'll ever need statistics for kinfocenter modules, save them with an URL like "kinfo:"
0439     if (d->mSaveStatistics && activeModule->metaData().pluginId() != QStringLiteral("kcm_landingpage")) {
0440         KActivities::ResourceInstance::notifyAccessed(QUrl(QStringLiteral("kcm:") + activeModule->metaData().pluginId()),
0441                                                       QStringLiteral("org.kde.systemsettings"));
0442     }
0443 
0444     d->mLayout->setContentsMargins(0, 0, 0, 0);
0445     d->mLayout->setSpacing(0);
0446     d->mButtons->setContentsMargins(style()->pixelMetric(QStyle::PM_LayoutLeftMargin),
0447                                     0, // Remove extra space between KCM content and bottom buttons
0448                                     style()->pixelMetric(QStyle::PM_LayoutRightMargin),
0449                                     style()->pixelMetric(QStyle::PM_LayoutBottomMargin));
0450     d->mPageWidget->layout()->setSpacing(0);
0451     if (auto titleWidget = qobject_cast<KTitleWidget *>(d->mPageWidget->pageHeader())) {
0452         titleWidget->layout()->setContentsMargins(Kirigami::Units().gridUnit(),
0453                                                   style()->pixelMetric(QStyle::PM_LayoutRightMargin),
0454                                                   style()->pixelMetric(QStyle::PM_LayoutRightMargin),
0455                                                   style()->pixelMetric(QStyle::PM_LayoutBottomMargin));
0456     }
0457 
0458     updatePageIconHeader(current);
0459     moduleShowDefaultsIndicators(d->mDefaultsIndicatorsVisible);
0460 }
0461 
0462 void ModuleView::stateChanged()
0463 {
0464     updatePageIconHeader(d->mPageWidget->currentPage());
0465     updateButtons();
0466 
0467     KCModuleProxy *activeModule = d->mPages.value(d->mPageWidget->currentPage());
0468     Q_EMIT moduleChanged(activeModule && activeModule->isChanged());
0469 }
0470 
0471 void ModuleView::updateButtons()
0472 {
0473     KCModuleProxy *activeModule = d->mPages.value(d->mPageWidget->currentPage());
0474     KAuth::Action moduleAction;
0475     bool change = false;
0476     bool defaulted = false;
0477     KCModule::Buttons buttons = KCModule::NoAdditionalButton;
0478 
0479     if (activeModule) {
0480         buttons = activeModule->buttons() & d->mButtonMask;
0481         change = activeModule->isChanged();
0482         defaulted = activeModule->defaulted();
0483 
0484         // Do not display Help button if there is no docPath available
0485         if (activeModule->metaData().value(QStringLiteral("X-DocPath")).isEmpty()) {
0486             buttons &= ~KCModule::Help;
0487         }
0488 
0489         disconnect(d->mApplyAuthorize, SIGNAL(authorized(KAuth::Action)), this, SLOT(moduleSave()));
0490         disconnect(d->mApply, SIGNAL(clicked()), this, SLOT(moduleSave()));
0491         if (activeModule->realModule()->authAction().isValid()) {
0492             connect(d->mApplyAuthorize, SIGNAL(authorized(KAuth::Action)), this, SLOT(moduleSave()));
0493             moduleAction = activeModule->realModule()->authAction();
0494         } else {
0495             connect(d->mApply, SIGNAL(clicked()), this, SLOT(moduleSave()));
0496         }
0497     }
0498 
0499     d->mApplyAuthorize->setAuthAction(moduleAction);
0500     d->mDefault->setEnabled(!defaulted);
0501     d->mDefault->setVisible(buttons & KCModule::Default);
0502     d->mApply->setEnabled(change);
0503     d->mApply->setVisible(buttons & KCModule::Apply);
0504     d->mReset->setEnabled(change);
0505     d->mReset->setVisible(buttons & KCModule::Apply);
0506     d->mHelp->setEnabled(buttons & KCModule::Help);
0507     d->mHelp->setVisible(buttons & KCModule::Help);
0508 
0509     d->mButtons->setVisible(buttons != KCModule::NoAdditionalButton);
0510 }
0511 
0512 void ModuleView::keyPressEvent(QKeyEvent *event)
0513 {
0514     if (event->key() == Qt::Key_F1 && d->mHelp->isVisible() && d->mHelp->isEnabled()) {
0515         d->mHelp->animateClick();
0516         event->accept();
0517         return;
0518     } else if (event->key() == Qt::Key_Escape) {
0519         event->accept();
0520         Q_EMIT closeRequest();
0521         return;
0522     } else if (event->key() == Qt::Key_F1 && event->modifiers() == Qt::ShiftModifier) {
0523         QWhatsThis::enterWhatsThisMode();
0524         event->accept();
0525         return;
0526     }
0527 
0528     QWidget::keyPressEvent(event);
0529 }
0530 
0531 void ModuleView::setFaceType(KPageView::FaceType type)
0532 {
0533     d->mPageWidget->setFaceType(type);
0534 }
0535 
0536 KPageView::FaceType ModuleView::faceType() const
0537 {
0538     return d->mPageWidget->faceType();
0539 }
0540 
0541 void ModuleView::setSaveStatistics(bool save)
0542 {
0543     d->mSaveStatistics = save;
0544 }
0545 
0546 bool ModuleView::saveStatistics() const
0547 {
0548     return d->mSaveStatistics;
0549 }
0550 
0551 void ModuleView::setApplyVisible(bool visible)
0552 {
0553     d->mButtonMask.setFlag(KCModule::Apply, visible);
0554     updateButtons();
0555 }
0556 
0557 bool ModuleView::isApplyVisible() const
0558 {
0559     return d->mApply->isVisible();
0560 }
0561 
0562 void ModuleView::setDefaultsVisible(bool visible)
0563 {
0564     d->mButtonMask.setFlag(KCModule::Default, visible);
0565     updateButtons();
0566 }
0567 
0568 bool ModuleView::isDefaultsVisible() const
0569 {
0570     return d->mDefault->isVisible();
0571 }
0572 
0573 bool ModuleView::isResetVisible() const
0574 {
0575     return d->mReset->isVisible();
0576 }
0577 
0578 void ModuleView::moduleShowDefaultsIndicators(bool show)
0579 {
0580     d->mDefaultsIndicatorsVisible = show;
0581     KCModuleProxy *activeModule = d->mPages.value(d->mPageWidget->currentPage());
0582     if (activeModule) {
0583         activeModule->setDefaultsIndicatorsVisible(show);
0584     }
0585 }
0586 
0587 void ModuleView::setHeaderHeight(qreal height)
0588 {
0589     if (height == d->mCustomHeader->minimumHeight()) {
0590         return;
0591     }
0592 
0593     d->mCustomHeader->setMinimumHeight(height);
0594 }
0595 
0596 qreal ModuleView::headerHeight() const
0597 {
0598     return d->mCustomHeader->minimumHeight();
0599 }
0600 
0601 void ModuleView::setActiveModule(const QString &moduleName)
0602 {
0603     const auto pageList = d->mPagesPluginIdMap.keys();
0604     for (const auto page : pageList) {
0605         if (d->mPagesPluginIdMap.value(page) == moduleName) {
0606             d->mPageWidget->setCurrentPage(page);
0607             break;
0608         }
0609     }
0610 }
0611 
0612 KPluginMetaData ModuleView::activeModuleMetadata() const
0613 {
0614     KCModuleProxy *activeModule = d->mPages.value(d->mPageWidget->currentPage());
0615     if (!activeModule) {
0616         return {};
0617     }
0618     return activeModule->metaData();
0619 }