File indexing completed on 2024-05-12 05:12:43

0001 /*
0002     SPDX-FileCopyrightText: 2009 Stephen Kelly <steveire@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "akonadibrowsermodel.h"
0008 
0009 #include <KLocalizedString>
0010 #include <KMime/KMimeMessage>
0011 
0012 #include <KContacts/Addressee>
0013 #include <KContacts/ContactGroup>
0014 
0015 #include <KCalendarCore/Event>
0016 #include <KCalendarCore/Incidence>
0017 
0018 using IncidencePtr = QSharedPointer<KCalendarCore::Incidence>;
0019 
0020 class AkonadiBrowserModel::State
0021 {
0022 public:
0023     virtual ~State() = default;
0024 
0025     QStringList m_collectionHeaders;
0026     QStringList m_itemHeaders;
0027 
0028     [[nodiscard]] virtual QVariant entityData(const Item &item, int column, int role) const = 0;
0029 };
0030 
0031 class GenericState : public AkonadiBrowserModel::State
0032 {
0033 public:
0034     GenericState()
0035     {
0036         m_collectionHeaders << QStringLiteral("Collection");
0037         m_itemHeaders << QStringLiteral("Id") << QStringLiteral("Remote Id") << QStringLiteral("GID") << QStringLiteral("MimeType");
0038     }
0039 
0040     ~GenericState() override = default;
0041 
0042     enum Columns { IdColumn = 0, RemoteIdColumn = 1, GIDColumn = 2, MimeTypeColumn = 3 };
0043 
0044     [[nodiscard]] QVariant entityData(const Item &item, int column, int role) const override
0045     {
0046         if (Qt::DisplayRole != role) {
0047             return {};
0048         }
0049 
0050         switch (column) {
0051         case IdColumn:
0052             return item.id();
0053         case RemoteIdColumn:
0054             return item.remoteId();
0055         case GIDColumn:
0056             return item.gid();
0057         case MimeTypeColumn:
0058             return item.mimeType();
0059         }
0060         return {};
0061     }
0062 };
0063 
0064 class MailState : public AkonadiBrowserModel::State
0065 {
0066 public:
0067     MailState()
0068     {
0069         m_collectionHeaders << i18n("Collection");
0070         m_itemHeaders << i18n("Subject") << i18n("Sender") << i18n("Date");
0071     }
0072 
0073     ~MailState() override = default;
0074 
0075     [[nodiscard]] QVariant entityData(const Item &item, int column, int role) const override
0076     {
0077         if (Qt::DisplayRole != role) {
0078             return {};
0079         }
0080 
0081         if (!item.hasPayload<KMime::Message::Ptr>()) {
0082             return {};
0083         }
0084         const auto mail = item.payload<KMime::Message::Ptr>();
0085 
0086         // NOTE: remember to update AkonadiBrowserSortModel::lessThan if you insert/move columns
0087         switch (column) {
0088         case 0:
0089             if (mail->subject()) {
0090                 return mail->subject()->asUnicodeString();
0091             } else {
0092                 return QStringLiteral("(No subject)");
0093             }
0094         case 1:
0095             if (mail->from()) {
0096                 return mail->from()->asUnicodeString();
0097             } else {
0098                 return QString();
0099             }
0100         case 2:
0101             if (mail->date()) {
0102                 return mail->date()->asUnicodeString();
0103             } else {
0104                 return QString();
0105             }
0106         }
0107 
0108         return {};
0109     }
0110 };
0111 
0112 class ContactsState : public AkonadiBrowserModel::State
0113 {
0114 public:
0115     ContactsState()
0116     {
0117         m_collectionHeaders << QStringLiteral("Collection");
0118         m_itemHeaders << QStringLiteral("Given Name") << QStringLiteral("Family Name") << QStringLiteral("Email");
0119     }
0120 
0121     ~ContactsState() override = default;
0122 
0123     [[nodiscard]] QVariant entityData(const Item &item, int column, int role) const override
0124     {
0125         if (Qt::DisplayRole != role) {
0126             return {};
0127         }
0128 
0129         if (!item.hasPayload<KContacts::Addressee>() && !item.hasPayload<KContacts::ContactGroup>()) {
0130             return {};
0131         }
0132 
0133         if (item.hasPayload<KContacts::Addressee>()) {
0134             const auto addr = item.payload<KContacts::Addressee>();
0135 
0136             switch (column) {
0137             case 0:
0138                 return addr.givenName();
0139             case 1:
0140                 return addr.familyName();
0141             case 2:
0142                 return addr.preferredEmail();
0143             }
0144             return {};
0145         }
0146         if (item.hasPayload<KContacts::ContactGroup>()) {
0147             switch (column) {
0148             case 0:
0149                 const auto group = item.payload<KContacts::ContactGroup>();
0150                 return group.name();
0151             }
0152             return {};
0153         }
0154         return {};
0155     }
0156 };
0157 
0158 class CalendarState : public AkonadiBrowserModel::State
0159 {
0160 public:
0161     CalendarState()
0162     {
0163         m_collectionHeaders << QStringLiteral("Collection");
0164         m_itemHeaders << QStringLiteral("UID") << QStringLiteral("Summary") << QStringLiteral("DateTime start") << QStringLiteral("DateTime End")
0165                       << QStringLiteral("Type");
0166     }
0167 
0168     ~CalendarState() override = default;
0169 
0170     [[nodiscard]] QVariant entityData(const Item &item, int column, int role) const override
0171     {
0172         if (Qt::DisplayRole != role) {
0173             return {};
0174         }
0175 
0176         if (!item.hasPayload<IncidencePtr>()) {
0177             return {};
0178         }
0179         const auto incidence = item.payload<IncidencePtr>();
0180         // NOTE: remember to update AkonadiBrowserSortModel::lessThan if you insert/move columns
0181         switch (column) {
0182         case 0:
0183             return incidence->uid();
0184         case 1:
0185             return incidence->summary();
0186         case 2:
0187             return incidence->dtStart().toString();
0188         case 3:
0189             return incidence->dateTime(KCalendarCore::Incidence::RoleEnd).toString();
0190         case 4:
0191             return incidence->typeStr();
0192         default:
0193             break;
0194         }
0195         return {};
0196     }
0197 };
0198 
0199 AkonadiBrowserModel::AkonadiBrowserModel(ChangeRecorder *monitor, QObject *parent)
0200     : EntityTreeModel(monitor, parent)
0201 {
0202     m_genericState = new GenericState();
0203     m_mailState = new MailState();
0204     m_contactsState = new ContactsState();
0205     m_calendarState = new CalendarState();
0206 
0207     m_currentState = m_genericState;
0208 }
0209 
0210 AkonadiBrowserModel::~AkonadiBrowserModel()
0211 {
0212     delete m_genericState;
0213     delete m_mailState;
0214     delete m_contactsState;
0215     delete m_calendarState;
0216 }
0217 
0218 QVariant AkonadiBrowserModel::entityData(const Item &item, int column, int role) const
0219 {
0220     QVariant var = m_currentState->entityData(item, column, role);
0221     if (!var.isValid()) {
0222         if (column < 1) {
0223             return EntityTreeModel::entityData(item, column, role);
0224         }
0225         return QString();
0226     }
0227 
0228     return var;
0229 }
0230 
0231 QVariant AkonadiBrowserModel::entityData(const Akonadi::Collection &collection, int column, int role) const
0232 {
0233     return Akonadi::EntityTreeModel::entityData(collection, column, role);
0234 }
0235 
0236 int AkonadiBrowserModel::entityColumnCount(HeaderGroup headerGroup) const
0237 {
0238     if (ItemListHeaders == headerGroup) {
0239         return m_currentState->m_itemHeaders.size();
0240     }
0241 
0242     if (CollectionTreeHeaders == headerGroup) {
0243         return m_currentState->m_collectionHeaders.size();
0244     }
0245     // Practically, this should never happen.
0246     return EntityTreeModel::entityColumnCount(headerGroup);
0247 }
0248 
0249 QVariant AkonadiBrowserModel::entityHeaderData(int section, Qt::Orientation orientation, int role, HeaderGroup headerGroup) const
0250 {
0251     if (section < 0) {
0252         return {};
0253     }
0254 
0255     if (orientation == Qt::Vertical) {
0256         return EntityTreeModel::entityHeaderData(section, orientation, role, headerGroup);
0257     }
0258 
0259     if (headerGroup == EntityTreeModel::CollectionTreeHeaders) {
0260         if (role == Qt::DisplayRole) {
0261             if (section >= m_currentState->m_collectionHeaders.size()) {
0262                 return {};
0263             }
0264             return m_currentState->m_collectionHeaders.at(section);
0265         }
0266     } else if (headerGroup == EntityTreeModel::ItemListHeaders) {
0267         if (role == Qt::DisplayRole) {
0268             if (section >= m_currentState->m_itemHeaders.size()) {
0269                 return {};
0270             }
0271             return m_currentState->m_itemHeaders.at(section);
0272         }
0273     }
0274     return EntityTreeModel::entityHeaderData(section, orientation, role, headerGroup);
0275 }
0276 
0277 AkonadiBrowserModel::ItemDisplayMode AkonadiBrowserModel::itemDisplayMode() const
0278 {
0279     return m_itemDisplayMode;
0280 }
0281 
0282 void AkonadiBrowserModel::setItemDisplayMode(AkonadiBrowserModel::ItemDisplayMode itemDisplayMode)
0283 {
0284     const int oldColumnCount = columnCount();
0285     m_itemDisplayMode = itemDisplayMode;
0286     AkonadiBrowserModel::State *newState = nullptr;
0287     switch (itemDisplayMode) {
0288     case MailMode:
0289         newState = m_mailState;
0290         break;
0291     case ContactsMode:
0292         newState = m_contactsState;
0293         break;
0294     case CalendarMode:
0295         newState = m_calendarState;
0296         break;
0297     case GenericMode:
0298     default:
0299         newState = m_genericState;
0300         break;
0301     }
0302     const int newColumnCount = qMax(newState->m_collectionHeaders.count(), newState->m_itemHeaders.count());
0303 
0304     // qCDebug(AKONADICONSOLE_LOG) << "column count changed from" << oldColumnCount << "to" << newColumnCount;
0305     if (newColumnCount > oldColumnCount) {
0306         beginInsertColumns(QModelIndex(), oldColumnCount, newColumnCount - 1);
0307         m_currentState = newState;
0308         endInsertColumns();
0309     } else if (newColumnCount < oldColumnCount) {
0310         beginRemoveColumns(QModelIndex(), newColumnCount, oldColumnCount - 1);
0311         m_currentState = newState;
0312         endRemoveColumns();
0313     } else {
0314         m_currentState = newState;
0315     }
0316     headerDataChanged(Qt::Horizontal, 0, newColumnCount - 1);
0317 
0318     // The above is not enough to see the new headers, because EntityMimeTypeFilterModel gets column count and headers from our data,
0319     // and doesn't listen to dataChanged/headerDataChanged...
0320     columnsChanged();
0321 }
0322 
0323 AkonadiBrowserSortModel::AkonadiBrowserSortModel(AkonadiBrowserModel *model, QObject *parent)
0324     : QSortFilterProxyModel(parent)
0325     , mBrowserModel(model)
0326 {
0327 }
0328 
0329 AkonadiBrowserSortModel::~AkonadiBrowserSortModel() = default;
0330 
0331 bool AkonadiBrowserSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
0332 {
0333     if (mBrowserModel->itemDisplayMode() == AkonadiBrowserModel::CalendarMode) {
0334         if (left.column() == 2 || left.column() == 3) {
0335             const Item leftItem = left.data(EntityTreeModel::ItemRole).value<Item>();
0336             const Item rightItem = right.data(EntityTreeModel::ItemRole).value<Item>();
0337             if (!leftItem.hasPayload<IncidencePtr>() || !rightItem.hasPayload<IncidencePtr>()) {
0338                 return false;
0339             }
0340             const auto leftInc = leftItem.payload<IncidencePtr>();
0341             const auto rightInc = rightItem.payload<IncidencePtr>();
0342 
0343             if (left.column() == 1) {
0344                 return leftInc->dtStart() < rightInc->dtStart();
0345             } else if (left.column() == 2) {
0346                 return leftInc->dateTime(KCalendarCore::IncidenceBase::RoleEnd) < rightInc->dateTime(KCalendarCore::IncidenceBase::RoleEnd);
0347             }
0348         }
0349     } else if (mBrowserModel->itemDisplayMode() == AkonadiBrowserModel::MailMode) {
0350         if (left.column() == 2) {
0351             const Item leftItem = left.data(EntityTreeModel::ItemRole).value<Item>();
0352             const Item rightItem = right.data(EntityTreeModel::ItemRole).value<Item>();
0353             if (!leftItem.hasPayload<KMime::Message::Ptr>() || !rightItem.hasPayload<KMime::Message::Ptr>()) {
0354                 return false;
0355             }
0356             const auto leftMail = leftItem.payload<KMime::Message::Ptr>();
0357             const auto rightMail = rightItem.payload<KMime::Message::Ptr>();
0358             const KMime::Headers::Date *ldate = leftMail->date(false);
0359             const KMime::Headers::Date *rdate = rightMail->date(false);
0360             if (ldate && rdate) {
0361                 return ldate->dateTime() < rdate->dateTime();
0362             } else {
0363                 return false;
0364             }
0365         }
0366     }
0367 
0368     return QSortFilterProxyModel::lessThan(left, right);
0369 }
0370 
0371 #include "moc_akonadibrowsermodel.cpp"