/* SPDX-FileCopyrightText: 2007 Matthew Woehlke SPDX-FileCopyrightText: 2007 Jeremy Whiting SPDX-FileCopyrightText: 2016 Olivier Churlaud SPDX-FileCopyrightText: 2019 Kai Uwe Broulik SPDX-FileCopyrightText: 2019 David Redondo SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL */ #include "themesmodel.h" #include #include #include #include #include #include #include #include #include ThemesModel::ThemesModel(QObject *parent) : QAbstractListModel(parent) { } ThemesModel::~ThemesModel() = default; int ThemesModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return m_data.count(); } QVariant ThemesModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= m_data.count()) { return QVariant(); } const auto &item = m_data.at(index.row()); switch (role) { case Qt::DisplayRole: return item.display; case PluginNameRole: return item.pluginName; case DescriptionRole: return item.description; case ColorTypeRole: return item.type; case IsLocalRole: return item.isLocal; case PendingDeletionRole: return item.pendingDeletion; } return QVariant(); } bool ThemesModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid() || index.row() >= m_data.count()) { return false; } if (role == PendingDeletionRole) { auto &item = m_data[index.row()]; const bool pendingDeletion = value.toBool(); if (item.pendingDeletion != pendingDeletion) { item.pendingDeletion = pendingDeletion; Q_EMIT dataChanged(index, index, {PendingDeletionRole}); if (index.row() == selectedThemeIndex() && pendingDeletion) { // move to the next non-pending theme const auto nonPending = match(index, PendingDeletionRole, false); if (!nonPending.isEmpty()) { setSelectedTheme(nonPending.first().data(PluginNameRole).toString()); } } Q_EMIT pendingDeletionsChanged(); return true; } } return false; } QHash ThemesModel::roleNames() const { return { {Qt::DisplayRole, QByteArrayLiteral("display")}, {PluginNameRole, QByteArrayLiteral("pluginName")}, {DescriptionRole, QByteArrayLiteral("description")}, {ColorTypeRole, QByteArrayLiteral("colorType")}, {IsLocalRole, QByteArrayLiteral("isLocal")}, {PendingDeletionRole, QByteArrayLiteral("pendingDeletion")}, }; } QString ThemesModel::selectedTheme() const { return m_selectedTheme; } void ThemesModel::setSelectedTheme(const QString &pluginName) { if (m_selectedTheme == pluginName) { return; } m_selectedTheme = pluginName; Q_EMIT selectedThemeChanged(pluginName); Q_EMIT selectedThemeIndexChanged(); } int ThemesModel::pluginIndex(const QString &pluginName) const { const auto results = match(index(0, 0), PluginNameRole, pluginName, 1, Qt::MatchExactly); if (results.count() == 1) { return results.first().row(); } return -1; } int ThemesModel::selectedThemeIndex() const { return pluginIndex(m_selectedTheme); } void ThemesModel::load() { beginResetModel(); const int oldCount = m_data.count(); m_data.clear(); // Get all desktop themes QStringList themes; const QStringList packs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("plasma/desktoptheme"), QStandardPaths::LocateDirectory); for (const QString &ppath : packs) { const QDir cd(ppath); const QStringList &entries = cd.entryList(QDir::Dirs | QDir::Hidden | QDir::NoDotAndDotDot); for (const QString &pack : entries) { const QString prefix = QStringLiteral("%1%2%3%4metadata.").arg(ppath, QDir::separator(), pack, QDir::separator()); QString _metadata = QStringLiteral("%1json").arg(prefix); if (QFile::exists(_metadata)) { themes << _metadata; continue; } _metadata = QStringLiteral("%1desktop").arg(prefix); if (QFile::exists(_metadata)) { themes << _metadata; } } } for (const QString &theme : std::as_const(themes)) { int themeSepIndex = theme.lastIndexOf(QLatin1Char('/'), -1); const QString themeRoot = theme.left(themeSepIndex); int themeNameSepIndex = themeRoot.lastIndexOf(QLatin1Char('/'), -1); const QString packageName = themeRoot.right(themeRoot.length() - themeNameSepIndex - 1); QString name; QString comment; if (theme.endsWith(QLatin1String(".json"))) { KPluginMetaData data = KPluginMetaData::fromJsonFile(theme); name = data.name(); comment = data.description(); } else { KDesktopFile df(theme); if (df.noDisplay()) { continue; } name = df.readName(); if (name.isEmpty()) { name = packageName; } comment = df.readComment(); } const bool isLocal = QFileInfo(theme).isWritable(); bool hasPluginName = std::any_of(m_data.begin(), m_data.end(), [&](const ThemesModelData &item) { return item.pluginName == packageName; }); if (!hasPluginName) { // Plasma Theme creates a KColorScheme out of the "color" file and falls back to system colors if there is none const QString colorsPath = themeRoot + QStringLiteral("/colors"); const bool followsSystemColors = !QFileInfo::exists(colorsPath); ColorType type = FollowsColorTheme; if (!followsSystemColors) { const KSharedConfig::Ptr config = KSharedConfig::openConfig(colorsPath); const QPalette palette = KColorScheme::createApplicationPalette(config); const int windowBackgroundGray = qGray(palette.window().color().rgb()); if (windowBackgroundGray < 192) { type = DarkTheme; } else { type = LightTheme; } } ThemesModelData item{name, packageName, comment, type, isLocal, false}; m_data.append(item); } } // Sort case-insensitively QCollator collator; collator.setCaseSensitivity(Qt::CaseInsensitive); std::sort(m_data.begin(), m_data.end(), [&collator](const ThemesModelData &a, const ThemesModelData &b) { return collator.compare(a.display, b.display) < 0; }); endResetModel(); // an item might have been added before the currently selected one if (oldCount != m_data.count()) { Q_EMIT selectedThemeIndexChanged(); } } QStringList ThemesModel::pendingDeletions() const { QStringList pendingDeletions; for (const auto &item : std::as_const(m_data)) { if (item.pendingDeletion) { pendingDeletions.append(item.pluginName); } } return pendingDeletions; } void ThemesModel::removeRow(int row) { beginRemoveRows(QModelIndex(), row, row); m_data.erase(m_data.begin() + row); endRemoveRows(); }