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