File indexing completed on 2024-12-22 03:41:45
0001 /* 0002 SPDX-FileCopyrightText: 2021 Nicolas Fella <nicolas.fella@gmx.de> 0003 SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "kpluginmodel.h" 0009 #include "kpluginproxymodel.h" 0010 0011 #include <QPluginLoader> 0012 0013 #include <KCategorizedSortFilterProxyModel> 0014 #include <KConfigGroup> 0015 0016 #include <utility> 0017 0018 #include "kcmutilscore_debug.h" 0019 0020 class KPluginModelPrivate 0021 { 0022 public: 0023 bool isDefaulted() 0024 { 0025 return std::all_of(m_plugins.cbegin(), m_plugins.cend(), [this](const KPluginMetaData &data) { 0026 return isPluginEnabled(data) == data.isEnabledByDefault(); 0027 }); 0028 } 0029 bool isPluginEnabled(const KPluginMetaData &plugin) const 0030 { 0031 auto pendingState = m_pendingStates.constFind(plugin.pluginId()); 0032 if (pendingState != m_pendingStates.constEnd()) { 0033 return pendingState.value(); 0034 } 0035 0036 if (m_config.isValid()) { 0037 return m_config.readEntry(plugin.pluginId() + QLatin1String("Enabled"), plugin.isEnabledByDefault()); 0038 } 0039 return plugin.isEnabledByDefault(); 0040 } 0041 KPluginMetaData findConfig(const KPluginMetaData &plugin) const 0042 { 0043 const QString metaDataKCM = plugin.value(QStringLiteral("X-KDE-ConfigModule")); 0044 0045 if (!metaDataKCM.isEmpty()) { 0046 const QString absoluteKCMPath = QPluginLoader(metaDataKCM).fileName(); 0047 // If we have a static plugin the file does not exist on disk 0048 // instead we query in the plugin namespace 0049 if (absoluteKCMPath.isEmpty()) { 0050 const int idx = metaDataKCM.lastIndexOf(QLatin1Char('/')); 0051 const QString pluginNamespace = metaDataKCM.left(idx); 0052 const QString pluginId = metaDataKCM.mid(idx + 1); 0053 return KPluginMetaData::findPluginById(pluginNamespace, pluginId); 0054 } else { 0055 return KPluginMetaData(plugin.rawData(), absoluteKCMPath); 0056 } 0057 } 0058 0059 return KPluginMetaData(); 0060 } 0061 0062 QList<KPluginMetaData> m_plugins; 0063 QSet<KPluginMetaData> m_unsortablePlugins; 0064 QHash<QString, KPluginMetaData> m_pluginKcms; 0065 KConfigGroup m_config; 0066 QList<QString> m_orderedCategories; // Preserve order of categories in which they were added 0067 QHash<QString, QString> m_categoryLabels; 0068 QHash<QString, bool> m_pendingStates; 0069 }; 0070 0071 KPluginModel::KPluginModel(QObject *parent) 0072 : QAbstractListModel(parent) 0073 , d(new KPluginModelPrivate()) 0074 { 0075 } 0076 0077 KPluginModel::~KPluginModel() = default; 0078 0079 QVariant KPluginModel::data(const QModelIndex &index, int role) const 0080 { 0081 const KPluginMetaData &plugin = d->m_plugins[index.row()]; 0082 0083 switch (role) { 0084 case Roles::NameRole: 0085 return plugin.name(); 0086 case Roles::DescriptionRole: 0087 return plugin.description(); 0088 case Roles::IconRole: 0089 return plugin.iconName(); 0090 case Roles::EnabledRole: 0091 return d->isPluginEnabled(plugin); 0092 case Roles::IsChangeableRole: 0093 if (d->m_unsortablePlugins.contains(plugin)) { 0094 return false; 0095 } 0096 if (d->m_config.isValid()) { 0097 return !d->m_config.isEntryImmutable(plugin.pluginId() + QLatin1String("Enabled")); 0098 } 0099 return true; 0100 case MetaDataRole: 0101 return QVariant::fromValue(plugin); 0102 case KCategorizedSortFilterProxyModel::CategoryDisplayRole: 0103 case KCategorizedSortFilterProxyModel::CategorySortRole: 0104 return d->m_categoryLabels[plugin.pluginId()]; 0105 case ConfigRole: 0106 return QVariant::fromValue(d->m_pluginKcms.value(plugin.pluginId())); 0107 case IdRole: 0108 return plugin.pluginId(); 0109 case EnabledByDefaultRole: 0110 return plugin.isEnabledByDefault(); 0111 case SortableRole: 0112 return !d->m_unsortablePlugins.contains(plugin); 0113 } 0114 0115 return {}; 0116 } 0117 0118 bool KPluginModel::setData(const QModelIndex &index, const QVariant &value, int role) 0119 { 0120 if (role == Roles::EnabledRole) { 0121 const QString pluginId = d->m_plugins[index.row()].pluginId(); 0122 0123 // If we already have a pending state and the user reverts it remove it from the map 0124 auto pendingStateIt = d->m_pendingStates.constFind(pluginId); 0125 if (pendingStateIt != d->m_pendingStates.constEnd()) { 0126 if (pendingStateIt.value() != value.toBool()) { 0127 d->m_pendingStates.erase(pendingStateIt); 0128 } 0129 } else { 0130 d->m_pendingStates[pluginId] = value.toBool(); 0131 } 0132 0133 Q_EMIT dataChanged(index, index, {Roles::EnabledRole}); 0134 Q_EMIT defaulted(d->isDefaulted()); 0135 Q_EMIT isSaveNeededChanged(); 0136 0137 return true; 0138 } 0139 0140 return false; 0141 } 0142 0143 int KPluginModel::rowCount(const QModelIndex & /*parent*/) const 0144 { 0145 return d->m_plugins.count(); 0146 } 0147 0148 QHash<int, QByteArray> KPluginModel::roleNames() const 0149 { 0150 return { 0151 {KCategorizedSortFilterProxyModel::CategoryDisplayRole, "category"}, 0152 {Roles::NameRole, "name"}, 0153 {Roles::IconRole, "icon"}, 0154 {Roles::EnabledRole, "enabled"}, 0155 {Roles::DescriptionRole, "description"}, 0156 {Roles::IsChangeableRole, "changable"}, 0157 {Roles::EnabledByDefaultRole, "enabledByDefault"}, 0158 {Roles::MetaDataRole, "metaData"}, 0159 {Roles::ConfigRole, "config"}, 0160 }; 0161 }; 0162 0163 void KPluginModel::addUnsortablePlugins(const QList<KPluginMetaData> &newPlugins, const QString &categoryLabel) 0164 { 0165 d->m_unsortablePlugins.unite(QSet(newPlugins.begin(), newPlugins.end())); 0166 addPlugins(newPlugins, categoryLabel); 0167 } 0168 0169 bool KPluginModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild) 0170 { 0171 if (sourceParent.isValid() || destinationParent.isValid()) { 0172 return false; 0173 } 0174 if ((sourceRow + count - 1) >= d->m_plugins.size()) { 0175 return false; 0176 } 0177 0178 const bool isMoveDown = destinationChild > sourceRow; 0179 if (!beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, isMoveDown ? destinationChild + 1 : destinationChild)) { 0180 return false; 0181 } 0182 for (int i = 0; i < count; i++) { 0183 d->m_plugins.insert(destinationChild, d->m_plugins.takeAt(sourceRow + i)); 0184 } 0185 endMoveRows(); 0186 return true; 0187 } 0188 0189 void KPluginModel::addPlugins(const QList<KPluginMetaData> &newPlugins, const QString &categoryLabel) 0190 { 0191 beginInsertRows({}, d->m_plugins.size(), d->m_plugins.size() + newPlugins.size() - 1); 0192 d->m_orderedCategories << categoryLabel; 0193 d->m_plugins.append(newPlugins); 0194 0195 for (const KPluginMetaData &plugin : newPlugins) { 0196 d->m_categoryLabels[plugin.pluginId()] = categoryLabel; 0197 d->m_pluginKcms.insert(plugin.pluginId(), d->findConfig(plugin)); 0198 } 0199 0200 endInsertRows(); 0201 0202 Q_EMIT defaulted(d->isDefaulted()); 0203 } 0204 0205 void KPluginModel::removePlugin(const KPluginMetaData &data) 0206 { 0207 if (const int index = d->m_plugins.indexOf(data); index != -1) { 0208 beginRemoveRows({}, index, index); 0209 d->m_plugins.removeAt(index); 0210 d->m_unsortablePlugins.remove(data); 0211 endRemoveRows(); 0212 } 0213 } 0214 0215 void KPluginModel::setConfig(const KConfigGroup &config) 0216 { 0217 d->m_config = config; 0218 0219 if (!d->m_plugins.isEmpty()) { 0220 Q_EMIT dataChanged(index(0, 0), index(d->m_plugins.size() - 1, 0), {Roles::EnabledRole, Roles::IsChangeableRole}); 0221 } 0222 } 0223 0224 void KPluginModel::clear() 0225 { 0226 if (d->m_plugins.isEmpty()) { 0227 return; 0228 } 0229 beginRemoveRows({}, 0, d->m_plugins.size() - 1); 0230 d->m_plugins.clear(); 0231 d->m_pluginKcms.clear(); 0232 // In case of the "Reset"-button of the KCMs load is called again with the goal 0233 // of discarding all local changes. Consequently, the pending states have to be cleared here. 0234 d->m_pendingStates.clear(); 0235 endRemoveRows(); 0236 } 0237 0238 void KPluginModel::save() 0239 { 0240 if (d->m_config.isValid()) { 0241 for (auto it = d->m_pendingStates.cbegin(); it != d->m_pendingStates.cend(); ++it) { 0242 d->m_config.writeEntry(it.key() + QLatin1String("Enabled"), it.value()); 0243 } 0244 0245 d->m_config.sync(); 0246 } 0247 d->m_pendingStates.clear(); 0248 } 0249 0250 KPluginMetaData KPluginModel::findConfigForPluginId(const QString &pluginId) const 0251 { 0252 for (const KPluginMetaData &plugin : std::as_const(d->m_plugins)) { 0253 if (plugin.pluginId() == pluginId) { 0254 return d->findConfig(plugin); 0255 } 0256 } 0257 return KPluginMetaData(); 0258 } 0259 0260 void KPluginModel::load() 0261 { 0262 if (!d->m_config.isValid()) { 0263 return; 0264 } 0265 0266 d->m_pendingStates.clear(); 0267 Q_EMIT dataChanged(index(0, 0), index(d->m_plugins.size() - 1, 0), {Roles::EnabledRole}); 0268 } 0269 0270 void KPluginModel::defaults() 0271 { 0272 for (int pluginIndex = 0, count = d->m_plugins.count(); pluginIndex < count; ++pluginIndex) { 0273 const KPluginMetaData plugin = d->m_plugins.at(pluginIndex); 0274 const bool changed = d->isPluginEnabled(plugin) != plugin.isEnabledByDefault(); 0275 0276 if (changed) { 0277 // If the entry was marked as changed, but we flip the value it is unchanged again 0278 if (d->m_pendingStates.remove(plugin.pluginId()) == 0) { 0279 // If the entry was not changed before, we have to mark it as changed 0280 d->m_pendingStates.insert(plugin.pluginId(), plugin.isEnabledByDefault()); 0281 } 0282 Q_EMIT dataChanged(index(pluginIndex, 0), index(pluginIndex, 0), {Roles::EnabledRole}); 0283 } 0284 } 0285 0286 Q_EMIT defaulted(true); 0287 } 0288 0289 bool KPluginModel::isSaveNeeded() 0290 { 0291 return !d->m_pendingStates.isEmpty(); 0292 } 0293 0294 QStringList KPluginModel::getOrderedCategoryLabels() 0295 { 0296 return d->m_orderedCategories; 0297 } 0298 0299 #include "moc_kpluginmodel.cpp"