File indexing completed on 2024-04-28 05:36:35

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