You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

218 lines
6.0 KiB

/*
* Copyright (C) 2019 Kai Uwe Broulik <kde@broulik.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "stylesmodel.h"
#include <QCollator>
#include <QDir>
#include <QStandardPaths>
#include <QStyleFactory>
#include <KConfig>
#include <KConfigGroup>
#include <algorithm>
StylesModel::StylesModel(QObject *parent)
: QAbstractListModel(parent)
{
}
StylesModel::~StylesModel() = default;
int StylesModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return 0;
}
return m_data.count();
}
QVariant StylesModel::data(const QModelIndex &index, int role) const
{
if (!checkIndex(index)) {
return QVariant();
}
const auto &item = m_data.at(index.row());
switch (role) {
case Qt::DisplayRole:
if (!item.display.isEmpty()) {
return item.display;
}
return item.styleName;
case StyleNameRole:
return item.styleName;
case DescriptionRole:
return item.description;
case ConfigurableRole:
return !item.configPage.isEmpty();
}
return QVariant();
}
QHash<int, QByteArray> StylesModel::roleNames() const
{
return {
{Qt::DisplayRole, QByteArrayLiteral("display")},
{StyleNameRole, QByteArrayLiteral("styleName")},
{DescriptionRole, QByteArrayLiteral("description")},
{ConfigurableRole, QByteArrayLiteral("configurable")},
};
}
QString StylesModel::selectedStyle() const
{
return m_selectedStyle;
}
void StylesModel::setSelectedStyle(const QString &style)
{
if (m_selectedStyle == style) {
return;
}
const bool firstTime = m_selectedStyle.isNull();
m_selectedStyle = style;
if (!firstTime) {
emit selectedStyleChanged(style);
}
emit selectedStyleIndexChanged();
}
int StylesModel::indexOfStyle(const QString &style) const
{
auto it = std::find_if(m_data.begin(), m_data.end(), [&style](const StylesModelData &item) {
return item.styleName == style;
});
if (it != m_data.end()) {
return std::distance(m_data.begin(), it);
}
return -1;
}
int StylesModel::selectedStyleIndex() const
{
return indexOfStyle(m_selectedStyle);
}
QString StylesModel::styleConfigPage(const QString &style) const
{
const int idx = indexOfStyle(style);
if (idx == -1) {
return QString();
}
return m_data.at(idx).configPage;
}
void StylesModel::load()
{
beginResetModel();
const int oldCount = m_data.count();
m_data.clear();
// Combines the info we get from QStyleFactory and our themerc files
QHash<QString, StylesModelData> styleData;
const QStringList allStyles = QStyleFactory::keys();
for (const QString &styleName : allStyles) {
auto &item = styleData[styleName];
item.styleName = styleName;
}
QStringList themeFiles;
const QStringList themeDirs =
QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kstyle/themes"), QStandardPaths::LocateDirectory);
for (const QString &dir : themeDirs) {
const QStringList fileNames = QDir(dir).entryList(QStringList{QStringLiteral("*.themerc")});
for (const QString &file : fileNames) {
const QString suffixedFileName = QLatin1String("kstyle/themes/") + file;
if (!themeFiles.contains(suffixedFileName)) {
themeFiles.append(suffixedFileName);
}
}
}
std::transform(themeFiles.begin(), themeFiles.end(), themeFiles.begin(), [](const QString &item) {
return QStandardPaths::locate(QStandardPaths::GenericDataLocation, item);
});
for (const QString &file : themeFiles) {
KConfig config(file, KConfig::SimpleConfig);
if (!config.hasGroup("KDE") || !config.hasGroup("Misc")) {
continue;
}
KConfigGroup kdeGroup = config.group("KDE");
const QString styleName = kdeGroup.readEntry("WidgetStyle", QString());
if (styleName.isEmpty()) {
continue;
}
auto it = styleData.find(styleName);
if (it == styleData.end()) {
continue;
}
auto &item = *it;
KConfigGroup desktopEntryGroup = config.group("Desktop Entry");
if (desktopEntryGroup.readEntry("Hidden", false)) {
// Don't list hidden styles
styleData.remove(styleName);
continue;
}
KConfigGroup miscGroup = config.group("Misc");
item.display = miscGroup.readEntry("Name");
item.description = miscGroup.readEntry("Comment");
item.configPage = miscGroup.readEntry("ConfigPage");
}
m_data = styleData.values().toVector();
// Sort case-insensitively
QCollator collator;
collator.setCaseSensitivity(Qt::CaseInsensitive);
std::sort(m_data.begin(), m_data.end(), [&collator](const StylesModelData &a, const StylesModelData &b) {
const QString aDisplay = !a.display.isEmpty() ? a.display : a.styleName;
const QString bDisplay = !b.display.isEmpty() ? b.display : b.styleName;
return collator.compare(aDisplay, bDisplay) < 0;
});
endResetModel();
// an item might have been added before the currently selected one
if (oldCount != m_data.count()) {
emit selectedStyleIndexChanged();
}
}