KCMUtils

kpluginmodel.cpp
1/*
2 SPDX-FileCopyrightText: 2021 Nicolas Fella <nicolas.fella@gmx.de>
3 SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "kpluginmodel.h"
9#include "kpluginproxymodel.h"
10
11#include <QPluginLoader>
12
13#include <KCategorizedSortFilterProxyModel>
14#include <KConfigGroup>
15
16#include <utility>
17
18#include "kcmutilscore_debug.h"
19
20class KPluginModelPrivate
21{
22public:
23 bool isDefaulted()
24 {
25 return std::all_of(m_plugins.cbegin(), m_plugins.cend(), [this](const KPluginMetaData &data) {
26 return isPluginEnabled(data) == data.isEnabledByDefault();
27 });
28 }
29 bool isPluginEnabled(const KPluginMetaData &plugin) const
30 {
31 auto pendingState = m_pendingStates.constFind(plugin.pluginId());
32 if (pendingState != m_pendingStates.constEnd()) {
33 return pendingState.value();
34 }
35
36 if (m_config.isValid()) {
37 return m_config.readEntry(plugin.pluginId() + QLatin1String("Enabled"), plugin.isEnabledByDefault());
38 }
39 return plugin.isEnabledByDefault();
40 }
41 KPluginMetaData findConfig(const KPluginMetaData &plugin) const
42 {
43 const QString metaDataKCM = plugin.value(QStringLiteral("X-KDE-ConfigModule"));
44
45 if (!metaDataKCM.isEmpty()) {
46 const QString absoluteKCMPath = QPluginLoader(metaDataKCM).fileName();
47 // If we have a static plugin the file does not exist on disk
48 // instead we query in the plugin namespace
49 if (absoluteKCMPath.isEmpty()) {
50 const int idx = metaDataKCM.lastIndexOf(QLatin1Char('/'));
51 const QString pluginNamespace = metaDataKCM.left(idx);
52 const QString pluginId = metaDataKCM.mid(idx + 1);
53 return KPluginMetaData::findPluginById(pluginNamespace, pluginId);
54 } else {
55 return KPluginMetaData(plugin.rawData(), absoluteKCMPath);
56 }
57 }
58
59 return KPluginMetaData();
60 }
61
62 QList<KPluginMetaData> m_plugins;
63 QSet<KPluginMetaData> m_unsortablePlugins;
65 KConfigGroup m_config;
66 QList<QString> m_orderedCategories; // Preserve order of categories in which they were added
67 QHash<QString, QString> m_categoryLabels;
68 QHash<QString, bool> m_pendingStates;
69};
70
71KPluginModel::KPluginModel(QObject *parent)
72 : QAbstractListModel(parent)
73 , d(new KPluginModelPrivate())
74{
75}
76
77KPluginModel::~KPluginModel() = default;
78
79QVariant KPluginModel::data(const QModelIndex &index, int role) const
80{
81 const KPluginMetaData &plugin = d->m_plugins[index.row()];
82
83 switch (role) {
84 case Roles::NameRole:
85 return plugin.name();
86 case Roles::DescriptionRole:
87 return plugin.description();
88 case Roles::IconRole:
89 return plugin.iconName();
90 case Roles::EnabledRole:
91 return d->isPluginEnabled(plugin);
92 case Roles::IsChangeableRole:
93 if (d->m_unsortablePlugins.contains(plugin)) {
94 return false;
95 }
96 if (d->m_config.isValid()) {
97 return !d->m_config.isEntryImmutable(plugin.pluginId() + QLatin1String("Enabled"));
98 }
99 return true;
100 case MetaDataRole:
101 return QVariant::fromValue(plugin);
104 return d->m_categoryLabels[plugin.pluginId()];
105 case ConfigRole:
106 return QVariant::fromValue(d->m_pluginKcms.value(plugin.pluginId()));
107 case IdRole:
108 return plugin.pluginId();
109 case EnabledByDefaultRole:
110 return plugin.isEnabledByDefault();
111 case SortableRole:
112 return !d->m_unsortablePlugins.contains(plugin);
113 }
114
115 return {};
116}
117
118bool KPluginModel::setData(const QModelIndex &index, const QVariant &value, int role)
119{
120 if (role == Roles::EnabledRole) {
121 const QString pluginId = d->m_plugins[index.row()].pluginId();
122
123 // If we already have a pending state and the user reverts it remove it from the map
124 auto pendingStateIt = d->m_pendingStates.constFind(pluginId);
125 if (pendingStateIt != d->m_pendingStates.constEnd()) {
126 if (pendingStateIt.value() != value.toBool()) {
127 d->m_pendingStates.erase(pendingStateIt);
128 }
129 } else {
130 d->m_pendingStates[pluginId] = value.toBool();
131 }
132
133 Q_EMIT dataChanged(index, index, {Roles::EnabledRole});
134 Q_EMIT defaulted(d->isDefaulted());
136
137 return true;
138 }
139
140 return false;
141}
142
143int KPluginModel::rowCount(const QModelIndex & /*parent*/) const
144{
145 return d->m_plugins.count();
146}
147
148QHash<int, QByteArray> KPluginModel::roleNames() const
149{
150 return {
152 {Roles::NameRole, "name"},
153 {Roles::IconRole, "icon"},
154 {Roles::EnabledRole, "enabled"},
155 {Roles::DescriptionRole, "description"},
156 {Roles::IsChangeableRole, "changable"},
157 {Roles::EnabledByDefaultRole, "enabledByDefault"},
158 {Roles::MetaDataRole, "metaData"},
159 {Roles::ConfigRole, "config"},
160 };
161};
162
163void KPluginModel::addUnsortablePlugins(const QList<KPluginMetaData> &newPlugins, const QString &categoryLabel)
164{
165 d->m_unsortablePlugins.unite(QSet(newPlugins.begin(), newPlugins.end()));
166 addPlugins(newPlugins, categoryLabel);
167}
168
169bool KPluginModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild)
170{
171 if (sourceParent.isValid() || destinationParent.isValid()) {
172 return false;
173 }
174 if ((sourceRow + count - 1) >= d->m_plugins.size()) {
175 return false;
176 }
177
178 const bool isMoveDown = destinationChild > sourceRow;
179 if (!beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, isMoveDown ? destinationChild + 1 : destinationChild)) {
180 return false;
181 }
182 for (int i = 0; i < count; i++) {
183 d->m_plugins.insert(destinationChild, d->m_plugins.takeAt(sourceRow + i));
184 }
185 endMoveRows();
186 return true;
187}
188
189void KPluginModel::addPlugins(const QList<KPluginMetaData> &newPlugins, const QString &categoryLabel)
190{
191 beginInsertRows({}, d->m_plugins.size(), d->m_plugins.size() + newPlugins.size() - 1);
192 d->m_orderedCategories << categoryLabel;
193 d->m_plugins.append(newPlugins);
194
195 for (const KPluginMetaData &plugin : newPlugins) {
196 d->m_categoryLabels[plugin.pluginId()] = categoryLabel;
197 d->m_pluginKcms.insert(plugin.pluginId(), d->findConfig(plugin));
198 }
199
201
202 Q_EMIT defaulted(d->isDefaulted());
203}
204
206{
207 if (const int index = d->m_plugins.indexOf(data); index != -1) {
209 d->m_plugins.removeAt(index);
210 d->m_unsortablePlugins.remove(data);
212 }
213}
214
216{
217 d->m_config = config;
218
219 if (!d->m_plugins.isEmpty()) {
220 Q_EMIT dataChanged(index(0, 0), index(d->m_plugins.size() - 1, 0), {Roles::EnabledRole, Roles::IsChangeableRole});
221 }
222}
223
225{
226 if (d->m_plugins.isEmpty()) {
227 return;
228 }
229 beginRemoveRows({}, 0, d->m_plugins.size() - 1);
230 d->m_plugins.clear();
231 d->m_pluginKcms.clear();
232 // In case of the "Reset"-button of the KCMs load is called again with the goal
233 // of discarding all local changes. Consequently, the pending states have to be cleared here.
234 d->m_pendingStates.clear();
236}
237
239{
240 if (d->m_config.isValid()) {
241 for (auto it = d->m_pendingStates.cbegin(); it != d->m_pendingStates.cend(); ++it) {
242 d->m_config.writeEntry(it.key() + QLatin1String("Enabled"), it.value());
243 }
244
245 d->m_config.sync();
246 }
247 d->m_pendingStates.clear();
248}
249
251{
252 for (const KPluginMetaData &plugin : std::as_const(d->m_plugins)) {
253 if (plugin.pluginId() == pluginId) {
254 return d->findConfig(plugin);
255 }
256 }
257 return KPluginMetaData();
258}
259
261{
262 if (!d->m_config.isValid()) {
263 return;
264 }
265
266 d->m_pendingStates.clear();
267 Q_EMIT dataChanged(index(0, 0), index(d->m_plugins.size() - 1, 0), {Roles::EnabledRole});
268}
269
271{
272 for (int pluginIndex = 0, count = d->m_plugins.count(); pluginIndex < count; ++pluginIndex) {
273 const KPluginMetaData plugin = d->m_plugins.at(pluginIndex);
274 const bool changed = d->isPluginEnabled(plugin) != plugin.isEnabledByDefault();
275
276 if (changed) {
277 // If the entry was marked as changed, but we flip the value it is unchanged again
278 if (d->m_pendingStates.remove(plugin.pluginId()) == 0) {
279 // If the entry was not changed before, we have to mark it as changed
280 d->m_pendingStates.insert(plugin.pluginId(), plugin.isEnabledByDefault());
281 }
282 Q_EMIT dataChanged(index(pluginIndex, 0), index(pluginIndex, 0), {Roles::EnabledRole});
283 }
284 }
285
286 Q_EMIT defaulted(true);
287}
288
290{
291 return !d->m_pendingStates.isEmpty();
292}
293
294QStringList KPluginModel::getOrderedCategoryLabels()
295{
296 return d->m_orderedCategories;
297}
298
299#include "moc_kpluginmodel.cpp"
bool isValid() const
QString readEntry(const char *key, const char *aDefault=nullptr) const
QString pluginId() const
bool value(QStringView key, bool defaultValue) const
QJsonObject rawData() const
QString iconName() const
QString name() const
static KPluginMetaData findPluginById(const QString &directory, const QString &pluginId, KPluginMetaDataOptions options={})
bool isEnabledByDefault() const
QString description() const
KPluginMetaData findConfigForPluginId(const QString &pluginId) const
Returns the KPluginMetaData object of the plugin's config module.
void addUnsortablePlugins(const QList< KPluginMetaData > &plugins, const QString &categoryLabel)
Add plugins that should not be sorted automatically based on their name This is useful in case your a...
void clear()
Removes all plugins.
void load()
Load the enabled state of the plugins from the config group set by setConfig.
Q_SIGNAL void isSaveNeededChanged()
Emitted when isSaveNeeded is changed.
void addPlugins(const QList< KPluginMetaData > &plugins, const QString &categoryLabel)
Append plugins to the model.
void defaults()
Reset the enabled state of the plugins to its defaults.
Q_SIGNAL void defaulted(bool isDefaulted)
Emitted when the enabled state matches the default changes.
void removePlugin(const KPluginMetaData &data)
void setConfig(const KConfigGroup &config)
Set the KConfigGroup that is used to load/save the enabled state.
bool isSaveNeeded()
Whether or not there are unsaved changes to the enabled state of the plugins.
void save()
Save the enabled state of the plugins to the config group set by setConfig.
void beginInsertRows(const QModelIndex &parent, int first, int last)
bool beginMoveRows(const QModelIndex &sourceParent, int sourceFirst, int sourceLast, const QModelIndex &destinationParent, int destinationChild)
void beginRemoveRows(const QModelIndex &parent, int first, int last)
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles)
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
const_iterator constEnd() const const
const_iterator constFind(const Key &key) const const
iterator begin()
const_iterator cbegin() const const
const_iterator cend() const const
iterator end()
qsizetype size() const const
bool isValid() const const
int row() const const
Q_EMITQ_EMIT
bool isEmpty() const const
qsizetype lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const const
QString left(qsizetype n) const const
QString mid(qsizetype position, qsizetype n) const const
QVariant fromValue(T &&value)
bool toBool() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:02:19 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.