Verified Commit 71d96dff authored by Alexander Lohnau's avatar Alexander Lohnau
Browse files

Port settings to KPluginMetaData

- Use KPluginLoader to load the KCMs
- Clean up unneeded parts of KCMUtils classes, like rootOnlyMMessage
- Sort plugins beforehand, this saves us the trouble of inserting them
at their correct position
- Drop code paths for KPluginInfo and KCModuleInfo
- Drop support for setdlg files, instead use the KPLuginMetaData from
the Kontact plugin to store the information.
- Port away from KPluginInfo::kcmServices, see https://phabricator.kde.org/T13555#259170
parent eb9ec4b1
......@@ -12,7 +12,6 @@
#include <KPluginMetaData>
class QComboBox;
class QCheckBox;
namespace Kontact
{
class KcmKontact : public KCModule
......
......@@ -7,6 +7,7 @@
#include "kontactconfiguredialog.h"
#include "kontact_debug.h"
#include <KConfig>
#include <KConfigGroup>
#include <KSharedConfig>
#include <QDBusInterface>
#include <QDBusReply>
......
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2003 Matthias Kretz <kretz@kde.org>
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-only
*/
......@@ -15,7 +16,6 @@
#include <KServiceTypeTrader>
#include <KSharedConfig>
#include <QCheckBox>
#include <QCoreApplication>
#include <QDialogButtonBox>
#include <QDir>
......@@ -24,10 +24,7 @@
#include <QStack>
#include <QVBoxLayout>
uint qHash(const KCModuleInfo &info)
{
return qHash(info.fileName());
}
#include "kontact_debug.h"
namespace KSettings
{
......@@ -47,59 +44,10 @@ Dialog::~Dialog()
{
}
void Dialog::setAllowComponentSelection(bool selection)
{
d_func()->staticlistview = !selection;
}
bool Dialog::allowComponentSelection() const
{
return !d_func()->staticlistview;
}
void Dialog::setKCMArguments(const QStringList &arguments)
{
Q_D(Dialog);
d->arguments = arguments;
}
void Dialog::setComponentBlacklist(const QStringList &blacklist)
void Dialog::addPluginComponent(const KPluginMetaData &parentPluginMetaData, const QVector<KPluginMetaData> &pluginMetaData)
{
Q_D(Dialog);
d->componentBlacklist = blacklist;
}
void Dialog::addPluginInfos(const KPluginInfo::List &plugininfos)
{
Q_D(Dialog);
for (KPluginInfo::List::ConstIterator it = plugininfos.begin(); it != plugininfos.end(); ++it) {
d->registeredComponents.append(it->pluginName());
const auto lst = it->kcmServices();
if (lst.isEmpty()) {
// this plugin has no kcm services, still we want to show the disable/enable stuff
// so add a dummy kcm
d->kcmInfos << KCModuleInfo(*it);
continue;
}
for (const KService::Ptr &service : lst) {
d->kcmInfos << KCModuleInfo(service);
}
}
// The plugin, when disabled, disables all the KCMs described by kcmServices().
// - Normally they are grouped using a .setdlg file so that the group parent can get a
// checkbox to enable/disable the plugin.
// - If the plugin does not belong to a group and has only one KCM the checkbox can be
// used with this KCM.
// - If the plugin belongs to a group but there are other modules in the group that do not
// belong to this plugin we give a kError and show no checkbox
// - If the plugin belongs to multiple groups we give a kError and show no checkbox
d->plugininfos = plugininfos;
}
KPluginInfo::List Dialog::pluginInfos() const
{
return d_func()->plugininfos;
d->componentsMetaData.append({parentPluginMetaData, pluginMetaData});
}
void Dialog::showEvent(QShowEvent *)
......@@ -107,10 +55,6 @@ void Dialog::showEvent(QShowEvent *)
Q_D(Dialog);
if (d->firstshow) {
setUpdatesEnabled(false);
d->kcmInfos += d->instanceServices();
if (!d->components.isEmpty()) {
d->kcmInfos += d->parentComponentsServices(d->components);
}
d->createDialogFromServices();
d->firstshow = false;
setUpdatesEnabled(true);
......@@ -124,103 +68,20 @@ void Dialog::showEvent(QShowEvent *)
DialogPrivate::DialogPrivate(Dialog *parent)
: KCMultiDialogPrivate(parent)
, staticlistview(true)
, firstshow(true)
, pluginStateDirty(0)
{
}
QSet<KCModuleInfo> DialogPrivate::instanceServices()
{
// qDebug() ;
QString componentName = QCoreApplication::instance()->applicationName();
registeredComponents.append(componentName);
// qDebug() << "calling KServiceGroup::childGroup( " << componentName << " )";
KServiceGroup::Ptr service = KServiceGroup::childGroup(componentName);
QSet<KCModuleInfo> ret;
if (service && service->isValid()) {
// qDebug() << "call was successful";
const KServiceGroup::List list = service->entries();
for (KServiceGroup::List::ConstIterator it = list.begin(); it != list.end(); ++it) {
KSycocaEntry::Ptr p = (*it);
if (p->isType(KST_KService)) {
// qDebug() << "found service";
const KService::Ptr service(static_cast<KService *>(p.data()));
ret << KCModuleInfo(service);
} else
qWarning() << "KServiceGroup::childGroup returned"
" something else than a KService";
}
}
return ret;
}
QSet<KCModuleInfo> DialogPrivate::parentComponentsServices(const QStringList &kcdparents)
{
registeredComponents += kcdparents;
QString constraint = kcdparents.join(QLatin1String("' in [X-KDE-ParentComponents]) or ('"));
constraint = QStringLiteral("('") + constraint + QStringLiteral("' in [X-KDE-ParentComponents])");
// qDebug() << "constraint = " << constraint;
const QList<KService::Ptr> services = KServiceTypeTrader::self()->query(QStringLiteral("KCModule"), constraint);
QSet<KCModuleInfo> ret;
ret.reserve(services.count());
for (const KService::Ptr &service : services) {
ret << KCModuleInfo(service);
}
return ret;
}
bool DialogPrivate::isPluginForKCMEnabled(const KCModuleInfo *moduleinfo, KPluginInfo &pinfo) const
{
// if the user of this class requested to hide disabled modules
// we check whether it should be enabled or not
bool enabled = true;
// qDebug() << "check whether the '" << moduleinfo->moduleName() << "' KCM should be shown";
// for all parent components
const QStringList parentComponents = moduleinfo->property(QStringLiteral("X-KDE-ParentComponents")).toStringList();
for (QStringList::ConstIterator pcit = parentComponents.begin(); pcit != parentComponents.end(); ++pcit) {
// if the parentComponent is not registered ignore it
if (!registeredComponents.contains(*pcit)) {
continue;
}
// we check if the parent component is a plugin
// if not the KCModule must be enabled
enabled = true;
if (pinfo.pluginName() == *pcit) {
// it is a plugin: we check whether the plugin is enabled
pinfo.load();
enabled = pinfo.isPluginEnabled();
// qDebug() << "parent " << *pcit << " is " << (enabled ? "enabled" : "disabled");
}
// if it is enabled we're done for this KCModuleInfo
if (enabled) {
return true;
}
}
return enabled;
}
bool DialogPrivate::isPluginImmutable(const KPluginInfo &pinfo) const
{
return pinfo.property(QStringLiteral("X-KDE-PluginInfo-Immutable")).toBool();
}
KPageWidgetItem *DialogPrivate::createPageItem(KPageWidgetItem *parentItem, const QString &name, const QString &comment, const QString &iconName, int weight)
KPageWidgetItem *DialogPrivate::createPageItem(KPageWidgetItem *parentItem, const QString &name, const QString &comment, const QString &iconName)
{
Q_Q(Dialog);
QWidget *page = new QWidget(q);
QCheckBox *checkBox = new QCheckBox(i18n("Enable component"), page);
QLabel *iconLabel = new QLabel(page);
QLabel *commentLabel = new QLabel(comment, page);
commentLabel->setTextFormat(Qt::RichText);
QVBoxLayout *layout = new QVBoxLayout(page);
layout->addWidget(checkBox);
layout->addWidget(iconLabel);
layout->addWidget(commentLabel);
layout->addStretch();
......@@ -228,198 +89,34 @@ KPageWidgetItem *DialogPrivate::createPageItem(KPageWidgetItem *parentItem, cons
KPageWidgetItem *item = new KPageWidgetItem(page, name);
item->setIcon(QIcon::fromTheme(iconName));
iconLabel->setPixmap(item->icon().pixmap(128, 128));
item->setProperty("_k_weight", weight);
checkBoxForItem.insert(item, checkBox);
const KPageWidgetModel *model = qobject_cast<const KPageWidgetModel *>(q->pageWidget()->model());
Q_ASSERT(model);
if (parentItem) {
const QModelIndex parentIndex = model->index(parentItem);
const int siblingCount = model->rowCount(parentIndex);
int row = 0;
for (; row < siblingCount; ++row) {
KPageWidgetItem *siblingItem = model->item(model->index(row, 0, parentIndex));
if (siblingItem->property("_k_weight").toInt() > weight) {
// the item we found is heavier than the new module
q->insertPage(siblingItem, item);
break;
}
}
if (row == siblingCount) {
// the new module is either the first or the heaviest item
q->addSubPage(parentItem, item);
}
q->addSubPage(parentItem, item);
} else {
const int siblingCount = model->rowCount();
int row = 0;
for (; row < siblingCount; ++row) {
KPageWidgetItem *siblingItem = model->item(model->index(row, 0));
if (siblingItem->property("_k_weight").toInt() > weight) {
// the item we found is heavier than the new module
q->insertPage(siblingItem, item);
break;
}
}
if (row == siblingCount) {
// the new module is either the first or the heaviest item
q->addPage(item);
}
q->addPage(item);
}
return (item);
}
void DialogPrivate::parseGroupFile(const QString &filename)
{
KConfig file(filename, KConfig::SimpleConfig);
const QStringList groups = file.groupList();
for (const QString &group : groups) {
if (group.isEmpty()) {
continue;
}
KConfigGroup conf(&file, group);
const QString parentId = conf.readEntry("Parent");
KPageWidgetItem *parentItem = pageItemForGroupId.value(parentId);
KPageWidgetItem *item =
createPageItem(parentItem, conf.readEntry("Name"), conf.readEntry("Comment"), conf.readEntry("Icon"), conf.readEntry("Weight", 100));
pageItemForGroupId.insert(group, item);
}
}
void DialogPrivate::createDialogFromServices()
{
Q_Q(Dialog);
// read .setdlg files (eg: share/kapp/kapp.setdlg)
const QString setdlgpath = QStandardPaths::locate(QStandardPaths::AppDataLocation /*includes appname, too*/,
QCoreApplication::instance()->applicationName() + QStringLiteral(".setdlg"));
if (!setdlgpath.isEmpty()) {
parseGroupFile(setdlgpath);
}
const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("ksettingsdialog"), QStandardPaths::LocateDirectory);
QMap<QString /*fileName*/, QString /*fullPath*/> fileMap;
for (const QString &dir : dirs) {
const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.setdlg"));
for (const QString &file : fileNames) {
if (!fileMap.contains(file)) {
fileMap.insert(file, dir + QLatin1Char('/') + file);
}
}
}
for (auto it = fileMap.constBegin(); it != fileMap.constEnd(); ++it) {
parseGroupFile(it.value());
}
// qDebug() << kcmInfos.count();
for (const KCModuleInfo &info : qAsConst(kcmInfos)) {
const QStringList parentComponents = info.property(QStringLiteral("X-KDE-ParentComponents")).toStringList();
bool blacklisted = false;
for (const QString &parentComponent : parentComponents) {
if (componentBlacklist.contains(parentComponent)) {
blacklisted = true;
break;
}
}
if (blacklisted) {
continue;
}
const QString parentId = info.property(QStringLiteral("X-KDE-CfgDlgHierarchy")).toString();
KPageWidgetItem *parent = pageItemForGroupId.value(parentId);
if (!parent) {
// dummy kcm
bool foundPlugin = false;
for (const KPluginInfo &pinfo : qAsConst(plugininfos)) {
if (pinfo.libraryPath() == info.library()) {
if (pinfo.kcmServices().isEmpty()) {
// FIXME get weight from service or plugin info
const int weight = 1000;
KPageWidgetItem *item = createPageItem(nullptr, pinfo.name(), pinfo.comment(), pinfo.icon(), weight);
connectItemCheckBox(item, pinfo, pinfo.isPluginEnabled());
foundPlugin = true;
break;
}
}
}
if (foundPlugin) {
continue;
}
}
KPageWidgetItem *item = q->addModule(info, parent, arguments);
// qDebug() << "added KCM '" << info.moduleName() << "'";
for (KPluginInfo pinfo : qAsConst(plugininfos)) {
// qDebug() << pinfo.pluginName();
if (pinfo.kcmServices().contains(info.service())) {
const bool isEnabled = isPluginForKCMEnabled(&info, pinfo);
// qDebug() << "correct KPluginInfo for this KCM";
// this KCM belongs to a plugin
if (parent && pinfo.kcmServices().count() >= 1) {
item->setEnabled(isEnabled);
const KPluginInfo &plugin = pluginForItem.value(parent);
if (plugin.isValid()) {
if (plugin != pinfo) {
qCritical() << "A group contains more than one plugin: '" << plugin.pluginName() << "' and '" << pinfo.pluginName()
<< "'. Now it won't be possible to enable/disable the plugin.";
parent->setCheckable(false);
q->disconnect(parent, SIGNAL(toggled(bool)), q, SLOT(_k_updateEnabledState(bool)));
}
// else everything is fine
} else {
connectItemCheckBox(parent, pinfo, isEnabled);
}
} else {
pluginForItem.insert(item, pinfo);
item->setCheckable(!isPluginImmutable(pinfo));
item->setChecked(isEnabled);
q->connect(item, SIGNAL(toggled(bool)), q, SLOT(_k_updateEnabledState(bool)));
}
break;
}
}
}
// now that the KCMs are in, check for empty groups and remove them again
{
const KPageWidgetModel *model = qobject_cast<const KPageWidgetModel *>(q->pageWidget()->model());
const QHash<QString, KPageWidgetItem *>::ConstIterator end = pageItemForGroupId.constEnd();
QHash<QString, KPageWidgetItem *>::ConstIterator it = pageItemForGroupId.constBegin();
for (; it != end; ++it) {
const QModelIndex index = model->index(it.value());
KPluginInfo pinfo;
for (const KPluginInfo &p : qAsConst(plugininfos)) {
if (p.name() == it.key()) {
pinfo = p;
break;
}
}
bool allowEmpty = false;
if (pinfo.isValid()) {
allowEmpty = pinfo.property(QStringLiteral("X-KDE-PluginInfo-AllowEmptySettings")).toBool();
}
if (model->rowCount(index) == 0) {
// no children, and it's not allowed => remove this item
if (!allowEmpty) {
q->removePage(it.value());
} else {
connectItemCheckBox(it.value(), pinfo, pinfo.isPluginEnabled());
}
}
for (const auto &pair : qAsConst(componentsMetaData)) {
const KPluginMetaData &parentComponentMetaData = pair.first;
const QVector<KPluginMetaData> &kcmsMetaData = pair.second;
KPageWidgetItem *parentItem =
createPageItem(nullptr, parentComponentMetaData.name(), parentComponentMetaData.description(), parentComponentMetaData.iconName());
// connectItemCheckBox(item, pinfo, pinfo.isPluginEnabled());
for (const KPluginMetaData &metaData : kcmsMetaData) {
q->addModule(metaData, parentItem);
}
}
// TODO: Don't show the reset button until the issue with the
// KPluginSelector::load() method is solved.
// Problem:
// KCMultiDialog::show() call KCModule::load() to reset all KCMs
// (KPluginSelector::load() resets all plugin selections and all plugin
// KCMs).
// The reset button calls KCModule::load(), too but in this case we want the
// KPluginSelector to only reset the current visible plugin KCM and not
// touch the plugin selections.
// I have no idea how to check that in KPluginSelector::load()...
// q->showButton(KDialog::User1, true);
QObject::connect(q, QOverload<>::of(&KCMultiDialog::configCommitted), q, [this]() {
updateConfiguration();
});
......@@ -430,21 +127,6 @@ void DialogPrivate::createDialogFromServices()
});
}
void DialogPrivate::connectItemCheckBox(KPageWidgetItem *item, const KPluginInfo &pinfo, bool isEnabled)
{
Q_Q(Dialog);
QCheckBox *checkBox = checkBoxForItem.value(item);
Q_ASSERT(checkBox);
pluginForItem.insert(item, pinfo);
item->setCheckable(!isPluginImmutable(pinfo));
item->setChecked(isEnabled);
checkBox->setVisible(!isPluginImmutable(pinfo));
checkBox->setChecked(isEnabled);
q->connect(item, SIGNAL(toggled(bool)), q, SLOT(_k_updateEnabledState(bool)));
q->connect(item, &KPageWidgetItem::toggled, checkBox, &QAbstractButton::setChecked);
q->connect(checkBox, &QAbstractButton::clicked, item, &KPageWidgetItem::setChecked);
}
void DialogPrivate::updateConfiguration()
{
Q_Q(Dialog);
......@@ -478,7 +160,7 @@ void DialogPrivate::_k_updateEnabledState(bool enabled)
Q_Q(Dialog);
KPageWidgetItem *item = qobject_cast<KPageWidgetItem *>(q->sender());
if (!item) {
qWarning() << "invalid sender";
qCWarning(KONTACT_LOG) << "invalid sender";
return;
}
......@@ -487,13 +169,13 @@ void DialogPrivate::_k_updateEnabledState(bool enabled)
Q_ASSERT(model);
QModelIndex index = model->index(item);
if (!index.isValid()) {
qWarning() << "could not find item in model";
qCWarning(KONTACT_LOG) << "could not find item in model";
return;
}
const KPluginInfo &pinfo = pluginForItem.value(item);
if (!pinfo.isValid()) {
qWarning() << "could not find KPluginInfo in item";
qCWarning(KONTACT_LOG) << "could not find KPluginInfo in item";
return;
}
if (pinfo.isPluginEnabled() != enabled) {
......@@ -505,18 +187,13 @@ void DialogPrivate::_k_updateEnabledState(bool enabled)
_k_clientChanged();
}
// qDebug() ;
QModelIndex firstborn = model->index(0, 0, index);
if (firstborn.isValid()) {
// qDebug() << "iterating over children";
// change all children
index = firstborn;
QStack<QModelIndex> stack;
while (index.isValid()) {
// qDebug() << index;
KPageWidgetItem *item = model->item(index);
// qDebug() << "item->setEnabled(" << enabled << ')';
item->setEnabled(enabled);
firstborn = model->index(0, 0, index);
if (firstborn.isValid()) {
......
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2003 Matthias Kretz <kretz@kde.org>
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-only
*/
......@@ -8,15 +9,12 @@
#ifndef KSETTINGS_DIALOG_H
#define KSETTINGS_DIALOG_H
#include <kcmultidialog.h>
#include <kcmutils_export.h>
#include "./kcmultidialog.h"
#include <KPluginInfo>
#include <KService>
template<class T>
class QList;
class KCModuleInfo;
namespace KSettings
{
......@@ -67,7 +65,7 @@ class DialogPrivate;
*
* @author Matthias Kretz <kretz@kde.org>
*/
class KCMUTILS_EXPORT Dialog : public KCMultiDialog
class Dialog : public KCMultiDialog
{
friend class PageNode;
Q_DECLARE_PRIVATE(Dialog)
......@@ -102,56 +100,9 @@ public:
~Dialog() override;
/**
* If you use a Configurable dialog you need to pass KPluginInfo
* objects that the dialog should configure.
* bla bla bla
*/
void addPluginInfos(const QList<KPluginInfo> &plugininfos);
/**
* Sets the argument list that is given to all the KControlModule's when
* they are created.
* Use this if you have KControlModule's that need special arguments to
* work
*
* Note that this function only works before showing the
* KSettings::Dialog for the first time.
* @param arguments The list of arguments passed to each KCM
*/
void setKCMArguments(const QStringList &arguments);
/**
* Set the blacklisted component list. Any KCM that lists one
* of the components in the given blacklist is not loaded even if it
* would fit otherwise. This is a way to explicitly prevent loading of
* certain KControlModules.
*
* Note that this function only works before showing the
* KSettings::Dialog for the first time.
* @param blacklist the list of components that prevent a KCM from being
* loaded
*/
void setComponentBlacklist(const QStringList &blacklist);
/**
* Tells the dialog whether the entries in the listview are all static
* or whether it should add checkboxes to select which parts
* of the optional functionality should be active or not.
*
* Note that this function only works before showing the dialog for the first time.
*
* Defaults to \p false.
*
* @param allowSelection \p true The user can select what functionality he wants.
* @param allowSelection \p false While running no entries are added or deleted
*/
void setAllowComponentSelection(bool allowSelection);
bool allowComponentSelection() const;
/**
* Returns a list of all KPluginInfo objects the dialog uses.
*/
QList<KPluginInfo> pluginInfos() const;
void addPluginComponent(const KPluginMetaData &parentPluginMetaData, const QVector<KPluginMetaData> &pluginMetaData);
protected:
/**
......
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Matthias Kretz <kretz@kde.org>
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-only
*/
......@@ -20,8 +21,6 @@
#include <KPluginInfo>
#include <KService>
class QCheckBox;
namespace KSettings
{
class DialogPrivate : public KCMultiDialogPrivate
......@@ -31,18 +30,16 @@ class DialogPrivate : public KCMultiDialogPrivate
protected:
DialogPrivate(Dialog *parent);
QHash<QString, KPageWidgetItem *> pageItemForGroupId;
QHash<KPageWidgetItem *, KPluginInfo> pluginForItem;