Warning, file /frameworks/kcmutils/src/ksettings/dialog.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 This file is part of the KDE project 0003 SPDX-FileCopyrightText: 2003 Matthias Kretz <kretz@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-only 0006 */ 0007 0008 #include "dialog.h" 0009 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 85) 0010 #include "dialog_p.h" 0011 0012 #include <kcmutils_debug.h> 0013 0014 #include <KConfig> 0015 #include <KLocalizedString> 0016 #include <KPluginMetaData> 0017 #include <KServiceGroup> 0018 #include <KServiceTypeTrader> 0019 #include <KSharedConfig> 0020 0021 #include <QCheckBox> 0022 #include <QCoreApplication> 0023 #include <QDialogButtonBox> 0024 #include <QDir> 0025 #include <QLabel> 0026 #include <QPushButton> 0027 #include <QStack> 0028 #include <QVBoxLayout> 0029 0030 uint qHash(const KCModuleInfo &info) 0031 { 0032 return qHash(info.fileName()); 0033 } 0034 0035 namespace KSettings 0036 { 0037 Dialog::Dialog(QWidget *parent) 0038 : Dialog(QStringList{}, parent) 0039 { 0040 } 0041 0042 Dialog::Dialog(const QStringList &components, QWidget *parent) 0043 : KCMultiDialog(*new DialogPrivate(this), new KPageWidget, parent) 0044 { 0045 Q_D(Dialog); 0046 d->components = components; 0047 } 0048 0049 Dialog::~Dialog() 0050 { 0051 } 0052 0053 void Dialog::setAllowComponentSelection(bool selection) 0054 { 0055 d_func()->staticlistview = !selection; 0056 } 0057 0058 bool Dialog::allowComponentSelection() const 0059 { 0060 return !d_func()->staticlistview; 0061 } 0062 0063 void Dialog::setKCMArguments(const QStringList &arguments) 0064 { 0065 Q_D(Dialog); 0066 d->arguments = arguments; 0067 } 0068 0069 void Dialog::setComponentBlacklist(const QStringList &blacklist) 0070 { 0071 Q_D(Dialog); 0072 d->componentBlacklist = blacklist; 0073 } 0074 0075 void Dialog::addPluginInfos(const KPluginInfo::List &plugininfos) 0076 { 0077 Q_D(Dialog); 0078 for (KPluginInfo::List::ConstIterator it = plugininfos.begin(); it != plugininfos.end(); ++it) { 0079 d->registeredComponents.append(it->pluginName()); 0080 const auto lst = it->kcmServices(); 0081 if (lst.isEmpty()) { 0082 // this plugin has no kcm services, still we want to show the disable/enable stuff 0083 // so add a dummy kcm 0084 d->kcmInfos << KCModuleInfo(*it); 0085 continue; 0086 } 0087 for (const KService::Ptr &service : lst) { 0088 d->kcmInfos << KCModuleInfo(service); 0089 } 0090 } 0091 0092 // The plugin, when disabled, disables all the KCMs described by kcmServices(). 0093 // - Normally they are grouped using a .setdlg file so that the group parent can get a 0094 // checkbox to enable/disable the plugin. 0095 // - If the plugin does not belong to a group and has only one KCM the checkbox can be 0096 // used with this KCM. 0097 // - If the plugin belongs to a group but there are other modules in the group that do not 0098 // belong to this plugin we give a kError and show no checkbox 0099 // - If the plugin belongs to multiple groups we give a kError and show no checkbox 0100 d->plugininfos = plugininfos; 0101 } 0102 0103 KPluginInfo::List Dialog::pluginInfos() const 0104 { 0105 return d_func()->plugininfos; 0106 } 0107 0108 void Dialog::showEvent(QShowEvent *) 0109 { 0110 Q_D(Dialog); 0111 if (d->firstshow) { 0112 setUpdatesEnabled(false); 0113 d->kcmInfos += d->instanceServices(); 0114 if (!d->components.isEmpty()) { 0115 d->kcmInfos += d->parentComponentsServices(d->components); 0116 } 0117 d->createDialogFromServices(); 0118 d->firstshow = false; 0119 setUpdatesEnabled(true); 0120 } 0121 0122 for (const QString &compName : std::as_const(d->components)) { 0123 KSharedConfig::Ptr config = KSharedConfig::openConfig(compName + QLatin1String("rc")); 0124 config->sync(); 0125 } 0126 } 0127 0128 DialogPrivate::DialogPrivate(Dialog *parent) 0129 : KCMultiDialogPrivate(parent) 0130 , staticlistview(true) 0131 , firstshow(true) 0132 , pluginStateDirty(0) 0133 { 0134 } 0135 0136 QSet<KCModuleInfo> DialogPrivate::instanceServices() 0137 { 0138 // qDebug() ; 0139 QString componentName = QCoreApplication::instance()->applicationName(); 0140 registeredComponents.append(componentName); 0141 // qDebug() << "calling KServiceGroup::childGroup( " << componentName << " )"; 0142 KServiceGroup::Ptr service = KServiceGroup::childGroup(componentName); 0143 0144 QSet<KCModuleInfo> ret; 0145 0146 if (service && service->isValid()) { 0147 // qDebug() << "call was successful"; 0148 const KServiceGroup::List list = service->entries(); 0149 for (KServiceGroup::List::ConstIterator it = list.begin(); it != list.end(); ++it) { 0150 KSycocaEntry::Ptr p = (*it); 0151 if (p->isType(KST_KService)) { 0152 // qDebug() << "found service"; 0153 const KService::Ptr service(static_cast<KService *>(p.data())); 0154 ret << KCModuleInfo(service); 0155 } else { 0156 qCWarning(KCMUTILS_LOG) << "KServiceGroup::childGroup returned" 0157 " something else than a KService"; 0158 } 0159 } 0160 } 0161 0162 return ret; 0163 } 0164 0165 QSet<KCModuleInfo> DialogPrivate::parentComponentsServices(const QStringList &kcdparents) 0166 { 0167 registeredComponents += kcdparents; 0168 QString constraint = kcdparents.join(QLatin1String("' in [X-KDE-ParentComponents]) or ('")); 0169 constraint = QStringLiteral("('") + constraint + QStringLiteral("' in [X-KDE-ParentComponents])"); 0170 0171 // qDebug() << "constraint = " << constraint; 0172 const QList<KService::Ptr> services = KServiceTypeTrader::self()->query(QStringLiteral("KCModule"), constraint); 0173 QSet<KCModuleInfo> ret; 0174 ret.reserve(services.count()); 0175 for (const KService::Ptr &service : services) { 0176 ret << KCModuleInfo(service); 0177 } 0178 return ret; 0179 } 0180 0181 bool DialogPrivate::isPluginForKCMEnabled(const KCModuleInfo *moduleinfo, KPluginInfo &pinfo) const 0182 { 0183 // if the user of this class requested to hide disabled modules 0184 // we check whether it should be enabled or not 0185 bool enabled = true; 0186 // qDebug() << "check whether the '" << moduleinfo->moduleName() << "' KCM should be shown"; 0187 // for all parent components 0188 const QStringList parentComponents = moduleinfo->property(QStringLiteral("X-KDE-ParentComponents")).toStringList(); 0189 for (QStringList::ConstIterator pcit = parentComponents.begin(); pcit != parentComponents.end(); ++pcit) { 0190 // if the parentComponent is not registered ignore it 0191 if (!registeredComponents.contains(*pcit)) { 0192 continue; 0193 } 0194 0195 // we check if the parent component is a plugin 0196 // if not the KCModule must be enabled 0197 enabled = true; 0198 if (pinfo.pluginName() == *pcit) { 0199 // it is a plugin: we check whether the plugin is enabled 0200 pinfo.load(); 0201 enabled = pinfo.isPluginEnabled(); 0202 // qDebug() << "parent " << *pcit << " is " << (enabled ? "enabled" : "disabled"); 0203 } 0204 // if it is enabled we're done for this KCModuleInfo 0205 if (enabled) { 0206 return true; 0207 } 0208 } 0209 return enabled; 0210 } 0211 0212 bool DialogPrivate::isPluginImmutable(const KPluginInfo &pinfo) const 0213 { 0214 return pinfo.property(QStringLiteral("X-KDE-PluginInfo-Immutable")).toBool(); 0215 } 0216 0217 KPageWidgetItem *DialogPrivate::createPageItem(KPageWidgetItem *parentItem, const QString &name, const QString &comment, const QString &iconName, int weight) 0218 { 0219 Q_Q(Dialog); 0220 QWidget *page = new QWidget(q); 0221 0222 QCheckBox *checkBox = new QCheckBox(i18n("Enable component"), page); 0223 QLabel *iconLabel = new QLabel(page); 0224 QLabel *commentLabel = new QLabel(comment, page); 0225 commentLabel->setTextFormat(Qt::RichText); 0226 QVBoxLayout *layout = new QVBoxLayout(page); 0227 layout->addWidget(checkBox); 0228 layout->addWidget(iconLabel); 0229 layout->addWidget(commentLabel); 0230 layout->addStretch(); 0231 0232 KPageWidgetItem *item = new KPageWidgetItem(page, name); 0233 item->setIcon(QIcon::fromTheme(iconName)); 0234 iconLabel->setPixmap(item->icon().pixmap(128, 128)); 0235 item->setProperty("_k_weight", weight); 0236 checkBoxForItem.insert(item, checkBox); 0237 0238 const KPageWidgetModel *model = qobject_cast<const KPageWidgetModel *>(q->pageWidget()->model()); 0239 Q_ASSERT(model); 0240 0241 if (parentItem) { 0242 const QModelIndex parentIndex = model->index(parentItem); 0243 const int siblingCount = model->rowCount(parentIndex); 0244 int row = 0; 0245 for (; row < siblingCount; ++row) { 0246 KPageWidgetItem *siblingItem = model->item(model->index(row, 0, parentIndex)); 0247 if (siblingItem->property("_k_weight").toInt() > weight) { 0248 // the item we found is heavier than the new module 0249 q->insertPage(siblingItem, item); 0250 break; 0251 } 0252 } 0253 if (row == siblingCount) { 0254 // the new module is either the first or the heaviest item 0255 q->addSubPage(parentItem, item); 0256 } 0257 } else { 0258 const int siblingCount = model->rowCount(); 0259 int row = 0; 0260 for (; row < siblingCount; ++row) { 0261 KPageWidgetItem *siblingItem = model->item(model->index(row, 0)); 0262 if (siblingItem->property("_k_weight").toInt() > weight) { 0263 // the item we found is heavier than the new module 0264 q->insertPage(siblingItem, item); 0265 break; 0266 } 0267 } 0268 if (row == siblingCount) { 0269 // the new module is either the first or the heaviest item 0270 q->addPage(item); 0271 } 0272 } 0273 0274 return (item); 0275 } 0276 0277 void DialogPrivate::parseGroupFile(const QString &filename) 0278 { 0279 KConfig file(filename, KConfig::SimpleConfig); 0280 const QStringList groups = file.groupList(); 0281 for (const QString &group : groups) { 0282 if (group.isEmpty()) { 0283 continue; 0284 } 0285 KConfigGroup conf(&file, group); 0286 0287 const QString parentId = conf.readEntry("Parent"); 0288 KPageWidgetItem *parentItem = pageItemForGroupId.value(parentId); 0289 KPageWidgetItem *item = 0290 createPageItem(parentItem, conf.readEntry("Name"), conf.readEntry("Comment"), conf.readEntry("Icon"), conf.readEntry("Weight", 100)); 0291 pageItemForGroupId.insert(group, item); 0292 } 0293 } 0294 0295 void DialogPrivate::createDialogFromServices() 0296 { 0297 Q_Q(Dialog); 0298 // read .setdlg files (eg: share/kapp/kapp.setdlg) 0299 const QString setdlgpath = QStandardPaths::locate(QStandardPaths::AppDataLocation /*includes appname, too*/, 0300 QCoreApplication::instance()->applicationName() + QStringLiteral(".setdlg")); 0301 if (!setdlgpath.isEmpty()) { 0302 parseGroupFile(setdlgpath); 0303 } 0304 0305 const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("ksettingsdialog"), QStandardPaths::LocateDirectory); 0306 QMap<QString /*fileName*/, QString /*fullPath*/> fileMap; 0307 for (const QString &dir : dirs) { 0308 const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.setdlg")); 0309 for (const QString &file : fileNames) { 0310 if (!fileMap.contains(file)) { 0311 fileMap.insert(file, dir + QLatin1Char('/') + file); 0312 } 0313 } 0314 } 0315 for (auto it = fileMap.constBegin(); it != fileMap.constEnd(); ++it) { 0316 parseGroupFile(it.value()); 0317 } 0318 0319 // qDebug() << kcmInfos.count(); 0320 for (const KCModuleInfo &info : std::as_const(kcmInfos)) { 0321 const QStringList parentComponents = info.property(QStringLiteral("X-KDE-ParentComponents")).toStringList(); 0322 bool blacklisted = false; 0323 for (const QString &parentComponent : parentComponents) { 0324 if (componentBlacklist.contains(parentComponent)) { 0325 blacklisted = true; 0326 break; 0327 } 0328 } 0329 if (blacklisted) { 0330 continue; 0331 } 0332 const QString parentId = info.property(QStringLiteral("X-KDE-CfgDlgHierarchy")).toString(); 0333 KPageWidgetItem *parent = pageItemForGroupId.value(parentId); 0334 if (!parent) { 0335 // dummy kcm 0336 bool foundPlugin = false; 0337 for (const KPluginInfo &pinfo : std::as_const(plugininfos)) { 0338 if (pinfo.libraryPath() == info.library()) { 0339 if (pinfo.kcmServices().isEmpty()) { 0340 // FIXME get weight from service or plugin info 0341 const int weight = 1000; 0342 KPageWidgetItem *item = createPageItem(nullptr, pinfo.name(), pinfo.comment(), pinfo.icon(), weight); 0343 connectItemCheckBox(item, pinfo, pinfo.isPluginEnabled()); 0344 foundPlugin = true; 0345 break; 0346 } 0347 } 0348 } 0349 if (foundPlugin) { 0350 continue; 0351 } 0352 } 0353 KPageWidgetItem *item = q->addModule(info, parent, arguments); 0354 // qDebug() << "added KCM '" << info.moduleName() << "'"; 0355 for (KPluginInfo pinfo : std::as_const(plugininfos)) { 0356 // qDebug() << pinfo.pluginName(); 0357 if (pinfo.kcmServices().contains(info.service())) { 0358 const bool isEnabled = isPluginForKCMEnabled(&info, pinfo); 0359 // qDebug() << "correct KPluginInfo for this KCM"; 0360 // this KCM belongs to a plugin 0361 if (parent && pinfo.kcmServices().count() >= 1) { 0362 item->setEnabled(isEnabled); 0363 const KPluginInfo &plugin = pluginForItem.value(parent); 0364 if (plugin.isValid()) { 0365 if (plugin != pinfo) { 0366 qCCritical(KCMUTILS_LOG) << "A group contains more than one plugin: '" << plugin.pluginName() << "' and '" << pinfo.pluginName() 0367 << "'. Now it won't be possible to enable/disable the plugin."; 0368 parent->setCheckable(false); 0369 q->disconnect(parent, SIGNAL(toggled(bool)), q, SLOT(_k_updateEnabledState(bool))); 0370 } 0371 // else everything is fine 0372 } else { 0373 connectItemCheckBox(parent, pinfo, isEnabled); 0374 } 0375 } else { 0376 pluginForItem.insert(item, pinfo); 0377 item->setCheckable(!isPluginImmutable(pinfo)); 0378 item->setChecked(isEnabled); 0379 q->connect(item, SIGNAL(toggled(bool)), q, SLOT(_k_updateEnabledState(bool))); 0380 } 0381 break; 0382 } 0383 } 0384 } 0385 // now that the KCMs are in, check for empty groups and remove them again 0386 { 0387 const KPageWidgetModel *model = qobject_cast<const KPageWidgetModel *>(q->pageWidget()->model()); 0388 const QHash<QString, KPageWidgetItem *>::ConstIterator end = pageItemForGroupId.constEnd(); 0389 QHash<QString, KPageWidgetItem *>::ConstIterator it = pageItemForGroupId.constBegin(); 0390 for (; it != end; ++it) { 0391 const QModelIndex index = model->index(it.value()); 0392 KPluginInfo pinfo; 0393 for (const KPluginInfo &p : std::as_const(plugininfos)) { 0394 if (p.name() == it.key()) { 0395 pinfo = p; 0396 break; 0397 } 0398 } 0399 bool allowEmpty = false; 0400 if (pinfo.isValid()) { 0401 allowEmpty = pinfo.property(QStringLiteral("X-KDE-PluginInfo-AllowEmptySettings")).toBool(); 0402 } 0403 0404 if (model->rowCount(index) == 0) { 0405 // no children, and it's not allowed => remove this item 0406 if (!allowEmpty) { 0407 q->removePage(it.value()); 0408 } else { 0409 connectItemCheckBox(it.value(), pinfo, pinfo.isPluginEnabled()); 0410 } 0411 } 0412 } 0413 } 0414 0415 // TODO: Don't show the reset button until the issue with the 0416 // KPluginSelector::load() method is solved. 0417 // Problem: 0418 // KCMultiDialog::show() call KCModule::load() to reset all KCMs 0419 // (KPluginSelector::load() resets all plugin selections and all plugin 0420 // KCMs). 0421 // The reset button calls KCModule::load(), too but in this case we want the 0422 // KPluginSelector to only reset the current visible plugin KCM and not 0423 // touch the plugin selections. 0424 // I have no idea how to check that in KPluginSelector::load()... 0425 // q->showButton(KDialog::User1, true); 0426 0427 QObject::connect(q, qOverload<>(&KCMultiDialog::configCommitted), q, [this]() { 0428 updateConfiguration(); 0429 }); 0430 0431 QObject::connect(q, qOverload<const QByteArray &>(&KCMultiDialog::configCommitted), q, [](const QByteArray &componentName) { 0432 KSharedConfig::Ptr config = KSharedConfig::openConfig(QString::fromLatin1(componentName) + QLatin1String("rc")); 0433 config->reparseConfiguration(); 0434 }); 0435 } 0436 0437 void DialogPrivate::connectItemCheckBox(KPageWidgetItem *item, const KPluginInfo &pinfo, bool isEnabled) 0438 { 0439 Q_Q(Dialog); 0440 QCheckBox *checkBox = checkBoxForItem.value(item); 0441 Q_ASSERT(checkBox); 0442 pluginForItem.insert(item, pinfo); 0443 item->setCheckable(!isPluginImmutable(pinfo)); 0444 item->setChecked(isEnabled); 0445 checkBox->setVisible(!isPluginImmutable(pinfo)); 0446 checkBox->setChecked(isEnabled); 0447 q->connect(item, SIGNAL(toggled(bool)), q, SLOT(_k_updateEnabledState(bool))); 0448 q->connect(item, &KPageWidgetItem::toggled, checkBox, &QAbstractButton::setChecked); 0449 q->connect(checkBox, &QAbstractButton::clicked, item, &KPageWidgetItem::setChecked); 0450 } 0451 0452 void DialogPrivate::updateConfiguration() 0453 { 0454 Q_Q(Dialog); 0455 const QHash<KPageWidgetItem *, KPluginInfo>::Iterator endIt = pluginForItem.end(); 0456 QHash<KPageWidgetItem *, KPluginInfo>::Iterator it = pluginForItem.begin(); 0457 for (; it != endIt; ++it) { 0458 KPageWidgetItem *item = it.key(); 0459 KPluginInfo pinfo = it.value(); 0460 pinfo.setPluginEnabled(item->isChecked()); 0461 pinfo.save(); 0462 } 0463 0464 if (pluginStateDirty > 0) { 0465 Q_EMIT q->pluginSelectionChanged(); 0466 pluginStateDirty = 0; 0467 } 0468 } 0469 0470 void DialogPrivate::_k_clientChanged() 0471 { 0472 if (pluginStateDirty > 0) { 0473 Q_Q(Dialog); 0474 q->buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(true); 0475 } else { 0476 KCMultiDialogPrivate::_k_clientChanged(); 0477 } 0478 } 0479 0480 void DialogPrivate::_k_updateEnabledState(bool enabled) 0481 { 0482 Q_Q(Dialog); 0483 KPageWidgetItem *item = qobject_cast<KPageWidgetItem *>(q->sender()); 0484 if (!item) { 0485 qCWarning(KCMUTILS_LOG) << "invalid sender"; 0486 return; 0487 } 0488 0489 // iterate over all child KPageWidgetItem objects and check whether they need to be enabled/disabled 0490 const KPageWidgetModel *model = qobject_cast<const KPageWidgetModel *>(q->pageWidget()->model()); 0491 Q_ASSERT(model); 0492 QModelIndex index = model->index(item); 0493 if (!index.isValid()) { 0494 qCWarning(KCMUTILS_LOG) << "could not find item in model"; 0495 return; 0496 } 0497 0498 const KPluginInfo &pinfo = pluginForItem.value(item); 0499 if (!pinfo.isValid()) { 0500 qCWarning(KCMUTILS_LOG) << "could not find KPluginInfo in item"; 0501 return; 0502 } 0503 if (pinfo.isPluginEnabled() != enabled) { 0504 ++pluginStateDirty; 0505 } else { 0506 --pluginStateDirty; 0507 } 0508 if (pluginStateDirty < 2) { 0509 _k_clientChanged(); 0510 } 0511 0512 // qDebug() ; 0513 0514 QModelIndex firstborn = model->index(0, 0, index); 0515 if (firstborn.isValid()) { 0516 // qDebug() << "iterating over children"; 0517 // change all children 0518 index = firstborn; 0519 QStack<QModelIndex> stack; 0520 while (index.isValid()) { 0521 // qDebug() << index; 0522 KPageWidgetItem *item = model->item(index); 0523 // qDebug() << "item->setEnabled(" << enabled << ')'; 0524 item->setEnabled(enabled); 0525 firstborn = model->index(0, 0, index); 0526 if (firstborn.isValid()) { 0527 stack.push(index); 0528 index = firstborn; 0529 } else { 0530 index = index.sibling(index.row() + 1, 0); 0531 while (!index.isValid() && !stack.isEmpty()) { 0532 index = stack.pop(); 0533 index = index.sibling(index.row() + 1, 0); 0534 } 0535 } 0536 } 0537 } 0538 } 0539 0540 } // namespace 0541 0542 #include "moc_dialog.cpp" 0543 #endif