Warning, file /plasma/plasma-workspace/kcms/style/stylesmodel.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: 2019 Kai Uwe Broulik <kde@broulik.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "stylesmodel.h"
0008 
0009 #include <QCollator>
0010 #include <QDir>
0011 #include <QStandardPaths>
0012 #include <QStyleFactory>
0013 
0014 #include <KConfig>
0015 #include <KConfigGroup>
0016 
0017 #include <algorithm>
0018 
0019 StylesModel::StylesModel(QObject *parent)
0020     : QAbstractListModel(parent)
0021 {
0022 }
0023 
0024 StylesModel::~StylesModel() = default;
0025 
0026 int StylesModel::rowCount(const QModelIndex &parent) const
0027 {
0028     if (parent.isValid()) {
0029         return 0;
0030     }
0031 
0032     return m_data.count();
0033 }
0034 
0035 QVariant StylesModel::data(const QModelIndex &index, int role) const
0036 {
0037     if (!checkIndex(index)) {
0038         return QVariant();
0039     }
0040 
0041     const auto &item = m_data.at(index.row());
0042 
0043     switch (role) {
0044     case Qt::DisplayRole:
0045         if (!item.display.isEmpty()) {
0046             return item.display;
0047         }
0048         return item.styleName;
0049     case StyleNameRole:
0050         return item.styleName;
0051     case DescriptionRole:
0052         return item.description;
0053     case ConfigurableRole:
0054         return !item.configPage.isEmpty();
0055     }
0056 
0057     return QVariant();
0058 }
0059 
0060 QHash<int, QByteArray> StylesModel::roleNames() const
0061 {
0062     return {
0063         {Qt::DisplayRole, QByteArrayLiteral("display")},
0064         {StyleNameRole, QByteArrayLiteral("styleName")},
0065         {DescriptionRole, QByteArrayLiteral("description")},
0066         {ConfigurableRole, QByteArrayLiteral("configurable")},
0067     };
0068 }
0069 
0070 QString StylesModel::selectedStyle() const
0071 {
0072     return m_selectedStyle;
0073 }
0074 
0075 void StylesModel::setSelectedStyle(const QString &style)
0076 {
0077     if (m_selectedStyle == style) {
0078         return;
0079     }
0080 
0081     const bool firstTime = m_selectedStyle.isNull();
0082     m_selectedStyle = style;
0083 
0084     if (!firstTime) {
0085         Q_EMIT selectedStyleChanged(style);
0086     }
0087     Q_EMIT selectedStyleIndexChanged();
0088 }
0089 
0090 int StylesModel::indexOfStyle(const QString &style) const
0091 {
0092     auto it = std::find_if(m_data.begin(), m_data.end(), [&style](const StylesModelData &item) {
0093         return item.styleName == style;
0094     });
0095 
0096     if (it != m_data.end()) {
0097         return std::distance(m_data.begin(), it);
0098     }
0099 
0100     return -1;
0101 }
0102 
0103 int StylesModel::selectedStyleIndex() const
0104 {
0105     return indexOfStyle(m_selectedStyle);
0106 }
0107 
0108 QString StylesModel::styleConfigPage(const QString &style) const
0109 {
0110     const int idx = indexOfStyle(style);
0111     if (idx == -1) {
0112         return QString();
0113     }
0114 
0115     return m_data.at(idx).configPage;
0116 }
0117 
0118 void StylesModel::load()
0119 {
0120     beginResetModel();
0121 
0122     const int oldCount = m_data.count();
0123 
0124     m_data.clear();
0125 
0126     // Combines the info we get from QStyleFactory and our themerc files
0127     QHash<QString, StylesModelData> styleData;
0128 
0129     const QStringList allStyles = QStyleFactory::keys();
0130     for (const QString &styleName : allStyles) {
0131         auto &item = styleData[styleName];
0132         item.styleName = styleName;
0133     }
0134 
0135     QStringList themeFiles;
0136 
0137     const QStringList themeDirs =
0138         QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kstyle/themes"), QStandardPaths::LocateDirectory);
0139     for (const QString &dir : themeDirs) {
0140         const QStringList fileNames = QDir(dir).entryList(QStringList{QStringLiteral("*.themerc")});
0141         for (const QString &file : fileNames) {
0142             const QString suffixedFileName = QLatin1String("kstyle/themes/") + file;
0143             if (!themeFiles.contains(suffixedFileName)) {
0144                 themeFiles.append(suffixedFileName);
0145             }
0146         }
0147     }
0148 
0149     std::transform(themeFiles.begin(), themeFiles.end(), themeFiles.begin(), [](const QString &item) {
0150         return QStandardPaths::locate(QStandardPaths::GenericDataLocation, item);
0151     });
0152 
0153     for (const QString &file : themeFiles) {
0154         KConfig config(file, KConfig::SimpleConfig);
0155         if (!config.hasGroup("KDE") || !config.hasGroup("Misc")) {
0156             continue;
0157         }
0158 
0159         KConfigGroup kdeGroup = config.group("KDE");
0160 
0161         const QString styleName = kdeGroup.readEntry("WidgetStyle", QString());
0162         if (styleName.isEmpty()) {
0163             continue;
0164         }
0165 
0166         auto it = styleData.find(styleName);
0167         if (it == styleData.end()) {
0168             continue;
0169         }
0170 
0171         auto &item = *it;
0172 
0173         KConfigGroup desktopEntryGroup = config.group("Desktop Entry");
0174         if (desktopEntryGroup.readEntry("Hidden", false)) {
0175             // Don't list hidden styles
0176             styleData.remove(styleName);
0177             continue;
0178         }
0179 
0180         KConfigGroup miscGroup = config.group("Misc");
0181 
0182         item.display = miscGroup.readEntry("Name");
0183         item.description = miscGroup.readEntry("Comment");
0184         item.configPage = miscGroup.readEntry("ConfigPage");
0185     }
0186 
0187     m_data = styleData.values().toVector();
0188 
0189     // Sort case-insensitively
0190     QCollator collator;
0191     collator.setCaseSensitivity(Qt::CaseInsensitive);
0192     std::sort(m_data.begin(), m_data.end(), [&collator](const StylesModelData &a, const StylesModelData &b) {
0193         const QString aDisplay = !a.display.isEmpty() ? a.display : a.styleName;
0194         const QString bDisplay = !b.display.isEmpty() ? b.display : b.styleName;
0195         return collator.compare(aDisplay, bDisplay) < 0;
0196     });
0197 
0198     endResetModel();
0199 
0200     // an item might have been added before the currently selected one
0201     if (oldCount != m_data.count()) {
0202         Q_EMIT selectedStyleIndexChanged();
0203     }
0204 }