Warning, file /plasma/plasma-workspace/kcms/desktoptheme/themesmodel.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 SPDX-FileCopyrightText: 2007 Matthew Woehlke <mw_triad@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2007 Jeremy Whiting <jpwhiting@kde.org> 0004 SPDX-FileCopyrightText: 2016 Olivier Churlaud <olivier@churlaud.com> 0005 SPDX-FileCopyrightText: 2019 Kai Uwe Broulik <kde@privat.broulik.de> 0006 SPDX-FileCopyrightText: 2019 David Redondo <kde@david-redondo.de> 0007 0008 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0009 */ 0010 0011 #include "themesmodel.h" 0012 0013 #include <QCollator> 0014 #include <QDir> 0015 #include <QStandardPaths> 0016 0017 #include <KColorScheme> 0018 #include <KDesktopFile> 0019 #include <KPluginMetaData> 0020 0021 #include <KConfigGroup> 0022 #include <KSharedConfig> 0023 0024 #include <algorithm> 0025 0026 ThemesModel::ThemesModel(QObject *parent) 0027 : QAbstractListModel(parent) 0028 { 0029 } 0030 0031 ThemesModel::~ThemesModel() = default; 0032 0033 int ThemesModel::rowCount(const QModelIndex &parent) const 0034 { 0035 if (parent.isValid()) { 0036 return 0; 0037 } 0038 0039 return m_data.count(); 0040 } 0041 0042 QVariant ThemesModel::data(const QModelIndex &index, int role) const 0043 { 0044 if (!index.isValid() || index.row() >= m_data.count()) { 0045 return QVariant(); 0046 } 0047 0048 const auto &item = m_data.at(index.row()); 0049 0050 switch (role) { 0051 case Qt::DisplayRole: 0052 return item.display; 0053 case PluginNameRole: 0054 return item.pluginName; 0055 case DescriptionRole: 0056 return item.description; 0057 case ColorTypeRole: 0058 return item.type; 0059 case IsLocalRole: 0060 return item.isLocal; 0061 case PendingDeletionRole: 0062 return item.pendingDeletion; 0063 } 0064 return QVariant(); 0065 } 0066 0067 bool ThemesModel::setData(const QModelIndex &index, const QVariant &value, int role) 0068 { 0069 if (!index.isValid() || index.row() >= m_data.count()) { 0070 return false; 0071 } 0072 0073 if (role == PendingDeletionRole) { 0074 auto &item = m_data[index.row()]; 0075 0076 const bool pendingDeletion = value.toBool(); 0077 0078 if (item.pendingDeletion != pendingDeletion) { 0079 item.pendingDeletion = pendingDeletion; 0080 Q_EMIT dataChanged(index, index, {PendingDeletionRole}); 0081 0082 if (index.row() == selectedThemeIndex() && pendingDeletion) { 0083 // move to the next non-pending theme 0084 const auto nonPending = match(index, PendingDeletionRole, false); 0085 if (!nonPending.isEmpty()) { 0086 setSelectedTheme(nonPending.first().data(PluginNameRole).toString()); 0087 } 0088 } 0089 0090 Q_EMIT pendingDeletionsChanged(); 0091 return true; 0092 } 0093 } 0094 0095 return false; 0096 } 0097 0098 QHash<int, QByteArray> ThemesModel::roleNames() const 0099 { 0100 return { 0101 {Qt::DisplayRole, QByteArrayLiteral("display")}, 0102 {PluginNameRole, QByteArrayLiteral("pluginName")}, 0103 {DescriptionRole, QByteArrayLiteral("description")}, 0104 {ColorTypeRole, QByteArrayLiteral("colorType")}, 0105 {IsLocalRole, QByteArrayLiteral("isLocal")}, 0106 {PendingDeletionRole, QByteArrayLiteral("pendingDeletion")}, 0107 }; 0108 } 0109 0110 QString ThemesModel::selectedTheme() const 0111 { 0112 return m_selectedTheme; 0113 } 0114 0115 void ThemesModel::setSelectedTheme(const QString &pluginName) 0116 { 0117 if (m_selectedTheme == pluginName) { 0118 return; 0119 } 0120 0121 m_selectedTheme = pluginName; 0122 0123 Q_EMIT selectedThemeChanged(pluginName); 0124 0125 Q_EMIT selectedThemeIndexChanged(); 0126 } 0127 0128 int ThemesModel::pluginIndex(const QString &pluginName) const 0129 { 0130 const auto results = match(index(0, 0), PluginNameRole, pluginName, 1, Qt::MatchExactly); 0131 if (results.count() == 1) { 0132 return results.first().row(); 0133 } 0134 0135 return -1; 0136 } 0137 0138 int ThemesModel::selectedThemeIndex() const 0139 { 0140 return pluginIndex(m_selectedTheme); 0141 } 0142 0143 void ThemesModel::load() 0144 { 0145 beginResetModel(); 0146 0147 const int oldCount = m_data.count(); 0148 0149 m_data.clear(); 0150 0151 // Get all desktop themes 0152 QStringList themes; 0153 const QStringList packs = 0154 QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("plasma/desktoptheme"), QStandardPaths::LocateDirectory); 0155 for (const QString &ppath : packs) { 0156 const QDir cd(ppath); 0157 const QStringList &entries = cd.entryList(QDir::Dirs | QDir::Hidden | QDir::NoDotAndDotDot); 0158 for (const QString &pack : entries) { 0159 const QString prefix = QStringLiteral("%1%2%3%4metadata.").arg(ppath, QDir::separator(), pack, QDir::separator()); 0160 0161 QString _metadata = QStringLiteral("%1json").arg(prefix); 0162 if (QFile::exists(_metadata)) { 0163 themes << _metadata; 0164 continue; 0165 } 0166 0167 _metadata = QStringLiteral("%1desktop").arg(prefix); 0168 if (QFile::exists(_metadata)) { 0169 themes << _metadata; 0170 } 0171 } 0172 } 0173 0174 for (const QString &theme : qAsConst(themes)) { 0175 int themeSepIndex = theme.lastIndexOf(QLatin1Char('/'), -1); 0176 const QString themeRoot = theme.left(themeSepIndex); 0177 int themeNameSepIndex = themeRoot.lastIndexOf(QLatin1Char('/'), -1); 0178 const QString packageName = themeRoot.right(themeRoot.length() - themeNameSepIndex - 1); 0179 0180 QString name; 0181 QString comment; 0182 0183 if (theme.endsWith(QLatin1String(".json"))) { 0184 KPluginMetaData data = KPluginMetaData::fromJsonFile(theme); 0185 name = data.name(); 0186 comment = data.description(); 0187 } else { 0188 KDesktopFile df(theme); 0189 0190 if (df.noDisplay()) { 0191 continue; 0192 } 0193 0194 name = df.readName(); 0195 if (name.isEmpty()) { 0196 name = packageName; 0197 } 0198 comment = df.readComment(); 0199 } 0200 const bool isLocal = QFileInfo(theme).isWritable(); 0201 bool hasPluginName = std::any_of(m_data.begin(), m_data.end(), [&](const ThemesModelData &item) { 0202 return item.pluginName == packageName; 0203 }); 0204 if (!hasPluginName) { 0205 // Plasma Theme creates a KColorScheme out of the "color" file and falls back to system colors if there is none 0206 const QString colorsPath = themeRoot + QStringLiteral("/colors"); 0207 const bool followsSystemColors = !QFileInfo::exists(colorsPath); 0208 ColorType type = FollowsColorTheme; 0209 if (!followsSystemColors) { 0210 const KSharedConfig::Ptr config = KSharedConfig::openConfig(colorsPath); 0211 const QPalette palette = KColorScheme::createApplicationPalette(config); 0212 const int windowBackgroundGray = qGray(palette.window().color().rgb()); 0213 if (windowBackgroundGray < 192) { 0214 type = DarkTheme; 0215 } else { 0216 type = LightTheme; 0217 } 0218 } 0219 ThemesModelData item{name, packageName, comment, type, isLocal, false}; 0220 m_data.append(item); 0221 } 0222 } 0223 0224 // Sort case-insensitively 0225 QCollator collator; 0226 collator.setCaseSensitivity(Qt::CaseInsensitive); 0227 std::sort(m_data.begin(), m_data.end(), [&collator](const ThemesModelData &a, const ThemesModelData &b) { 0228 return collator.compare(a.display, b.display) < 0; 0229 }); 0230 0231 endResetModel(); 0232 0233 // an item might have been added before the currently selected one 0234 if (oldCount != m_data.count()) { 0235 Q_EMIT selectedThemeIndexChanged(); 0236 } 0237 } 0238 0239 QStringList ThemesModel::pendingDeletions() const 0240 { 0241 QStringList pendingDeletions; 0242 0243 for (const auto &item : qAsConst(m_data)) { 0244 if (item.pendingDeletion) { 0245 pendingDeletions.append(item.pluginName); 0246 } 0247 } 0248 0249 return pendingDeletions; 0250 } 0251 0252 void ThemesModel::removeRow(int row) 0253 { 0254 beginRemoveRows(QModelIndex(), row, row); 0255 m_data.erase(m_data.begin() + row); 0256 endRemoveRows(); 0257 }