File indexing completed on 2024-06-23 04:42:34

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