KCMUtils

kpluginmodel.cpp
1 /*
2  SPDX-FileCopyrightText: 2021 Nicolas Fella <[email protected]>
3  SPDX-FileCopyrightText: 2021 Alexander Lohnau <[email protected]>
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 #include <KServiceTypeTrader>
16 
17 #include <utility>
18 
19 #include "kcmutilscore_debug.h"
20 
21 class KPluginModelPrivate
22 {
23 public:
24  bool isDefaulted()
25  {
26  return std::all_of(m_plugins.cbegin(), m_plugins.cend(), [this](const KPluginMetaData &data) {
27  return isPluginEnabled(data) == data.isEnabledByDefault();
28  });
29  }
30  bool isPluginEnabled(const KPluginMetaData &plugin) const
31  {
32  auto pendingState = m_pendingStates.constFind(plugin.pluginId());
33  if (pendingState != m_pendingStates.constEnd()) {
34  return pendingState.value();
35  }
36 
37  if (m_config.isValid()) {
38  return m_config.readEntry(plugin.pluginId() + QLatin1String("Enabled"), plugin.isEnabledByDefault());
39  }
40  return plugin.isEnabledByDefault();
41  }
42  KPluginMetaData findConfig(const KPluginMetaData &plugin) const
43  {
44  const QString metaDataKCM = plugin.value(QStringLiteral("X-KDE-ConfigModule"));
45 
46  if (!metaDataKCM.isEmpty()) {
47  const QString absoluteKCMPath = QPluginLoader(metaDataKCM).fileName();
48  // If we have a static plugin the file does not exist on disk
49  // instead we query in the plugin namespace
50  if (absoluteKCMPath.isEmpty()) {
51  const int idx = metaDataKCM.lastIndexOf(QLatin1Char('/'));
52  const QString pluginNamespace = metaDataKCM.left(idx);
53  const QString pluginId = metaDataKCM.mid(idx + 1);
54  return KPluginMetaData::findPluginById(pluginNamespace, pluginId);
55  } else {
56  return KPluginMetaData(plugin.rawData(), absoluteKCMPath);
57  }
58  }
59 
60 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 89)
61  QT_WARNING_PUSH
62  QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations")
63  QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations")
64 
65  const QString constraint = QLatin1Char('\'') + plugin.pluginId() + QLatin1String("' in [X-KDE-ParentComponents]");
66  const auto services = KServiceTypeTrader::self()->query(QStringLiteral("KCModule"), constraint);
67  if (!services.isEmpty()) {
68  const KService::Ptr service = services.constFirst();
69  qCWarning(KCMUTILS_CORE_LOG) << service->entryPath() << "Querying the KCMs associated to a plugin using the X-KDE-ParentComponents is deprecated."
70  << "Instead define the X-KDE-ConfigModule with the namespace and plugin filen name.";
71  QJsonObject obj;
72  QJsonObject kplugin{{QLatin1String("Name"), service->name()}};
73  QString pluginInfoName = service->property(QStringLiteral("X-KDE-PluginInfo-Name")).toString();
74  if (!pluginInfoName.isEmpty()) {
75  kplugin.insert(QLatin1String("Id"), pluginInfoName);
76  }
77  obj.insert(QLatin1String("KPlugin"), kplugin);
78  obj.insert(QLatin1String("X-KDE-PluginKeyword"), service->pluginKeyword());
79  return KPluginMetaData(obj, service->library());
80  }
81  QT_WARNING_POP
82 #endif
83  return KPluginMetaData();
84  }
85 
86  QVector<KPluginMetaData> m_plugins;
88  KConfigGroup m_config;
89  QHash<QString, QString> m_categoryLabels;
90  QHash<QString, bool> m_pendingStates;
91 };
92 
93 KPluginModel::KPluginModel(QObject *parent)
94  : QAbstractListModel(parent)
95  , d(new KPluginModelPrivate())
96 {
97 }
98 
99 KPluginModel::~KPluginModel() = default;
100 
101 QVariant KPluginModel::data(const QModelIndex &index, int role) const
102 {
103  const KPluginMetaData &plugin = d->m_plugins[index.row()];
104 
105  switch (role) {
106  case Roles::NameRole:
107  return plugin.name();
108  case Roles::DescriptionRole:
109  return plugin.description();
110  case Roles::IconRole:
111  return plugin.iconName();
112  case Roles::EnabledRole:
113  return d->isPluginEnabled(plugin);
114  case Roles::IsChangeableRole:
115  if (d->m_config.isValid()) {
116  return !d->m_config.isEntryImmutable(plugin.pluginId() + QLatin1String("Enabled"));
117  }
118  return true;
119  case MetaDataRole:
120  return QVariant::fromValue(plugin);
123  return d->m_categoryLabels[plugin.pluginId()];
124  case ConfigRole:
125  return QVariant::fromValue(d->m_pluginKcms.value(plugin.pluginId()));
126  case IdRole:
127  return plugin.pluginId();
128  case EnabledByDefaultRole:
129  return plugin.isEnabledByDefault();
130  }
131 
132  return {};
133 }
134 
135 bool KPluginModel::setData(const QModelIndex &index, const QVariant &value, int role)
136 {
137  if (role == Roles::EnabledRole) {
138  const QString pluginId = d->m_plugins[index.row()].pluginId();
139 
140  // If we already have a pending state and the user reverts it remove it from the map
141  auto pendingStateIt = d->m_pendingStates.constFind(pluginId);
142  if (pendingStateIt != d->m_pendingStates.constEnd()) {
143  if (pendingStateIt.value() != value.toBool()) {
144  d->m_pendingStates.erase(pendingStateIt);
145  }
146  } else {
147  d->m_pendingStates[pluginId] = value.toBool();
148  }
149 
150  Q_EMIT dataChanged(index, index, {Roles::EnabledRole});
151  Q_EMIT defaulted(d->isDefaulted());
152  Q_EMIT isSaveNeededChanged();
153 
154  return true;
155  }
156 
157  return false;
158 }
159 
160 int KPluginModel::rowCount(const QModelIndex & /*parent*/) const
161 {
162  return d->m_plugins.count();
163 }
164 
165 QHash<int, QByteArray> KPluginModel::roleNames() const
166 {
167  return {
169  {Roles::NameRole, "name"},
170  {Roles::IconRole, "icon"},
171  {Roles::EnabledRole, "enabled"},
172  {Roles::DescriptionRole, "description"},
173  {Roles::IsChangeableRole, "changable"},
174  {Roles::EnabledByDefaultRole, "enabledByDefault"},
175  {Roles::MetaDataRole, "metaData"},
176  {Roles::ConfigRole, "config"},
177  };
178 };
179 
180 void KPluginModel::addPlugins(const QVector<KPluginMetaData> &newPlugins, const QString &categoryLabel)
181 {
182  beginInsertRows({}, d->m_plugins.size(), d->m_plugins.size() + newPlugins.size() - 1);
183  d->m_plugins.append(newPlugins);
184 
185  for (const KPluginMetaData &plugin : newPlugins) {
186  d->m_categoryLabels[plugin.pluginId()] = categoryLabel;
187  d->m_pluginKcms.insert(plugin.pluginId(), d->findConfig(plugin));
188  }
189 
190  endInsertRows();
191 
192  Q_EMIT defaulted(d->isDefaulted());
193 }
194 
195 void KPluginModel::setConfig(const KConfigGroup &config)
196 {
197  d->m_config = config;
198 
199  if (!d->m_plugins.isEmpty()) {
200  Q_EMIT dataChanged(index(0, 0), index(d->m_plugins.size() - 1, 0), {Roles::EnabledRole, Roles::IsChangeableRole});
201  }
202 }
203 
204 void KPluginModel::clear()
205 {
206  if (d->m_plugins.isEmpty()) {
207  return;
208  }
209  beginRemoveRows({}, 0, d->m_plugins.size() - 1);
210  d->m_plugins.clear();
211  d->m_pluginKcms.clear();
212  // In case of the "Reset"-button of the KCMs load is called again with the goal
213  // of discarding all local changes. Consequently, the pending states have to be cleared here.
214  d->m_pendingStates.clear();
215  endRemoveRows();
216 }
217 
218 void KPluginModel::save()
219 {
220  if (d->m_config.isValid()) {
221  for (auto it = d->m_pendingStates.cbegin(); it != d->m_pendingStates.cend(); ++it) {
222  d->m_config.writeEntry(it.key() + QLatin1String("Enabled"), it.value());
223  }
224 
225  d->m_config.sync();
226  }
227  d->m_pendingStates.clear();
228 }
229 
230 KPluginMetaData KPluginModel::findConfigForPluginId(const QString &pluginId) const
231 {
232  for (const KPluginMetaData &plugin : std::as_const(d->m_plugins)) {
233  if (plugin.pluginId() == pluginId) {
234  return d->findConfig(plugin);
235  }
236  }
237  return KPluginMetaData();
238 }
239 
240 void KPluginModel::load()
241 {
242  if (!d->m_config.isValid()) {
243  return;
244  }
245 
246  d->m_pendingStates.clear();
247  Q_EMIT dataChanged(index(0, 0), index(d->m_plugins.size() - 1, 0), {Roles::EnabledRole});
248 }
249 
250 void KPluginModel::defaults()
251 {
252  for (int pluginIndex = 0, count = d->m_plugins.count(); pluginIndex < count; ++pluginIndex) {
253  const KPluginMetaData plugin = d->m_plugins.at(pluginIndex);
254  const bool changed = d->isPluginEnabled(plugin) != plugin.isEnabledByDefault();
255 
256  if (changed) {
257  // If the entry was marked as changed, but we flip the value it is unchanged again
258  if (d->m_pendingStates.remove(plugin.pluginId()) == 0) {
259  // If the entry was not changed before, we have to mark it as changed
260  d->m_pendingStates.insert(plugin.pluginId(), plugin.isEnabledByDefault());
261  }
262  Q_EMIT dataChanged(index(pluginIndex, 0), index(pluginIndex, 0), {Roles::EnabledRole});
263  }
264  }
265 
266  Q_EMIT defaulted(true);
267 }
268 
269 bool KPluginModel::isSaveNeeded()
270 {
271  return !d->m_pendingStates.isEmpty();
272 }
273 
QJsonObject rawData() const
QVariant fromValue(const T &value)
int lastIndexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
QString iconName() const
QJsonObject::iterator insert(const QString &key, const QJsonValue &value)
QString name() const
bool isEmpty() const const
KSharedConfigPtr config()
bool toBool() const const
bool value(const QString &key, bool defaultValue) const
int row() const const
bool isEnabledByDefault() const
QString & insert(int position, QChar ch)
QString left(int n) const const
int size() const const
static KPluginMetaData findPluginById(const QString &directory, const QString &pluginId)
QString description() const
QString pluginId() const
QString mid(int position, int n) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Sun Jun 26 2022 03:51:23 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.