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"