File indexing completed on 2024-04-28 04:37:15

0001 /*
0002     SPDX-FileCopyrightText: 2014 Alex Richardson <arichardson.kde@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "configdialog.h"
0008 
0009 #include "debug.h"
0010 
0011 #include <QCloseEvent>
0012 #include <QPushButton>
0013 #include <QPointer>
0014 
0015 #include <KMessageBox>
0016 #include <KMessageBox_KDevCompat>
0017 #include <KLocalizedString>
0018 
0019 #include <iplugin.h>
0020 #include <configpage.h>
0021 #include <icore.h>
0022 #include <iplugincontroller.h>
0023 
0024 using namespace KDevelop;
0025 
0026 //FIXME: unit test this code!
0027 
0028 ConfigDialog::ConfigDialog(QWidget* parent)
0029     : KPageDialog(parent)
0030 {
0031     setWindowTitle(i18nc("@title:window", "Configure"));
0032     setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults);
0033     button(QDialogButtonBox::Apply)->setEnabled(false);
0034     setObjectName(QStringLiteral("configdialog"));
0035 
0036     auto onApplyClicked = [this] {
0037         auto page = qobject_cast<ConfigPage*>(currentPage()->widget());
0038         Q_ASSERT(page);
0039         applyChanges(page);
0040     };
0041 
0042     connect(button(QDialogButtonBox::Apply), &QPushButton::clicked, onApplyClicked);
0043     connect(button(QDialogButtonBox::Ok), &QPushButton::clicked, [this, onApplyClicked] {
0044         if (m_currentPageHasChanges) {
0045             onApplyClicked();
0046         }
0047     });
0048     connect(button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, [this]() {
0049         auto page = qobject_cast<ConfigPage*>(currentPage()->widget());
0050         Q_ASSERT(page);
0051         page->defaults();
0052     });
0053 
0054     connect(this, &KPageDialog::currentPageChanged, this, &ConfigDialog::checkForUnsavedChanges);
0055     // make sure we don't keep any entries for unloaded plugins
0056     connect(ICore::self()->pluginController(), &IPluginController::unloadingPlugin,
0057             this, &ConfigDialog::removePagesForPlugin);
0058 }
0059 
0060 
0061 KPageWidgetItem* ConfigDialog::itemForPage(ConfigPage* page) const
0062 {
0063     for (auto& item : m_pages) {
0064         if (item->widget() == page) {
0065             return item;
0066         }
0067     }
0068     return nullptr;
0069 }
0070 
0071 int ConfigDialog::checkForUnsavedChanges(KPageWidgetItem* current, KPageWidgetItem* before)
0072 {
0073     Q_UNUSED(current);
0074 
0075     if (!m_currentPageHasChanges) {
0076         return KMessageBox::PrimaryAction;
0077     }
0078 
0079     // before must be non-null, because if we change from nothing to a new page m_currentPageHasChanges must also be false!
0080     Q_ASSERT(before);
0081     auto oldPage = qobject_cast<ConfigPage*>(before->widget());
0082     Q_ASSERT(oldPage);
0083     auto dialogResult =
0084         KMessageBox::warningTwoActionsCancel(this,
0085                                              i18n("The settings of the current module have changed.\n"
0086                                                   "Do you want to apply the changes or discard them?"),
0087                                              i18nc("@title:window", "Apply Settings"), KStandardGuiItem::apply(),
0088                                              KStandardGuiItem::discard(), KStandardGuiItem::cancel());
0089     if (dialogResult == KMessageBox::SecondaryAction) {
0090         oldPage->reset();
0091         m_currentPageHasChanges = false;
0092         button(QDialogButtonBox::Apply)->setEnabled(false);
0093     } else if (dialogResult == KMessageBox::PrimaryAction) {
0094         applyChanges(oldPage);
0095     } else if (dialogResult == KMessageBox::Cancel) {
0096         // restore old state
0097         QSignalBlocker block(this); // prevent recursion
0098         setCurrentPage(before);
0099     }
0100     return dialogResult;
0101 }
0102 
0103 void ConfigDialog::closeEvent(QCloseEvent* event)
0104 {
0105     if (checkForUnsavedChanges(currentPage(), currentPage()) == KMessageBox::Cancel) {
0106         // if the user clicked cancel he wants to continue editing the current page -> don't close
0107         event->ignore();
0108     } else {
0109         event->accept();
0110     }
0111 }
0112 
0113 void ConfigDialog::removeConfigPage(ConfigPage* page)
0114 {
0115     auto item = itemForPage(page);
0116     Q_ASSERT(item);
0117     removePage(item);
0118     m_pages.removeAll(QPointer<KPageWidgetItem>(item));
0119     // also remove all items that were deleted because a parent KPageWidgetItem was removed
0120     m_pages.removeAll(QPointer<KPageWidgetItem>());
0121 }
0122 
0123 void ConfigDialog::removePagesForPlugin(IPlugin* plugin)
0124 {
0125     Q_ASSERT(plugin);
0126     const auto oldPages = m_pages;
0127     for (auto&& item : oldPages) {
0128         if (!item) {
0129             continue;
0130         }
0131         auto page = qobject_cast<ConfigPage*>(item->widget());
0132         if (page && page->plugin() == plugin) {
0133             removePage(item); // this also deletes the config page -> QPointer is set to null
0134         }
0135     };
0136     // also remove all items that were deleted because a parent KPageWidgetItem was removed
0137     m_pages.removeAll(QPointer<KPageWidgetItem>());
0138 }
0139 
0140 void ConfigDialog::appendConfigPage(ConfigPage* page)
0141 {
0142     addConfigPageInternal(addPage(page, page->name()), page);
0143 }
0144 
0145 void ConfigDialog::insertConfigPage(ConfigPage* before, ConfigPage* page)
0146 {
0147     Q_ASSERT(before);
0148     auto beforeItem = itemForPage(before);
0149     Q_ASSERT(beforeItem);
0150     addConfigPageInternal(insertPage(beforeItem, page, page->name()), page);
0151 }
0152 
0153 void ConfigDialog::appendSubConfigPage(ConfigPage* parentPage, ConfigPage* page)
0154 {
0155     auto item = itemForPage(parentPage);
0156     Q_ASSERT(item);
0157     addConfigPageInternal(addSubPage(item, page, page->name()), page);
0158 }
0159 
0160 void ConfigDialog::addConfigPageInternal(KPageWidgetItem* item, ConfigPage* page)
0161 {
0162     item->setHeader(page->fullName());
0163     item->setIcon(page->icon());
0164     page->initConfigManager();
0165     // connect to changed() *after* calling initConfigManager(), which may call reset()
0166     connect(page, &ConfigPage::changed, this, &ConfigDialog::onPageChanged);
0167     m_pages.append(item);
0168     for (int i = 0; i < page->childPages(); ++i) {
0169         auto child = page->childPage(i);
0170         appendSubConfigPage(page, child);
0171     }
0172 }
0173 
0174 void ConfigDialog::onPageChanged()
0175 {
0176     QObject* from = sender();
0177     if (from && from != currentPage()->widget()) {
0178         qCWarning(SHELL) << "Settings in config page" << from << "changed, while" << currentPage()->widget() << "is currently selected. This case is not implemented yet.";
0179         return;
0180         // TODO: add a QHash<ConfigPage*, bool> as a member to make sure the apply button is always correct
0181 
0182         // TODO: when pressing okay show confirm dialog if other pages have changed or just silently apply every page? "Items on other pages have changed, do you wish to review those changes? + list with changed pages."
0183     }
0184     if (!m_currentlyApplyingChanges) {
0185         // e.g. PluginPreferences emits changed() from its apply method, better fix this here than having to
0186         // ensure that no plugin emits changed() from apply()
0187         // together with KPageDialog emitting currentPageChanged("Plugins", nullptr) this could cause a crash
0188         // when we dereference before
0189         m_currentPageHasChanges = true;
0190         button(QDialogButtonBox::Apply)->setEnabled(true);
0191     }
0192 }
0193 
0194 
0195 void ConfigDialog::applyChanges(ConfigPage* page)
0196 {
0197     Q_ASSERT(m_currentPageHasChanges);
0198     // must set this to false before calling apply, otherwise we get the confirmation dialog
0199     // whenever we enable/disable plugins.
0200     // This is because KPageWidget then emits currentPageChanged("Plugins", nullptr), which seems like a bug to me,
0201     // it should rather emit currentPageChanged("Plugins", "Plugins") or even better nothing at all, since the current
0202     // page did not actually change!
0203     // TODO: fix KPageWidget
0204     m_currentPageHasChanges = false;
0205     m_currentlyApplyingChanges = true;
0206     page->apply();
0207     m_currentlyApplyingChanges = false;
0208     Q_ASSERT(!m_currentPageHasChanges);
0209     button(QDialogButtonBox::Apply)->setEnabled(false);
0210     emit configSaved(page);
0211 }
0212 
0213 #include "moc_configdialog.cpp"