File indexing completed on 2024-04-28 07:41:27
0001 /* 0002 SPDX-FileCopyrightText: 2000 Matthias Elter <elter@kde.org> 0003 SPDX-FileCopyrightText: 2003 Daniel Molkentin <molkentin@kde.org> 0004 SPDX-FileCopyrightText: 2003, 2006 Matthias Kretz <kretz@kde.org> 0005 SPDX-FileCopyrightText: 2004 Frans Englich <frans.englich@telia.com> 0006 SPDX-FileCopyrightText: 2006 Tobias Koenig <tokoe@kde.org> 0007 SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de> 0008 0009 SPDX-License-Identifier: LGPL-2.0-or-later 0010 */ 0011 0012 #include "kcmultidialog.h" 0013 #include "kcmoduleloader.h" 0014 #include "kcmoduleqml_p.h" 0015 #include "kcmultidialog_p.h" 0016 #include <kcmutils_debug.h> 0017 0018 #include <QApplication> 0019 #include <QDesktopServices> 0020 #include <QJsonArray> 0021 #include <QLayout> 0022 #include <QProcess> 0023 #include <QPushButton> 0024 #include <QScreen> 0025 #include <QStandardPaths> 0026 #include <QStringList> 0027 #include <QStyle> 0028 #include <QTimer> 0029 #include <QUrl> 0030 0031 #include <KGuiItem> 0032 #include <KIconUtils> 0033 #include <KLocalizedString> 0034 #include <KMessageBox> 0035 #include <KPageWidgetModel> 0036 0037 bool KCMultiDialogPrivate::resolveChanges(KCModule *module) 0038 { 0039 if (!module || !module->needsSave()) { 0040 return true; 0041 } 0042 0043 // Let the user decide 0044 const int queryUser = KMessageBox::warningTwoActionsCancel(q, 0045 i18n("The settings of the current module have changed.\n" 0046 "Do you want to apply the changes or discard them?"), 0047 i18n("Apply Settings"), 0048 KStandardGuiItem::apply(), 0049 KStandardGuiItem::discard(), 0050 KStandardGuiItem::cancel()); 0051 0052 switch (queryUser) { 0053 case KMessageBox::PrimaryAction: 0054 return moduleSave(module); 0055 0056 case KMessageBox::SecondaryAction: 0057 module->load(); 0058 return true; 0059 0060 case KMessageBox::Cancel: 0061 return false; 0062 0063 default: 0064 Q_ASSERT(false); 0065 return false; 0066 } 0067 } 0068 0069 void KCMultiDialogPrivate::slotCurrentPageChanged(KPageWidgetItem *current, KPageWidgetItem *previous) 0070 { 0071 KCModule *previousModule = nullptr; 0072 for (int i = 0; i < modules.count(); ++i) { 0073 if (modules[i].item == previous) { 0074 previousModule = modules[i].kcm; 0075 } 0076 } 0077 0078 // Delete global margins and spacing, since we want the contents to 0079 // be able to touch the edges of the window 0080 q->layout()->setContentsMargins(0, 0, 0, 0); 0081 0082 const KPageWidget *pageWidget = q->pageWidget(); 0083 pageWidget->layout()->setSpacing(0); 0084 0085 // Then, we set the margins for the title header and the buttonBox footer 0086 const QStyle *style = q->style(); 0087 const QMargins layoutMargins = QMargins(style->pixelMetric(QStyle::PM_LayoutLeftMargin), 0088 style->pixelMetric(QStyle::PM_LayoutTopMargin), 0089 style->pixelMetric(QStyle::PM_LayoutRightMargin), 0090 style->pixelMetric(QStyle::PM_LayoutBottomMargin)); 0091 0092 if (pageWidget->pageHeader()) { 0093 pageWidget->pageHeader()->setContentsMargins(layoutMargins); 0094 } 0095 0096 q->buttonBox()->setContentsMargins(layoutMargins.left(), layoutMargins.top(), layoutMargins.right(), layoutMargins.bottom()); 0097 0098 q->blockSignals(true); 0099 q->setCurrentPage(previous); 0100 0101 if (resolveChanges(previousModule)) { 0102 q->setCurrentPage(current); 0103 } 0104 q->blockSignals(false); 0105 0106 // We need to get the state of the now active module 0107 clientChanged(); 0108 } 0109 0110 void KCMultiDialogPrivate::clientChanged() 0111 { 0112 // Get the current module 0113 KCModule *activeModule = nullptr; 0114 bool scheduleFirstShow = false; 0115 for (int i = 0; i < modules.count(); ++i) { 0116 if (modules[i].item == q->currentPage()) { 0117 activeModule = modules[i].kcm; 0118 scheduleFirstShow = activeModule && modules[i].firstShow; 0119 break; 0120 } 0121 } 0122 0123 // When we first show a module, we call the load method 0124 // Just in case we have multiple loadModule calls in a row, the current module could change 0125 // Meaning we wait for the next tick, check the active module and call load if needed 0126 if (scheduleFirstShow) { 0127 QTimer::singleShot(0, q, [this]() { 0128 for (int i = 0; i < modules.count(); ++i) { 0129 if (modules[i].firstShow && modules[i].kcm && modules[i].item == q->currentPage()) { 0130 modules[i].kcm->load(); 0131 modules[i].firstShow = false; 0132 } 0133 } 0134 }); 0135 } 0136 0137 const bool change = activeModule && activeModule->needsSave(); 0138 const bool defaulted = activeModule && activeModule->representsDefaults(); 0139 const auto buttons = activeModule ? activeModule->buttons() : KCModule::NoAdditionalButton; 0140 0141 QPushButton *resetButton = q->buttonBox()->button(QDialogButtonBox::Reset); 0142 if (resetButton) { 0143 resetButton->setVisible(buttons & KCModule::Apply); 0144 resetButton->setEnabled(change); 0145 } 0146 0147 QPushButton *applyButton = q->buttonBox()->button(QDialogButtonBox::Apply); 0148 if (applyButton) { 0149 applyButton->setVisible(buttons & KCModule::Apply); 0150 applyButton->setEnabled(change); 0151 } 0152 0153 QPushButton *cancelButton = q->buttonBox()->button(QDialogButtonBox::Cancel); 0154 if (cancelButton) { 0155 cancelButton->setVisible(buttons & KCModule::Apply); 0156 } 0157 0158 QPushButton *okButton = q->buttonBox()->button(QDialogButtonBox::Ok); 0159 if (okButton) { 0160 okButton->setVisible(buttons & KCModule::Apply); 0161 } 0162 0163 QPushButton *closeButton = q->buttonBox()->button(QDialogButtonBox::Close); 0164 if (closeButton) { 0165 closeButton->setHidden(buttons & KCModule::Apply); 0166 } 0167 0168 QPushButton *helpButton = q->buttonBox()->button(QDialogButtonBox::Help); 0169 if (helpButton) { 0170 helpButton->setVisible(buttons & KCModule::Help); 0171 } 0172 0173 QPushButton *defaultButton = q->buttonBox()->button(QDialogButtonBox::RestoreDefaults); 0174 if (defaultButton) { 0175 defaultButton->setVisible(buttons & KCModule::Default); 0176 defaultButton->setEnabled(!defaulted); 0177 } 0178 } 0179 0180 void KCMultiDialogPrivate::updateHeader(bool use, const QString &message) 0181 { 0182 KPageWidgetItem *item = q->currentPage(); 0183 const auto findIt = std::find_if(modules.cbegin(), modules.cend(), [item](const CreatedModule &module) { 0184 return module.item == item; 0185 }); 0186 Q_ASSERT(findIt != modules.cend()); 0187 0188 KCModule *kcm = findIt->kcm; 0189 const QString moduleName = kcm->metaData().name(); 0190 const QString icon = kcm->metaData().iconName(); 0191 0192 if (use) { 0193 item->setHeader(QStringLiteral("<b>") + moduleName + QStringLiteral("</b><br><i>") + message + QStringLiteral("</i>")); 0194 item->setIcon(KIconUtils::addOverlay(QIcon::fromTheme(icon), QIcon::fromTheme(QStringLiteral("dialog-warning")), Qt::BottomRightCorner)); 0195 } else { 0196 item->setHeader(moduleName); 0197 item->setIcon(QIcon::fromTheme(icon)); 0198 } 0199 } 0200 0201 void KCMultiDialogPrivate::init() 0202 { 0203 q->setFaceType(KPageDialog::Auto); 0204 q->setWindowTitle(i18n("Configure")); 0205 q->setModal(false); 0206 0207 QDialogButtonBox *buttonBox = new QDialogButtonBox(q); 0208 buttonBox->setStandardButtons(QDialogButtonBox::Help | QDialogButtonBox::RestoreDefaults | QDialogButtonBox::Cancel | QDialogButtonBox::Apply 0209 | QDialogButtonBox::Close | QDialogButtonBox::Ok | QDialogButtonBox::Reset); 0210 KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::ok()); 0211 KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel()); 0212 KGuiItem::assign(buttonBox->button(QDialogButtonBox::RestoreDefaults), KStandardGuiItem::defaults()); 0213 KGuiItem::assign(buttonBox->button(QDialogButtonBox::Apply), KStandardGuiItem::apply()); 0214 KGuiItem::assign(buttonBox->button(QDialogButtonBox::Close), KStandardGuiItem::close()); 0215 KGuiItem::assign(buttonBox->button(QDialogButtonBox::Reset), KStandardGuiItem::reset()); 0216 KGuiItem::assign(buttonBox->button(QDialogButtonBox::Help), KStandardGuiItem::help()); 0217 buttonBox->button(QDialogButtonBox::Close)->setVisible(false); 0218 buttonBox->button(QDialogButtonBox::Reset)->setEnabled(false); 0219 buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); 0220 0221 q->connect(buttonBox->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, q, &KCMultiDialog::slotApplyClicked); 0222 q->connect(buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, q, &KCMultiDialog::slotOkClicked); 0223 q->connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, q, &KCMultiDialog::slotDefaultClicked); 0224 q->connect(buttonBox->button(QDialogButtonBox::Help), &QAbstractButton::clicked, q, &KCMultiDialog::slotHelpClicked); 0225 q->connect(buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, q, &KCMultiDialog::slotUser1Clicked); 0226 0227 q->setButtonBox(buttonBox); 0228 q->connect(q, &KPageDialog::currentPageChanged, q, [this](KPageWidgetItem *current, KPageWidgetItem *before) { 0229 slotCurrentPageChanged(current, before); 0230 }); 0231 } 0232 0233 KCMultiDialog::KCMultiDialog(QWidget *parent) 0234 : KPageDialog(parent) 0235 , d(new KCMultiDialogPrivate(this)) 0236 { 0237 d->init(); 0238 } 0239 0240 KCMultiDialog::~KCMultiDialog() = default; 0241 0242 void KCMultiDialog::showEvent(QShowEvent *ev) 0243 { 0244 KPageDialog::showEvent(ev); 0245 adjustSize(); 0246 /** 0247 * adjustSize() relies on sizeHint but is limited to 2/3 of the desktop size 0248 * Workaround for https://bugreports.qt.io/browse/QTBUG-3459 0249 * 0250 * We adjust the size after passing the show event 0251 * because otherwise window pos is set to (0,0) 0252 */ 0253 0254 const QSize maxSize = screen()->availableGeometry().size(); 0255 resize(qMin(sizeHint().width(), maxSize.width()), qMin(sizeHint().height(), maxSize.height())); 0256 } 0257 0258 void KCMultiDialog::slotDefaultClicked() 0259 { 0260 const KPageWidgetItem *item = currentPage(); 0261 if (!item) { 0262 return; 0263 } 0264 0265 for (int i = 0; i < d->modules.count(); ++i) { 0266 if (d->modules[i].item == item) { 0267 d->modules[i].kcm->defaults(); 0268 d->clientChanged(); 0269 return; 0270 } 0271 } 0272 } 0273 0274 void KCMultiDialog::slotUser1Clicked() 0275 { 0276 const KPageWidgetItem *item = currentPage(); 0277 if (!item) { 0278 return; 0279 } 0280 0281 for (int i = 0; i < d->modules.count(); ++i) { 0282 if (d->modules[i].item == item) { 0283 d->modules[i].kcm->load(); 0284 d->clientChanged(); 0285 return; 0286 } 0287 } 0288 } 0289 0290 bool KCMultiDialogPrivate::moduleSave(KCModule *module) 0291 { 0292 if (!module) { 0293 return false; 0294 } 0295 0296 module->save(); 0297 return true; 0298 } 0299 0300 void KCMultiDialogPrivate::apply() 0301 { 0302 for (const CreatedModule &module : std::as_const(modules)) { 0303 KCModule *kcm = module.kcm; 0304 0305 if (kcm->needsSave()) { 0306 kcm->save(); 0307 } 0308 } 0309 0310 Q_EMIT q->configCommitted(); 0311 } 0312 0313 void KCMultiDialog::slotApplyClicked() 0314 { 0315 QPushButton *applyButton = buttonBox()->button(QDialogButtonBox::Apply); 0316 applyButton->setFocus(); 0317 0318 d->apply(); 0319 } 0320 0321 void KCMultiDialog::slotOkClicked() 0322 { 0323 QPushButton *okButton = buttonBox()->button(QDialogButtonBox::Ok); 0324 okButton->setFocus(); 0325 0326 d->apply(); 0327 accept(); 0328 } 0329 0330 void KCMultiDialog::slotHelpClicked() 0331 { 0332 const KPageWidgetItem *item = currentPage(); 0333 if (!item) { 0334 return; 0335 } 0336 0337 QString docPath; 0338 for (int i = 0; i < d->modules.count(); ++i) { 0339 if (d->modules[i].item == item) { 0340 if (docPath.isEmpty()) { 0341 docPath = d->modules[i].kcm->metaData().value(QStringLiteral("X-DocPath")); 0342 } 0343 break; 0344 } 0345 } 0346 0347 const QUrl docUrl = QUrl(QStringLiteral("help:/")).resolved(QUrl(docPath)); // same code as in KHelpClient::invokeHelp 0348 const QString docUrlScheme = docUrl.scheme(); 0349 const QString helpExec = QStandardPaths::findExecutable(QStringLiteral("khelpcenter")); 0350 const bool foundExec = !helpExec.isEmpty(); 0351 if (!foundExec) { 0352 qCDebug(KCMUTILS_LOG) << "Couldn't find khelpcenter executable in PATH."; 0353 } 0354 if (foundExec && (docUrlScheme == QLatin1String("man") || docUrlScheme == QLatin1String("info"))) { 0355 QProcess::startDetached(helpExec, QStringList() << docUrl.toString()); 0356 } else { 0357 QDesktopServices::openUrl(docUrl); 0358 } 0359 } 0360 0361 void KCMultiDialog::closeEvent(QCloseEvent *event) 0362 { 0363 KPageDialog::closeEvent(event); 0364 0365 for (auto &module : d->modules) { 0366 delete module.kcm; 0367 module.kcm = nullptr; 0368 } 0369 } 0370 0371 KPageWidgetItem *KCMultiDialog::addModule(const KPluginMetaData &metaData, const QVariantList &args) 0372 { 0373 // Create the scroller 0374 auto *moduleScroll = new UnboundScrollArea(this); 0375 // Prepare the scroll area 0376 moduleScroll->setWidgetResizable(true); 0377 moduleScroll->setFrameStyle(QFrame::NoFrame); 0378 moduleScroll->viewport()->setAutoFillBackground(false); 0379 0380 KCModule *kcm = KCModuleLoader::loadModule(metaData, moduleScroll, args); 0381 moduleScroll->setWidget(kcm->widget()); 0382 0383 KPageWidgetItem *item = new KPageWidgetItem(moduleScroll, metaData.name()); 0384 0385 KCMultiDialogPrivate::CreatedModule createdModule; 0386 createdModule.kcm = kcm; 0387 createdModule.item = item; 0388 d->modules.append(createdModule); 0389 0390 if (qobject_cast<KCModuleQml *>(kcm)) { 0391 item->setHeaderVisible(false); 0392 } 0393 0394 item->setHeader(metaData.name()); 0395 item->setIcon(QIcon::fromTheme(metaData.iconName())); 0396 const int weight = metaData.rawData().value(QStringLiteral("X-KDE-Weight")).toInt(); 0397 item->setProperty("_k_weight", weight); 0398 0399 bool updateCurrentPage = false; 0400 const KPageWidgetModel *model = qobject_cast<const KPageWidgetModel *>(pageWidget()->model()); 0401 Q_ASSERT(model); 0402 const int siblingCount = model->rowCount(); 0403 int row = 0; 0404 for (; row < siblingCount; ++row) { 0405 KPageWidgetItem *siblingItem = model->item(model->index(row, 0)); 0406 if (siblingItem->property("_k_weight").toInt() > weight) { 0407 // the item we found is heavier than the new module 0408 // qDebug() << "adding KCM " << item->name() << " before " << siblingItem->name(); 0409 insertPage(siblingItem, item); 0410 if (siblingItem == currentPage()) { 0411 updateCurrentPage = true; 0412 } 0413 0414 break; 0415 } 0416 } 0417 if (row == siblingCount) { 0418 // the new module is either the first or the heaviest item 0419 // qDebug() << "adding KCM " << item->name() << " at the top level"; 0420 addPage(item); 0421 } 0422 0423 connect(kcm, &KCModule::needsSaveChanged, this, [this]() { 0424 d->clientChanged(); 0425 }); 0426 0427 if (d->modules.count() == 1 || updateCurrentPage) { 0428 setCurrentPage(item); 0429 d->clientChanged(); 0430 } 0431 return item; 0432 } 0433 0434 void KCMultiDialog::clear() 0435 { 0436 for (int i = 0; i < d->modules.count(); ++i) { 0437 removePage(d->modules[i].item); 0438 } 0439 0440 d->modules.clear(); 0441 0442 d->clientChanged(); 0443 } 0444 0445 void KCMultiDialog::setDefaultsIndicatorsVisible(bool show) 0446 { 0447 for (const auto &module : std::as_const(d->modules)) { 0448 module.kcm->setDefaultsIndicatorsVisible(show); 0449 } 0450 } 0451 0452 #include "moc_kcmultidialog.cpp"