File indexing completed on 2023-10-03 10:29:59

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2003 Benjamin C Meyer <ben+kdelibs at meyerhome dot net>
0004     SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
0005     SPDX-FileCopyrightText: 2004 Michael Brade <brade@kde.org>
0006     SPDX-FileCopyrightText: 2021 Ahmad Samir <a.samirh78@gmail.com>
0007 
0008     SPDX-License-Identifier: LGPL-2.0-or-later
0009 */
0010 
0011 #include "kconfigdialog.h"
0012 
0013 #include <KCoreConfigSkeleton>
0014 #include <KLocalizedString>
0015 #include <KPageWidgetModel>
0016 #include <kconfigdialogmanager.h>
0017 #include <khelpclient.h>
0018 
0019 #include <QDialogButtonBox>
0020 #include <QIcon>
0021 #include <QPushButton>
0022 #include <QScrollArea>
0023 #include <QScrollBar>
0024 #include <QVBoxLayout>
0025 
0026 #include <vector>
0027 
0028 class KConfigDialogPrivate
0029 {
0030 public:
0031     KConfigDialogPrivate(const QString &name, KCoreConfigSkeleton *config, KConfigDialog *qq)
0032         : q(qq)
0033     {
0034         const QString dialogName = !name.isEmpty() ? name : QString::asprintf("SettingsDialog-%p", static_cast<void *>(q));
0035 
0036         q->setObjectName(dialogName);
0037         q->setWindowTitle(i18nc("@title:window", "Configure"));
0038         q->setFaceType(KPageDialog::List);
0039         s_openDialogs.push_back({dialogName, q});
0040 
0041         QDialogButtonBox *buttonBox = q->buttonBox();
0042         buttonBox->setStandardButtons(QDialogButtonBox::RestoreDefaults | QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel
0043                                       | QDialogButtonBox::Help);
0044         QObject::connect(buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, q, &KConfigDialog::updateSettings);
0045         QObject::connect(buttonBox->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, q, &KConfigDialog::updateSettings);
0046         QObject::connect(buttonBox->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, q, [this]() {
0047             updateButtons();
0048         });
0049         QObject::connect(buttonBox->button(QDialogButtonBox::Cancel), &QAbstractButton::clicked, q, &KConfigDialog::updateWidgets);
0050         QObject::connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, q, &KConfigDialog::updateWidgetsDefault);
0051         QObject::connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, q, [this]() {
0052             updateButtons();
0053         });
0054         QObject::connect(buttonBox->button(QDialogButtonBox::Help), &QAbstractButton::clicked, q, &KConfigDialog::showHelp);
0055 
0056         QObject::connect(q, &KPageDialog::pageRemoved, q, &KConfigDialog::onPageRemoved);
0057 
0058         manager = new KConfigDialogManager(q, config);
0059         setupManagerConnections(manager);
0060 
0061         if (QPushButton *applyButton = q->buttonBox()->button(QDialogButtonBox::Apply)) {
0062             applyButton->setEnabled(false);
0063         };
0064     }
0065 
0066     KPageWidgetItem *addPageInternal(QWidget *page, const QString &itemName, const QString &pixmapName, const QString &header);
0067 
0068     void setupManagerConnections(KConfigDialogManager *manager);
0069 
0070     void updateApplyButton();
0071     void updateDefaultsButton();
0072     void updateButtons();
0073     void settingsChangedSlot();
0074 
0075     KConfigDialog *const q;
0076     QString mAnchor;
0077     QString mHelpApp;
0078     bool shown = false;
0079     KConfigDialogManager *manager = nullptr;
0080 
0081     struct WidgetManager {
0082         QWidget *widget = nullptr;
0083         KConfigDialogManager *manager = nullptr;
0084     };
0085     std::vector<WidgetManager> m_managerForPage;
0086 
0087     /**
0088      * The list of existing dialogs.
0089      */
0090     struct OpenDialogInfo {
0091         QString dialogName;
0092         KConfigDialog *dialog = nullptr;
0093     };
0094     static std::vector<OpenDialogInfo> s_openDialogs;
0095 };
0096 
0097 std::vector<KConfigDialogPrivate::OpenDialogInfo> KConfigDialogPrivate::s_openDialogs;
0098 
0099 KConfigDialog::KConfigDialog(QWidget *parent, const QString &name, KCoreConfigSkeleton *config)
0100     : KPageDialog(parent)
0101     , d(new KConfigDialogPrivate(name, config, this))
0102 {
0103 }
0104 
0105 KConfigDialog::~KConfigDialog()
0106 {
0107     auto &openDlgs = KConfigDialogPrivate::s_openDialogs;
0108     const QString currentObjectName = objectName();
0109     auto it = std::find_if(openDlgs.cbegin(), openDlgs.cend(), [=](const KConfigDialogPrivate::OpenDialogInfo &info) {
0110         return currentObjectName == info.dialogName;
0111     });
0112 
0113     if (it != openDlgs.cend()) {
0114         openDlgs.erase(it);
0115     }
0116 }
0117 
0118 KPageWidgetItem *KConfigDialog::addPage(QWidget *page, const QString &itemName, const QString &pixmapName, const QString &header, bool manage)
0119 {
0120     Q_ASSERT(page);
0121     if (!page) {
0122         return nullptr;
0123     }
0124 
0125     KPageWidgetItem *item = d->addPageInternal(page, itemName, pixmapName, header);
0126     if (manage) {
0127         d->manager->addWidget(page);
0128     }
0129 
0130     if (d->shown && manage) {
0131         // update the default button if the dialog is shown
0132         QPushButton *defaultButton = buttonBox()->button(QDialogButtonBox::RestoreDefaults);
0133         if (defaultButton) {
0134             bool is_default = defaultButton->isEnabled() && d->manager->isDefault();
0135             defaultButton->setEnabled(!is_default);
0136         }
0137     }
0138     return item;
0139 }
0140 
0141 KPageWidgetItem *KConfigDialog::addPage(QWidget *page, KCoreConfigSkeleton *config, const QString &itemName, const QString &pixmapName, const QString &header)
0142 {
0143     Q_ASSERT(page);
0144     if (!page) {
0145         return nullptr;
0146     }
0147 
0148     KPageWidgetItem *item = d->addPageInternal(page, itemName, pixmapName, header);
0149     auto *manager = new KConfigDialogManager(page, config);
0150     d->m_managerForPage.push_back({page, manager});
0151     d->setupManagerConnections(manager);
0152 
0153     if (d->shown) {
0154         // update the default button if the dialog is shown
0155         QPushButton *defaultButton = buttonBox()->button(QDialogButtonBox::RestoreDefaults);
0156         if (defaultButton) {
0157             const bool is_default = defaultButton->isEnabled() && manager->isDefault();
0158             defaultButton->setEnabled(!is_default);
0159         }
0160     }
0161     return item;
0162 }
0163 
0164 KPageWidgetItem *KConfigDialogPrivate::addPageInternal(QWidget *page, const QString &itemName, const QString &pixmapName, const QString &header)
0165 {
0166     QWidget *frame = new QWidget(q);
0167     QVBoxLayout *boxLayout = new QVBoxLayout(frame);
0168     boxLayout->setContentsMargins(0, 0, 0, 0);
0169     boxLayout->setContentsMargins(0, 0, 0, 0);
0170 
0171     QScrollArea *scroll = new QScrollArea(q);
0172     scroll->setFrameShape(QFrame::NoFrame);
0173     scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
0174     scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
0175     scroll->setWidget(page);
0176     scroll->setWidgetResizable(true);
0177     scroll->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
0178 
0179     if (page->minimumSizeHint().height() > scroll->sizeHint().height() - 2) {
0180         if (page->sizeHint().width() < scroll->sizeHint().width() + 2) {
0181             // QScrollArea is planning only a vertical scroll bar,
0182             // try to avoid the horizontal one by reserving space for the vertical one.
0183             // Currently KPageViewPrivate::_k_modelChanged() queries the minimumSizeHint().
0184             // We can only set the minimumSize(), so this approach relies on QStackedWidget size calculation.
0185             scroll->setMinimumWidth(scroll->sizeHint().width() + qBound(0, scroll->verticalScrollBar()->sizeHint().width(), 200) + 4);
0186         }
0187     }
0188 
0189     boxLayout->addWidget(scroll);
0190     KPageWidgetItem *item = new KPageWidgetItem(frame, itemName);
0191     item->setHeader(header);
0192     if (!pixmapName.isEmpty()) {
0193         item->setIcon(QIcon::fromTheme(pixmapName));
0194     }
0195 
0196     q->KPageDialog::addPage(item);
0197     return item;
0198 }
0199 
0200 void KConfigDialogPrivate::setupManagerConnections(KConfigDialogManager *manager)
0201 {
0202     q->connect(manager, qOverload<>(&KConfigDialogManager::settingsChanged), q, [this]() {
0203         settingsChangedSlot();
0204     });
0205     q->connect(manager, &KConfigDialogManager::widgetModified, q, [this]() {
0206         updateButtons();
0207     });
0208 
0209     QDialogButtonBox *buttonBox = q->buttonBox();
0210     q->connect(buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, manager, &KConfigDialogManager::updateSettings);
0211     q->connect(buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, manager, &KConfigDialogManager::updateSettings);
0212     q->connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, manager, &KConfigDialogManager::updateWidgets);
0213     q->connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, manager, &KConfigDialogManager::updateWidgetsDefault);
0214 }
0215 
0216 void KConfigDialogPrivate::updateApplyButton()
0217 {
0218     QPushButton *applyButton = q->buttonBox()->button(QDialogButtonBox::Apply);
0219     if (!applyButton) {
0220         return;
0221     }
0222 
0223     const bool hasManagerChanged = std::any_of(m_managerForPage.cbegin(), m_managerForPage.cend(), [](const WidgetManager &widgetManager) {
0224         return widgetManager.manager->hasChanged();
0225     });
0226 
0227     applyButton->setEnabled(manager->hasChanged() || q->hasChanged() || hasManagerChanged);
0228 }
0229 
0230 void KConfigDialogPrivate::updateDefaultsButton()
0231 {
0232     QPushButton *restoreDefaultsButton = q->buttonBox()->button(QDialogButtonBox::RestoreDefaults);
0233     if (!restoreDefaultsButton) {
0234         return;
0235     }
0236 
0237     const bool isManagerDefaulted = std::all_of(m_managerForPage.cbegin(), m_managerForPage.cend(), [](const WidgetManager &widgetManager) {
0238         return widgetManager.manager->isDefault();
0239     });
0240 
0241     restoreDefaultsButton->setDisabled(manager->isDefault() && q->isDefault() && isManagerDefaulted);
0242 }
0243 
0244 void KConfigDialog::onPageRemoved(KPageWidgetItem *item)
0245 {
0246     auto it = std::find_if(d->m_managerForPage.cbegin(), d->m_managerForPage.cend(), [item](const KConfigDialogPrivate::WidgetManager &wm) {
0247         return item->widget()->isAncestorOf(wm.widget);
0248     });
0249 
0250     if (it != d->m_managerForPage.cend()) { // There is a manager for this page, so remove it
0251         delete it->manager;
0252         d->m_managerForPage.erase(it);
0253         d->updateButtons();
0254     }
0255 }
0256 
0257 KConfigDialog *KConfigDialog::exists(const QString &name)
0258 {
0259     auto &openDlgs = KConfigDialogPrivate::s_openDialogs;
0260     auto it = std::find_if(openDlgs.cbegin(), openDlgs.cend(), [name](const KConfigDialogPrivate::OpenDialogInfo &info) {
0261         return name == info.dialogName;
0262     });
0263 
0264     return it != openDlgs.cend() ? it->dialog : nullptr;
0265 }
0266 
0267 bool KConfigDialog::showDialog(const QString &name)
0268 {
0269     KConfigDialog *dialog = exists(name);
0270     if (dialog) {
0271         dialog->show();
0272     }
0273     return (dialog != nullptr);
0274 }
0275 
0276 void KConfigDialogPrivate::updateButtons()
0277 {
0278     static bool only_once = false;
0279     if (only_once) {
0280         return;
0281     }
0282     only_once = true;
0283 
0284     updateApplyButton();
0285     updateDefaultsButton();
0286 
0287     Q_EMIT q->widgetModified();
0288     only_once = false;
0289 }
0290 
0291 void KConfigDialogPrivate::settingsChangedSlot()
0292 {
0293     // Update the buttons
0294     updateButtons();
0295     Q_EMIT q->settingsChanged(q->objectName());
0296 }
0297 
0298 void KConfigDialog::showEvent(QShowEvent *e)
0299 {
0300     if (!d->shown) {
0301         updateWidgets();
0302         d->manager->updateWidgets();
0303         for (auto [widget, manager] : d->m_managerForPage) {
0304             manager->updateWidgets();
0305         }
0306 
0307         d->updateApplyButton();
0308         d->updateDefaultsButton();
0309 
0310         d->shown = true;
0311     }
0312     KPageDialog::showEvent(e);
0313 }
0314 
0315 void KConfigDialog::updateSettings()
0316 {
0317 }
0318 
0319 void KConfigDialog::updateWidgets()
0320 {
0321 }
0322 
0323 void KConfigDialog::updateWidgetsDefault()
0324 {
0325 }
0326 
0327 bool KConfigDialog::hasChanged()
0328 {
0329     return false;
0330 }
0331 
0332 bool KConfigDialog::isDefault()
0333 {
0334     return true;
0335 }
0336 
0337 void KConfigDialog::updateButtons()
0338 {
0339     d->updateButtons();
0340 }
0341 
0342 void KConfigDialog::settingsChangedSlot()
0343 {
0344     d->settingsChangedSlot();
0345 }
0346 
0347 void KConfigDialog::setHelp(const QString &anchor, const QString &appname)
0348 {
0349     d->mAnchor = anchor;
0350     d->mHelpApp = appname;
0351 }
0352 
0353 void KConfigDialog::showHelp()
0354 {
0355     KHelpClient::invokeHelp(d->mAnchor, d->mHelpApp);
0356 }
0357 
0358 #include "moc_kconfigdialog.cpp"