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"