File indexing completed on 2026-07-12 11:20:43
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 #include <KServiceTypeTrader> 0016 0017 #include <utility> 0018 0019 #include "kcmutilscore_debug.h" 0020 0021 class KPluginModelPrivate 0022 { 0023 public: 0024 bool isDefaulted() 0025 { 0026 return std::all_of(m_plugins.cbegin(), m_plugins.cend(), [this](const KPluginMetaData &data) { 0027 return isPluginEnabled(data) == data.isEnabledByDefault(); 0028 }); 0029 } 0030 bool isPluginEnabled(const KPluginMetaData &plugin) const 0031 { 0032 auto pendingState = m_pendingStates.constFind(plugin.pluginId()); 0033 if (pendingState != m_pendingStates.constEnd()) { 0034 return pendingState.value(); 0035 } 0036 0037 if (m_config.isValid()) { 0038 return m_config.readEntry(plugin.pluginId() + QLatin1String("Enabled"), plugin.isEnabledByDefault()); 0039 } 0040 return plugin.isEnabledByDefault(); 0041 } 0042 KPluginMetaData findConfig(const KPluginMetaData &plugin) const 0043 { 0044 const QString metaDataKCM = plugin.value(QStringLiteral("X-KDE-ConfigModule")); 0045 0046 if (!metaDataKCM.isEmpty()) { 0047 const QString absoluteKCMPath = QPluginLoader(metaDataKCM).fileName(); 0048 // If we have a static plugin the file does not exist on disk 0049 // instead we query in the plugin namespace 0050 if (absoluteKCMPath.isEmpty()) { 0051 const int idx = metaDataKCM.lastIndexOf(QLatin1Char('/')); 0052 const QString pluginNamespace = metaDataKCM.left(idx); 0053 const QString pluginId = metaDataKCM.mid(idx + 1); 0054 return KPluginMetaData::findPluginById(pluginNamespace, pluginId); 0055 } else { 0056 return KPluginMetaData(plugin.rawData(), absoluteKCMPath); 0057 } 0058 } 0059 0060 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 89) 0061 QT_WARNING_PUSH 0062 QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations") 0063 QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations") 0064 0065 const QString constraint = QLatin1Char('\'') + plugin.pluginId() + QLatin1String("' in [X-KDE-ParentComponents]"); 0066 const auto services = KServiceTypeTrader::self()->query(QStringLiteral("KCModule"), constraint); 0067 if (!services.isEmpty()) { 0068 const KService::Ptr service = services.constFirst(); 0069 qCWarning(KCMUTILS_CORE_LOG) << service->entryPath() << "Querying the KCMs associated to a plugin using the X-KDE-ParentComponents is deprecated." 0070 << "Instead define the X-KDE-ConfigModule with the namespace and plugin filen name."; 0071 QJsonObject obj; 0072 QJsonObject kplugin{{QLatin1String("Name"), service->name()}}; 0073 QString pluginInfoName = service->property(QStringLiteral("X-KDE-PluginInfo-Name")).toString(); 0074 if (!pluginInfoName.isEmpty()) { 0075 kplugin.insert(QLatin1String("Id"), pluginInfoName); 0076 } 0077 obj.insert(QLatin1String("KPlugin"), kplugin); 0078 obj.insert(QLatin1String("X-KDE-PluginKeyword"), service->pluginKeyword()); 0079 return KPluginMetaData(obj, service->library()); 0080 } 0081 QT_WARNING_POP 0082 #endif 0083 return KPluginMetaData(); 0084 } 0085 0086 QVector<KPluginMetaData> m_plugins; 0087 QHash<QString, KPluginMetaData> m_pluginKcms; 0088 KConfigGroup m_config; 0089 QHash<QString, QString> m_categoryLabels; 0090 QHash<QString, bool> m_pendingStates; 0091 }; 0092 0093 KPluginModel::KPluginModel(QObject *parent) 0094 : QAbstractListModel(parent) 0095 , d(new KPluginModelPrivate()) 0096 { 0097 } 0098 0099 KPluginModel::~KPluginModel() = default; 0100 0101 QVariant KPluginModel::data(const QModelIndex &index, int role) const 0102 { 0103 const KPluginMetaData &plugin = d->m_plugins[index.row()]; 0104 0105 switch (role) { 0106 case Roles::NameRole: 0107 return plugin.name(); 0108 case Roles::DescriptionRole: 0109 return plugin.description(); 0110 case Roles::IconRole: 0111 return plugin.iconName(); 0112 case Roles::EnabledRole: 0113 return d->isPluginEnabled(plugin); 0114 case Roles::IsChangeableRole: 0115 if (d->m_config.isValid()) { 0116 return !d->m_config.isEntryImmutable(plugin.pluginId() + QLatin1String("Enabled")); 0117 } 0118 return true; 0119 case MetaDataRole: 0120 return QVariant::fromValue(plugin); 0121 case KCategorizedSortFilterProxyModel::CategoryDisplayRole: 0122 case KCategorizedSortFilterProxyModel::CategorySortRole: 0123 return d->m_categoryLabels[plugin.pluginId()]; 0124 case ConfigRole: 0125 return QVariant::fromValue(d->m_pluginKcms.value(plugin.pluginId())); 0126 case IdRole: 0127 return plugin.pluginId(); 0128 case EnabledByDefaultRole: 0129 return plugin.isEnabledByDefault(); 0130 } 0131 0132 return {}; 0133 } 0134 0135 bool KPluginModel::setData(const QModelIndex &index, const QVariant &value, int role) 0136 { 0137 if (role == Roles::EnabledRole) { 0138 const QString pluginId = d->m_plugins[index.row()].pluginId(); 0139 0140 // If we already have a pending state and the user reverts it remove it from the map 0141 auto pendingStateIt = d->m_pendingStates.constFind(pluginId); 0142 if (pendingStateIt != d->m_pendingStates.constEnd()) { 0143 if (pendingStateIt.value() != value.toBool()) { 0144 d->m_pendingStates.erase(pendingStateIt); 0145 } 0146 } else { 0147 d->m_pendingStates[pluginId] = value.toBool(); 0148 } 0149 0150 Q_EMIT dataChanged(index, index, {Roles::EnabledRole}); 0151 Q_EMIT defaulted(d->isDefaulted()); 0152 Q_EMIT isSaveNeededChanged(); 0153 0154 return true; 0155 } 0156 0157 return false; 0158 } 0159 0160 int KPluginModel::rowCount(const QModelIndex & /*parent*/) const 0161 { 0162 return d->m_plugins.count(); 0163 } 0164 0165 QHash<int, QByteArray> KPluginModel::roleNames() const 0166 { 0167 return { 0168 {KCategorizedSortFilterProxyModel::CategoryDisplayRole, "category"}, 0169 {Roles::NameRole, "name"}, 0170 {Roles::IconRole, "icon"}, 0171 {Roles::EnabledRole, "enabled"}, 0172 {Roles::DescriptionRole, "description"}, 0173 {Roles::IsChangeableRole, "changable"}, 0174 {Roles::EnabledByDefaultRole, "enabledByDefault"}, 0175 {Roles::MetaDataRole, "metaData"}, 0176 {Roles::ConfigRole, "config"}, 0177 }; 0178 }; 0179 0180 void KPluginModel::addPlugins(const QVector<KPluginMetaData> &newPlugins, const QString &categoryLabel) 0181 { 0182 beginInsertRows({}, d->m_plugins.size(), d->m_plugins.size() + newPlugins.size() - 1); 0183 d->m_plugins.append(newPlugins); 0184 0185 for (const KPluginMetaData &plugin : newPlugins) { 0186 d->m_categoryLabels[plugin.pluginId()] = categoryLabel; 0187 d->m_pluginKcms.insert(plugin.pluginId(), d->findConfig(plugin)); 0188 } 0189 0190 endInsertRows(); 0191 0192 Q_EMIT defaulted(d->isDefaulted()); 0193 } 0194 0195 void KPluginModel::setConfig(const KConfigGroup &config) 0196 { 0197 d->m_config = config; 0198 0199 if (!d->m_plugins.isEmpty()) { 0200 Q_EMIT dataChanged(index(0, 0), index(d->m_plugins.size() - 1, 0), {Roles::EnabledRole, Roles::IsChangeableRole}); 0201 } 0202 } 0203 0204 void KPluginModel::clear() 0205 { 0206 if (d->m_plugins.isEmpty()) { 0207 return; 0208 } 0209 beginRemoveRows({}, 0, d->m_plugins.size() - 1); 0210 d->m_plugins.clear(); 0211 d->m_pluginKcms.clear(); 0212 // In case of the "Reset"-button of the KCMs load is called again with the goal 0213 // of discarding all local changes. Consequently, the pending states have to be cleared here. 0214 d->m_pendingStates.clear(); 0215 endRemoveRows(); 0216 } 0217 0218 void KPluginModel::save() 0219 { 0220 if (d->m_config.isValid()) { 0221 for (auto it = d->m_pendingStates.cbegin(); it != d->m_pendingStates.cend(); ++it) { 0222 d->m_config.writeEntry(it.key() + QLatin1String("Enabled"), it.value()); 0223 } 0224 0225 d->m_config.sync(); 0226 } 0227 d->m_pendingStates.clear(); 0228 } 0229 0230 KPluginMetaData KPluginModel::findConfigForPluginId(const QString &pluginId) const 0231 { 0232 for (const KPluginMetaData &plugin : std::as_const(d->m_plugins)) { 0233 if (plugin.pluginId() == pluginId) { 0234 return d->findConfig(plugin); 0235 } 0236 } 0237 return KPluginMetaData(); 0238 } 0239 0240 void KPluginModel::load() 0241 { 0242 if (!d->m_config.isValid()) { 0243 return; 0244 } 0245 0246 d->m_pendingStates.clear(); 0247 Q_EMIT dataChanged(index(0, 0), index(d->m_plugins.size() - 1, 0), {Roles::EnabledRole}); 0248 } 0249 0250 void KPluginModel::defaults() 0251 { 0252 for (int pluginIndex = 0, count = d->m_plugins.count(); pluginIndex < count; ++pluginIndex) { 0253 const KPluginMetaData plugin = d->m_plugins.at(pluginIndex); 0254 const bool changed = d->isPluginEnabled(plugin) != plugin.isEnabledByDefault(); 0255 0256 if (changed) { 0257 // If the entry was marked as changed, but we flip the value it is unchanged again 0258 if (d->m_pendingStates.remove(plugin.pluginId()) == 0) { 0259 // If the entry was not changed before, we have to mark it as changed 0260 d->m_pendingStates.insert(plugin.pluginId(), plugin.isEnabledByDefault()); 0261 } 0262 Q_EMIT dataChanged(index(pluginIndex, 0), index(pluginIndex, 0), {Roles::EnabledRole}); 0263 } 0264 } 0265 0266 Q_EMIT defaulted(true); 0267 } 0268 0269 bool KPluginModel::isSaveNeeded() 0270 { 0271 return !d->m_pendingStates.isEmpty(); 0272 } 0273 0274 #include "moc_kpluginmodel.cpp"