1 /****************************************************************************** 2 * 3 * SPDX-FileCopyrightText: 2008 Szymon Tomasz Stefanek <pragma@kvirc.net> 4 * 5 * SPDX-License-Identifier: GPL-2.0-or-later 6 * 7 *******************************************************************************/ 8 9 #pragma once 10 11 #include "model.h" 12 #include "threadingcache.h" 13 #include <QTimer> 14 #include <config-messagelist.h> 15 16 class QElapsedTimer; 17 namespace MessageList 18 { 19 namespace Core 20 { 21 class ViewItemJob; 22 class ModelInvariantRowMapper; 23 class MessageItemSetManager; 24 class ModelPrivate 25 { 26 public: ModelPrivate(Model * owner)27 explicit ModelPrivate(Model *owner) 28 : q(owner) 29 { 30 } 31 32 void fillView(); 33 34 /** 35 * This is called by MessageList::Manager once in a while. 36 * It is a good place to check if the date has changed and 37 * trigger a view reload. 38 */ 39 void checkIfDateChanged(); 40 41 void viewItemJobStep(); 42 43 /** 44 * Attempt to find the threading parent for the specified message item. 45 * Sets the message threading status to the appropriate value. 46 * 47 * This function performs In-Reply-To and References threading. 48 */ 49 MessageItem *findMessageParent(MessageItem *mi); 50 /** 51 * Attempt to find the threading parent for the specified message item. 52 * Sets the message threading status to the appropriate value. 53 * 54 * This function performs Subject based threading. 55 */ 56 MessageItem *guessMessageParent(MessageItem *mi); 57 58 enum AttachOptions { SkipCacheUpdate = 0, StoreInCache = 1 }; 59 void attachMessageToParent(Item *pParent, MessageItem *mi, AttachOptions attachOptions = StoreInCache); 60 void messageDetachedUpdateParentProperties(Item *oldParent, MessageItem *mi); 61 void attachMessageToGroupHeader(MessageItem *mi); 62 void attachGroup(GroupHeaderItem *ghi); 63 64 enum ViewItemJobResult { ViewItemJobCompleted, ViewItemJobInterrupted }; 65 ViewItemJobResult viewItemJobStepInternal(); 66 ViewItemJobResult viewItemJobStepInternalForJob(ViewItemJob *job, QElapsedTimer elapsedTimer); 67 68 // FIXME: Those look like they should be made virtual in some job class! -> Refactor 69 ViewItemJobResult viewItemJobStepInternalForJobPass1Fill(ViewItemJob *job, QElapsedTimer elapsedTimer); 70 ViewItemJobResult viewItemJobStepInternalForJobPass1Cleanup(ViewItemJob *job, QElapsedTimer elapsedTimer); 71 ViewItemJobResult viewItemJobStepInternalForJobPass1Update(ViewItemJob *job, QElapsedTimer elapsedTimer); 72 ViewItemJobResult viewItemJobStepInternalForJobPass2(ViewItemJob *job, QElapsedTimer elapsedTimer); 73 ViewItemJobResult viewItemJobStepInternalForJobPass3(ViewItemJob *job, QElapsedTimer elapsedTimer); 74 ViewItemJobResult viewItemJobStepInternalForJobPass4(ViewItemJob *job, QElapsedTimer elapsedTimer); 75 ViewItemJobResult viewItemJobStepInternalForJobPass5(ViewItemJob *job, QElapsedTimer elapsedTimer); 76 void clearJobList(); 77 void clearUnassignedMessageLists(); 78 void clearOrphanChildrenHash(); 79 void clearThreadingCacheReferencesIdMD5ToMessageItem(); 80 void clearThreadingCacheMessageSubjectMD5ToMessageItem(); 81 void addMessageToReferencesBasedThreadingCache(MessageItem *mi); 82 void removeMessageFromReferencesBasedThreadingCache(MessageItem *mi); 83 void addMessageToSubjectBasedThreadingCache(MessageItem *mi); 84 void removeMessageFromSubjectBasedThreadingCache(MessageItem *mi); 85 void clear(); 86 /** 87 * Sync the expanded state of the subtree with the specified root. 88 * This will cause the items that are marked with Item::ExpandNeeded to be 89 * expanded also in the view. For optimization purposes the specified root 90 * is assumed to be marked as Item::ExpandNeeded so be sure to check it 91 * before calling this function. 92 */ 93 void syncExpandedStateOfSubtree(Item *root); 94 /** 95 * Save the expanded state of the subtree with the specified root. 96 * The state will be saved in the initialExpandStatus() variable. 97 * For optimization purposes the specified root is assumed to be expanded 98 * and viewable. 99 */ 100 void saveExpandedStateOfSubtree(Item *root); 101 102 #ifdef KDEPIM_FOLDEROPEN_PROFILE 103 // This prints out all the stats we collected 104 void printStatistics(); 105 #endif 106 107 enum PropertyChanges { 108 DateChanged = 1, 109 MaxDateChanged = (1 << 1), 110 ActionItemStatusChanged = (1 << 2), 111 UnreadStatusChanged = (1 << 3), 112 ImportantStatusChanged = (1 << 4), 113 AttachmentStatusChanged = (1 << 5) 114 }; 115 116 /** 117 * Handle the specified property changes in item. Depending on the item 118 * position inside the parent and the types of item and parent the item 119 * might need re-grouping or re-sorting. This function takes care of that. 120 * It is meant to be called from somewhere inside viewItemJobStepInternal() 121 * as it postpones group updates to Pass5. 122 * 123 * parent and item must not be null. propertyChangeMask should not be zero. 124 * 125 * Return true if parent might be affected by the item property changes 126 * and false otherwise. 127 */ 128 bool handleItemPropertyChanges(int propertyChangeMask, Item *parent, Item *item); 129 130 /** 131 * This one checks if the parent of item requires an update due to the 132 * properties of item (that might have been changed or the item might 133 * have been simply added to the parent). The properties 134 * are propagated up to the root item. As optimization we ASSUME that 135 * the item->parent() exists (is non 0) and is NOT the root item. 136 * Be sure to check it before calling this function (it will assert in debug mode anyway). 137 * ... ah... and don't be afraid: this is NOT (directly) recursive :) 138 */ 139 void propagateItemPropertiesToParent(Item *item); 140 141 /** 142 * Recursively applies the current filter to the tree originating at the specified item. 143 * The item is hidden if the filter doesn't match (the item or any children of it) 144 * and this function returns false. 145 * If the filter matches somewhere in the subtree then the item isn't hidden 146 * and this function returns true. 147 * 148 * Assumes that the specified item is viewable. 149 */ 150 bool applyFilterToSubtree(Item *item, const QModelIndex &parentIndex); 151 152 // Slots connected to the underlying StorageModel. 153 154 void slotStorageModelRowsInserted(const QModelIndex &parent, int from, int to); 155 void slotStorageModelRowsRemoved(const QModelIndex &parent, int from, int to); 156 void slotStorageModelDataChanged(const QModelIndex &fromIndex, const QModelIndex &toIndex); 157 void slotStorageModelHeaderDataChanged(Qt::Orientation orientation, int first, int last); 158 void slotStorageModelLayoutChanged(); 159 void slotApplyFilter(); 160 161 Model *const q; 162 163 /** counter to avoid infinite recursions in the setStorageModel() function */ 164 int mRecursionCounterForReset; 165 166 /** 167 * The currently set storage model: shallow pointer. 168 */ 169 StorageModel *mStorageModel = nullptr; 170 171 /** 172 * The currently set aggregation mode: shallow pointer set by Widget 173 */ 174 const Aggregation *mAggregation = nullptr; 175 176 /** 177 * The currently used theme: shallow pointer 178 */ 179 const Theme *mTheme = nullptr; 180 181 /** 182 * The currently used sort order. Pointer not owned by us, but by the Widget. 183 */ 184 const SortOrder *mSortOrder = nullptr; 185 186 /** 187 * The filter to apply on messages. Shallow. Never 0. 188 */ 189 const Filter *mFilter = nullptr; 190 191 /** 192 * The timer involved in breaking the "fill" operation in steps 193 */ 194 QTimer mFillStepTimer; 195 196 /** 197 * Group Key (usually the label) -> GroupHeaderItem, used to quickly find groups, pointers are shallow copies 198 */ 199 QHash<QString, GroupHeaderItem *> mGroupHeaderItemHash; 200 201 /** 202 * Threading cache. 203 * MessageIdMD5 -> MessageItem, pointers are shallow copies 204 */ 205 QHash<QByteArray, MessageItem *> mThreadingCacheMessageIdMD5ToMessageItem; 206 207 /** 208 * Threading cache. 209 * MessageInReplyToIdMD5 -> MessageItem, pointers are shallow copies 210 */ 211 QMultiHash<QByteArray, MessageItem *> mThreadingCacheMessageInReplyToIdMD5ToMessageItem; 212 213 /** 214 * Threading cache. 215 * ReferencesIdMD5 -> MessageItem, pointers are shallow copies 216 */ 217 QHash<QByteArray, QList<MessageItem *> *> mThreadingCacheMessageReferencesIdMD5ToMessageItem; 218 219 /** 220 * Threading cache. 221 * SubjectMD5 -> MessageItem, pointers are shallow copies 222 */ 223 QHash<QByteArray, QList<MessageItem *> *> mThreadingCacheMessageSubjectMD5ToMessageItem; 224 225 /** 226 * List of group headers that either need to be re-sorted or must be removed because empty 227 */ 228 QHash<GroupHeaderItem *, GroupHeaderItem *> mGroupHeadersThatNeedUpdate; 229 230 /** 231 * List of unassigned messages, used to handle threading in two passes, pointers are owned! 232 */ 233 QList<MessageItem *> mUnassignedMessageListForPass2; 234 235 /** 236 * List of unassigned messages, used to handle threading in two passes, pointers are owned! 237 */ 238 QList<MessageItem *> mUnassignedMessageListForPass3; 239 240 /** 241 * List of unassigned messages, used to handle threading in two passes, pointers are owned! 242 */ 243 QList<MessageItem *> mUnassignedMessageListForPass4; 244 245 /** 246 * Hash of orphan children used in Pass1Cleanup. 247 */ 248 QHash<MessageItem *, MessageItem *> mOrphanChildrenHash; 249 250 /** 251 * Pending fill view jobs, pointers are owned 252 */ 253 QList<ViewItemJob *> mViewItemJobs; 254 255 /** 256 * The today's date. Set when the StorageModel is set and thus grouping is performed. 257 * This is used to put the today's messages in the "Today" group, for instance. 258 */ 259 QDate mTodayDate; 260 261 /** 262 * Owned invisible root item, useful to implement algorithms that not need 263 * to handle the special case of parentless items. This is never 0. 264 */ 265 Item *mRootItem = nullptr; 266 267 /** 268 * The view we're attached to. Shallow pointer (the View owns us). 269 */ 270 View *mView = nullptr; 271 272 /** 273 * The time at the current ViewItemJob step started. Used to compute the time we 274 * spent inside this step and eventually jump out on timeout. 275 */ 276 time_t mViewItemJobStepStartTime; 277 278 /** 279 * The timeout for a single ViewItemJob step 280 */ 281 int mViewItemJobStepChunkTimeout; 282 283 /** 284 * The idle time between two ViewItemJob steps 285 */ 286 int mViewItemJobStepIdleInterval; 287 288 /** 289 * The number of messages we process at once in a ViewItemJob step without 290 * checking the timeouts above. 291 */ 292 int mViewItemJobStepMessageCheckCount; 293 294 /** 295 * Our mighty ModelInvariantRowMapper: used to workaround an 296 * issue related to the Model/View architecture. 297 * 298 * \sa ModelInvariantRowMapper 299 */ 300 ModelInvariantRowMapper *mInvariantRowMapper = nullptr; 301 302 /** 303 * The label for the "Today" group item, cached, so we don't translate it multiple times. 304 */ 305 QString mCachedTodayLabel; 306 307 /** 308 * The label for the "Yesterday" group item, cached, so we don't translate it multiple times. 309 */ 310 QString mCachedYesterdayLabel; 311 312 /** 313 * The label for the "Unknown" group item, cached, so we don't translate it multiple times. 314 */ 315 QString mCachedUnknownLabel; 316 317 /** 318 * The label for the "Last Week" group item, cached, so we don't translate it multiple times. 319 */ 320 QString mCachedLastWeekLabel; 321 322 /** 323 * The label for the "Two Weeks Ago" group item, cached, so we don't translate it multiple times. 324 */ 325 QString mCachedTwoWeeksAgoLabel; 326 327 /** 328 * The label for the "Three Weeks Ago" group item, cached, so we don't translate it multiple times. 329 */ 330 QString mCachedThreeWeeksAgoLabel; 331 332 /** 333 * The label for the "Four Weeks Ago" group item, cached, so we don't translate it multiple times. 334 */ 335 QString mCachedFourWeeksAgoLabel; 336 337 /** 338 * The label for the "Five Weeks Ago" group item, cached, so we don't translate it multiple times. 339 */ 340 QString mCachedFiveWeeksAgoLabel; 341 342 /** 343 * Cached bits that we use for fast status checks 344 */ 345 qint32 mCachedWatchedOrIgnoredStatusBits; 346 347 /** 348 * The labels for week days names group items, cached, so we don't query QLocale multiple times. 349 */ 350 QMap<int, QString> mCachedDayNameLabel; 351 352 /* 353 * The labels for month names group items, cached, so we don't query QLocale multiple times. 354 */ 355 QMap<int, QString> mCachedMonthNameLabel; 356 357 /** 358 * Flag signaling a possibly long job batch. This is checked by other 359 * classes and used to display some kind of "please wait" feedback to the user. 360 */ 361 bool mInLengthyJobBatch; 362 363 /** 364 * We need to save the current item before each job step. This is because 365 * our job may cause items to be reparented (thus removed and readded with the current Qt API) 366 * and QTreeView will loose the current setting. We also use this to force the current 367 * to a specific item after a cleanup job. 368 */ 369 Item *mCurrentItemToRestoreAfterViewItemJobStep = nullptr; 370 371 /** 372 * Set to true in the first large loading job. 373 * Reset to false when the job finishes. 374 * 375 * Please note that this is NOT set for later jobs: only for the first (possibly huge) one. 376 */ 377 bool mLoading; 378 379 /** 380 * Pre-selection is the action of automatically selecting a message just after the folder 381 * has finished loading. We may want to select the message that was selected the last 382 * time this folder has been open, or we may want to select the first unread message. 383 * We also may want to do no pre-selection at all (for example, when the user 384 * starts navigating the view before the pre-selection could actually be made 385 * and pre-selecting would confuse him). This member holds the option. 386 * 387 * See also setStorageModel() and abortMessagePreSelection() 388 */ 389 PreSelectionMode mPreSelectionMode; 390 391 // Oldest and newest item while loading the model 392 // Not valid afterwards anymore. Used for pre-selection of the newest/oldest message 393 MessageItem *mOldestItem = nullptr; 394 MessageItem *mNewestItem = nullptr; 395 396 /** 397 * The id of the preselected ;essage is "translated" to a message pointer when it's fetched 398 * from the storage. This message is then selected when it becomes viewable 399 * (so at the end of the job). 0 if we have no message to select. 400 * 401 * See also setStorageModel() and abortMessagePreSelection() 402 */ 403 MessageItem *mLastSelectedMessageInFolder = nullptr; 404 405 /** 406 * The "persistent message item sets" are (guess what?) sets of messages 407 * that can be referenced globally via a persistent id. The MessageItemSetManager 408 * and this class keep the persistent sets coherent: messages that are deleted 409 * are automatically removed from all the sets. 410 * 411 * Users of this class typically create persistent sets when they start 412 * an asynchronous job and they query them back on the way or when the job is terminated. 413 * 414 * So mPersistentSetManager is in fact the manager for the outstanding "user" jobs. 415 * 0 if no jobs are pending (so there are no persistent sets at the moment). 416 */ 417 MessageItemSetManager *mPersistentSetManager = nullptr; 418 419 /** 420 * This pointer is passed to the Item functions that insert children. 421 * When we work with disconnected UI this pointer becomes 0. 422 */ 423 Model *mModelForItemFunctions = nullptr; 424 425 /** 426 * The cached result of StorageModel::containsOutboundMessages(). 427 * We access this property at each incoming message and StorageModel::containsOutboundMessages() is 428 * virtual (so it's always an indirect function call). Caching makes sense. 429 */ 430 bool mStorageModelContainsOutboundMessages; 431 432 /** 433 * Vector of signal-slot connections between StorageModel and us 434 */ 435 QVector<QMetaObject::Connection> mStorageModelConnections; 436 437 /** 438 * Caches child - parent relation based on Akonadi ID and persists the cache 439 * in a file for each Collection. This allows for very fast reconstruction of 440 * threading. 441 */ 442 ThreadingCache mThreadingCache; 443 }; 444 } // namespace Core 445 } // namespace MessageList 446 447