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());
135 Q_EMIT isSaveNeededChanged();
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
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
205void KPluginModel::removePlugin(const KPluginMetaData &data)
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
215void KPluginModel::setConfig(const KConfigGroup &config)
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
224void KPluginModel::clear()
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
238void KPluginModel::save()
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
250KPluginMetaData KPluginModel::findConfigForPluginId(const QString &pluginId) const
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
260void KPluginModel::load()
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
270void KPluginModel::defaults()
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
289bool KPluginModel::isSaveNeeded()
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
QJsonObject rawData() const
bool value(const QString &key, bool defaultValue) const
QString iconName() const
QString name() const
static KPluginMetaData findPluginById(const QString &directory, const QString &pluginId, KPluginMetaDataOptions options={})
bool isEnabledByDefault() const
QString description() const
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
T value(qsizetype i) const const
int row() const const
Q_EMITQ_EMIT
QList< T > findChildren(Qt::FindChildOptions options) const const
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 Tue Mar 26 2024 11:17:56 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.