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 }