File indexing completed on 2025-02-16 04:56:41

0001 // SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
0002 // SPDX-FileCopyrightText: 2022 Claudio Cambra <claudio.cambra@gmail.com>
0003 // SPDX-License-Identifier: GPL-2.0-or-later
0004 
0005 #include "colorproxymodel.h"
0006 
0007 #include <Akonadi/AgentInstance>
0008 #include <Akonadi/AgentManager>
0009 #include <Akonadi/AttributeFactory>
0010 #include <Akonadi/CollectionColorAttribute>
0011 #include <Akonadi/CollectionModifyJob>
0012 #include <Akonadi/CollectionUtils>
0013 #include <Akonadi/EntityDisplayAttribute>
0014 #include <KCalendarCore/Event>
0015 #include <KCalendarCore/Journal>
0016 #include <KCalendarCore/Todo>
0017 #include <KConfigGroup>
0018 #include <KContacts/Addressee>
0019 #include <KContacts/ContactGroup>
0020 #include <KLocalizedString>
0021 #include <KSharedConfig>
0022 #include <QFont>
0023 #include <QRandomGenerator>
0024 
0025 namespace
0026 {
0027 static bool hasCompatibleMimeTypes(const Akonadi::Collection &collection)
0028 {
0029     static QStringList goodMimeTypes;
0030 
0031     if (goodMimeTypes.isEmpty()) {
0032         goodMimeTypes << QStringLiteral("text/calendar") << KCalendarCore::Event::eventMimeType() << KCalendarCore::Todo::todoMimeType()
0033                       << KContacts::Addressee::mimeType() << KContacts::ContactGroup::mimeType() << KCalendarCore::Journal::journalMimeType();
0034     }
0035 
0036     for (int i = 0; i < goodMimeTypes.count(); ++i) {
0037         if (collection.contentMimeTypes().contains(goodMimeTypes.at(i))) {
0038             return true;
0039         }
0040     }
0041 
0042     return false;
0043 }
0044 }
0045 
0046 ColorProxyModel::ColorProxyModel(QObject *parent)
0047     : QSortFilterProxyModel(parent)
0048 {
0049     // Needed to read colorattribute of collections for incidence colors
0050     Akonadi::AttributeFactory::registerAttribute<Akonadi::CollectionColorAttribute>();
0051 }
0052 
0053 QVariant ColorProxyModel::data(const QModelIndex &index, int role) const
0054 {
0055     if (!index.isValid()) {
0056         return {};
0057     }
0058 
0059     if (role == Qt::DecorationRole) {
0060         const Akonadi::Collection collection = Akonadi::CollectionUtils::fromIndex(index);
0061 
0062         if (hasCompatibleMimeTypes(collection)) {
0063             if (collection.hasAttribute<Akonadi::EntityDisplayAttribute>() && !collection.attribute<Akonadi::EntityDisplayAttribute>()->iconName().isEmpty()) {
0064                 return collection.attribute<Akonadi::EntityDisplayAttribute>()->iconName();
0065             }
0066         }
0067     } else if (role == Qt::FontRole) {
0068         const Akonadi::Collection collection = Akonadi::CollectionUtils::fromIndex(index);
0069         if (!collection.contentMimeTypes().isEmpty() && collection.id() == m_standardCollectionId && collection.rights() & Akonadi::Collection::CanCreateItem) {
0070             auto font = qvariant_cast<QFont>(QSortFilterProxyModel::data(index, Qt::FontRole));
0071             font.setBold(true);
0072             return font;
0073         }
0074     } else if (role == Qt::DisplayRole) {
0075         const Akonadi::Collection collection = Akonadi::CollectionUtils::fromIndex(index);
0076         const Akonadi::Collection::Id colId = collection.id();
0077         const Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance(collection.resource());
0078 
0079         if (!instance.isOnline() && !collection.isVirtual()) {
0080             return i18nc("@item this is the default calendar", "%1 (Offline)", collection.displayName());
0081         }
0082         if (colId == m_standardCollectionId) {
0083             return i18nc("@item this is the default calendar", "%1 (Default)", collection.displayName());
0084         }
0085     } else if (role == Qt::BackgroundRole) {
0086         auto color = getCollectionColor(Akonadi::CollectionUtils::fromIndex(index));
0087         // Otherwise QML will get black
0088         if (color.isValid()) {
0089             return color;
0090         } else {
0091             return {};
0092         }
0093     } else if (role == isResource) {
0094         return Akonadi::CollectionUtils::isResource(Akonadi::CollectionUtils::fromIndex(index));
0095     }
0096 
0097     return QSortFilterProxyModel::data(index, role);
0098 }
0099 
0100 Qt::ItemFlags ColorProxyModel::flags(const QModelIndex &index) const
0101 {
0102     return Qt::ItemIsSelectable | QSortFilterProxyModel::flags(index);
0103 }
0104 
0105 QHash<int, QByteArray> ColorProxyModel::roleNames() const
0106 {
0107     QHash<int, QByteArray> roleNames = QSortFilterProxyModel::roleNames();
0108     roleNames[Qt::CheckStateRole] = "checkState";
0109     roleNames[Qt::BackgroundRole] = "collectionColor";
0110     roleNames[isResource] = "isResource";
0111     return roleNames;
0112 }
0113 
0114 QColor ColorProxyModel::getCollectionColor(Akonadi::Collection collection) const
0115 {
0116     const auto id = collection.id();
0117     auto supportsMimeType = collection.contentMimeTypes().contains(QLatin1StringView("application/x-vnd.akonadi.calendar.event"))
0118         || collection.contentMimeTypes().contains(QLatin1StringView("application/x-vnd.akonadi.calendar.todo"))
0119         || collection.contentMimeTypes().contains(QLatin1StringView("application/x-vnd.akonadi.calendar.journal"))
0120         || collection.contentMimeTypes().contains(KContacts::Addressee::mimeType())
0121         || collection.contentMimeTypes().contains(KContacts::ContactGroup::mimeType());
0122 
0123     if (!supportsMimeType) {
0124         return {};
0125     }
0126 
0127     if (colorCache.contains(id)) {
0128         return colorCache[id];
0129     }
0130 
0131     if (collection.hasAttribute<Akonadi::CollectionColorAttribute>()) {
0132         const auto colorAttr = collection.attribute<Akonadi::CollectionColorAttribute>();
0133         if (colorAttr && colorAttr->color().isValid()) {
0134             colorCache[id] = colorAttr->color();
0135             return colorAttr->color();
0136         }
0137     }
0138 
0139     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0140     KConfigGroup resourcesColorsConfig(config, QStringLiteral("Resources Colors"));
0141     const QStringList colorKeyList = resourcesColorsConfig.keyList();
0142 
0143     QColor color;
0144     for (const QString &key : colorKeyList) {
0145         if (key.toLongLong() == id) {
0146             color = resourcesColorsConfig.readEntry(key, QColor("blue"));
0147         }
0148     }
0149 
0150     if (!color.isValid()) {
0151         color.setRgb(QRandomGenerator::global()->bounded(256), QRandomGenerator::global()->bounded(256), QRandomGenerator::global()->bounded(256));
0152         colorCache[id] = color;
0153     }
0154 
0155     auto colorAttr = collection.attribute<Akonadi::CollectionColorAttribute>(Akonadi::Collection::AddIfMissing);
0156     colorAttr->setColor(color);
0157 
0158     auto modifyJob = new Akonadi::CollectionModifyJob(collection);
0159     connect(modifyJob, &Akonadi::CollectionModifyJob::result, this, [](KJob *job) {
0160         if (job->error()) {
0161             qWarning() << "Error occurred modifying collection color: " << job->errorString();
0162         }
0163     });
0164 
0165     return color;
0166 }
0167 
0168 QColor ColorProxyModel::color(Akonadi::Collection::Id collectionId) const
0169 {
0170     return colorCache[collectionId];
0171 }
0172 
0173 void ColorProxyModel::setColor(Akonadi::Collection::Id collectionId, const QColor &color)
0174 {
0175     colorCache[collectionId] = color;
0176 }
0177 
0178 void ColorProxyModel::setStandardCollectionId(Akonadi::Collection::Id standardCollectionId)
0179 {
0180     m_standardCollectionId = standardCollectionId;
0181 }
0182 
0183 #include "moc_colorproxymodel.cpp"