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

0001 /*
0002     SPDX-FileCopyrightText: 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2007 Jeremy Whiting <jpwhiting@kde.org>
0004     SPDX-FileCopyrightText: 2016 Olivier Churlaud <olivier@churlaud.com>
0005     SPDX-FileCopyrightText: 2019 Kai Uwe Broulik <kde@privat.broulik.de>
0006 
0007     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0008 */
0009 
0010 #include "colorsmodel.h"
0011 
0012 #include <QCollator>
0013 #include <QDir>
0014 #include <QStandardPaths>
0015 
0016 #include <KColorScheme>
0017 #include <KConfigGroup>
0018 #include <KSharedConfig>
0019 
0020 #include <algorithm>
0021 
0022 #include "colorsapplicator.h"
0023 
0024 using namespace Qt::StringLiterals;
0025 
0026 ColorsModel::ColorsModel(QObject *parent)
0027     : QAbstractListModel(parent)
0028 {
0029 }
0030 
0031 ColorsModel::~ColorsModel() = default;
0032 
0033 int ColorsModel::rowCount(const QModelIndex &parent) const
0034 {
0035     if (parent.isValid()) {
0036         return 0;
0037     }
0038 
0039     return m_data.count();
0040 }
0041 
0042 QVariant ColorsModel::data(const QModelIndex &index, int role) const
0043 {
0044     if (!index.isValid() || index.row() >= m_data.count()) {
0045         return QVariant();
0046     }
0047 
0048     const auto &item = m_data.at(index.row());
0049 
0050     switch (role) {
0051     case Qt::DisplayRole:
0052         return item.display;
0053     case SchemeNameRole:
0054         return item.schemeName;
0055     case PaletteRole:
0056         return item.palette;
0057     case SelectedPaletteRole:
0058         return item.selectedPalette;
0059     case DisabledText:
0060         return item.palette.color(QPalette::Disabled, QPalette::Text);
0061     case ActiveTitleBarBackgroundRole:
0062         return item.activeTitleBarBackground;
0063     case ActiveTitleBarForegroundRole:
0064         return item.activeTitleBarForeground;
0065     case PendingDeletionRole:
0066         return item.pendingDeletion;
0067     case RemovableRole:
0068         return item.removable;
0069     case AccentActiveTitlebarRole:
0070         return item.accentActiveTitlebar;
0071     case Tints:
0072         return item.tints;
0073     case TintFactor:
0074         return item.tintFactor;
0075     }
0076 
0077     return QVariant();
0078 }
0079 
0080 bool ColorsModel::setData(const QModelIndex &index, const QVariant &value, int role)
0081 {
0082     if (!index.isValid() || index.row() >= m_data.count()) {
0083         return false;
0084     }
0085 
0086     if (role == PendingDeletionRole) {
0087         auto &item = m_data[index.row()];
0088 
0089         const bool pendingDeletion = value.toBool();
0090 
0091         if (item.pendingDeletion != pendingDeletion) {
0092             item.pendingDeletion = pendingDeletion;
0093             Q_EMIT dataChanged(index, index, {PendingDeletionRole});
0094 
0095             if (index.row() == selectedSchemeIndex() && pendingDeletion) {
0096                 // move to the next non-pending theme
0097                 const auto nonPending = match(index, PendingDeletionRole, false);
0098                 if (!nonPending.isEmpty()) {
0099                     setSelectedScheme(nonPending.first().data(SchemeNameRole).toString());
0100                 }
0101             }
0102 
0103             Q_EMIT pendingDeletionsChanged();
0104             return true;
0105         }
0106     }
0107 
0108     return false;
0109 }
0110 
0111 QHash<int, QByteArray> ColorsModel::roleNames() const
0112 {
0113     return {
0114         {Qt::DisplayRole, QByteArrayLiteral("display")},
0115         {SchemeNameRole, QByteArrayLiteral("schemeName")},
0116         {PaletteRole, QByteArrayLiteral("palette")},
0117         {SelectedPaletteRole, QByteArrayLiteral("selectedPalette")},
0118         {ActiveTitleBarBackgroundRole, QByteArrayLiteral("activeTitleBarBackground")},
0119         {ActiveTitleBarForegroundRole, QByteArrayLiteral("activeTitleBarForeground")},
0120         {DisabledText, QByteArrayLiteral("disabledText")},
0121         {RemovableRole, QByteArrayLiteral("removable")},
0122         {AccentActiveTitlebarRole, QByteArrayLiteral("accentActiveTitlebar")},
0123         {PendingDeletionRole, QByteArrayLiteral("pendingDeletion")},
0124         {Tints, QByteArrayLiteral("tints")},
0125         {TintFactor, QByteArrayLiteral("tintFactor")},
0126     };
0127 }
0128 
0129 QString ColorsModel::selectedScheme() const
0130 {
0131     return m_selectedScheme;
0132 }
0133 
0134 void ColorsModel::setSelectedScheme(const QString &scheme)
0135 {
0136     if (m_selectedScheme == scheme) {
0137         return;
0138     }
0139 
0140     m_selectedScheme = scheme;
0141 
0142     Q_EMIT selectedSchemeChanged(scheme);
0143     Q_EMIT selectedSchemeIndexChanged();
0144 }
0145 
0146 int ColorsModel::indexOfScheme(const QString &scheme) const
0147 {
0148     auto it = std::find_if(m_data.begin(), m_data.end(), [&scheme](const ColorsModelData &item) {
0149         return item.schemeName == scheme;
0150     });
0151 
0152     if (it != m_data.end()) {
0153         return std::distance(m_data.begin(), it);
0154     }
0155 
0156     return -1;
0157 }
0158 
0159 int ColorsModel::selectedSchemeIndex() const
0160 {
0161     return indexOfScheme(m_selectedScheme);
0162 }
0163 
0164 void ColorsModel::load()
0165 {
0166     beginResetModel();
0167 
0168     const int oldCount = m_data.count();
0169 
0170     m_data.clear();
0171 
0172     QStringList schemeFiles;
0173 
0174     const QStringList schemeDirs =
0175         QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("color-schemes"), QStandardPaths::LocateDirectory);
0176     for (const QString &dir : schemeDirs) {
0177         const QStringList fileNames = QDir(dir).entryList(QStringList{QStringLiteral("*.colors")});
0178         for (const QString &file : fileNames) {
0179             const QString suffixedFileName = QLatin1String("color-schemes/") + file;
0180             // can't use QSet because of the transform below (passing const QString as this argument discards qualifiers)
0181             if (!schemeFiles.contains(suffixedFileName)) {
0182                 schemeFiles.append(suffixedFileName);
0183             }
0184         }
0185     }
0186 
0187     std::transform(schemeFiles.begin(), schemeFiles.end(), schemeFiles.begin(), [](const QString &item) {
0188         return QStandardPaths::locate(QStandardPaths::GenericDataLocation, item);
0189     });
0190 
0191     for (const QString &schemeFile : schemeFiles) {
0192         const QFileInfo fi(schemeFile);
0193         const QString baseName = fi.baseName();
0194 
0195         KSharedConfigPtr config = KSharedConfig::openConfig(schemeFile, KConfig::SimpleConfig);
0196         KConfigGroup group(config, u"General"_s);
0197         const QString name = group.readEntry("Name", baseName);
0198 
0199         const QPalette palette = KColorScheme::createApplicationPalette(config);
0200 
0201         // QPalette has no "selected" state, so there's e.g. no "selected link" color.
0202         const KColorScheme selectedScheme = KColorScheme(QPalette::Normal, KColorScheme::Selection, config);
0203         QPalette selectedPalette = palette;
0204         selectedPalette.setBrush(QPalette::Base, selectedScheme.background());
0205         selectedPalette.setBrush(QPalette::Text, selectedScheme.foreground());
0206         selectedPalette.setBrush(QPalette::AlternateBase, selectedScheme.background(KColorScheme::AlternateBackground));
0207         selectedPalette.setBrush(QPalette::Link, selectedScheme.foreground(KColorScheme::LinkText));
0208         selectedPalette.setBrush(QPalette::LinkVisited, selectedScheme.foreground(KColorScheme::VisitedText));
0209 
0210         QColor activeTitleBarBackground, activeTitleBarForeground;
0211         if (KColorScheme::isColorSetSupported(config, KColorScheme::Header)) {
0212             KColorScheme headerColorScheme(QPalette::Active, KColorScheme::Header, config);
0213             activeTitleBarBackground = headerColorScheme.background().color();
0214             activeTitleBarForeground = headerColorScheme.foreground().color();
0215         } else {
0216             KConfigGroup wmConfig(config, u"WM"_s);
0217             activeTitleBarBackground = wmConfig.readEntry("activeBackground", palette.color(QPalette::Active, QPalette::Highlight));
0218             activeTitleBarForeground = wmConfig.readEntry("activeForeground", palette.color(QPalette::Active, QPalette::HighlightedText));
0219         }
0220 
0221         const bool colorActiveTitleBar = group.readEntry("accentActiveTitlebar", false);
0222 
0223         ColorsModelData item{
0224             name,
0225             baseName,
0226             palette,
0227             selectedPalette,
0228             activeTitleBarBackground,
0229             activeTitleBarForeground,
0230             fi.isWritable(),
0231             colorActiveTitleBar,
0232             false, // pending deletion
0233             group.hasKey("TintFactor"),
0234             group.readEntry<qreal>("TintFactor", DefaultTintFactor),
0235         };
0236 
0237         m_data.append(item);
0238     }
0239 
0240     // Sort case-insensitively
0241     QCollator collator;
0242     collator.setCaseSensitivity(Qt::CaseInsensitive);
0243     std::sort(m_data.begin(), m_data.end(), [&collator](const ColorsModelData &a, const ColorsModelData &b) {
0244         return collator.compare(a.display, b.display) < 0;
0245     });
0246 
0247     endResetModel();
0248 
0249     // an item might have been added before the currently selected one
0250     if (oldCount != m_data.count()) {
0251         Q_EMIT selectedSchemeIndexChanged();
0252     }
0253 }
0254 
0255 QStringList ColorsModel::pendingDeletions() const
0256 {
0257     QStringList pendingDeletions;
0258 
0259     for (const auto &item : m_data) {
0260         if (item.pendingDeletion) {
0261             pendingDeletions.append(item.schemeName);
0262         }
0263     }
0264 
0265     return pendingDeletions;
0266 }
0267 
0268 void ColorsModel::removeItemsPendingDeletion()
0269 {
0270     for (int i = m_data.count() - 1; i >= 0; --i) {
0271         if (m_data.at(i).pendingDeletion) {
0272             beginRemoveRows(QModelIndex(), i, i);
0273             m_data.remove(i);
0274             endRemoveRows();
0275         }
0276     }
0277 }
0278 
0279 #include "moc_colorsmodel.cpp"