File indexing completed on 2024-05-19 05:38:25

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