28#include "core/model.h"
29#include "core/filter.h"
30#include "core/groupheaderitem.h"
31#include "core/item_p.h"
32#include "core/manager.h"
33#include "core/messageitem.h"
34#include "core/messageitemsetmanager.h"
35#include "core/model_p.h"
36#include "core/modelinvariantrowmapper.h"
37#include "core/storagemodelbase.h"
38#include "core/theme.h"
40#include "messagelist_debug.h"
41#include <config-messagelist.h>
43#include "MessageCore/StringUtil"
44#include <Akonadi/Item>
45#include <Akonadi/MessageStatus>
46#include <MessageCore/DateFormatter>
48#include <KLocalizedString>
50#include <QApplication>
52#include <QElapsedTimer>
61using namespace std::chrono_literals;
67Q_GLOBAL_STATIC(
QTimer, _k_heartBeatTimer)
186 int mMessageCheckCount;
200 ViewItemJob(
int startIndex,
int endIndex,
int chunkTimeout,
int idleInterval,
int messageCheckCount,
bool disconnectUI =
false)
201 : mStartIndex(startIndex)
202 , mCurrentIndex(startIndex)
203 , mEndIndex(endIndex)
204 , mInvariantIndexList(nullptr)
205 , mChunkTimeout(chunkTimeout)
206 , mIdleInterval(idleInterval)
207 , mMessageCheckCount(messageCheckCount)
208 , mCurrentPass(Pass1Fill)
209 , mDisconnectUI(disconnectUI)
219 , mEndIndex(invariantIndexList->count() - 1)
220 , mInvariantIndexList(invariantIndexList)
221 , mChunkTimeout(chunkTimeout)
222 , mIdleInterval(idleInterval)
223 , mMessageCheckCount(messageCheckCount)
225 , mDisconnectUI(false)
231 delete mInvariantIndexList;
235 [[nodiscard]]
int startIndex()
const
240 void setStartIndex(
int startIndex)
242 mStartIndex = startIndex;
243 mCurrentIndex = startIndex;
246 [[nodiscard]]
int currentIndex()
const
248 return mCurrentIndex;
251 void setCurrentIndex(
int currentIndex)
253 mCurrentIndex = currentIndex;
256 [[nodiscard]]
int endIndex()
const
261 void setEndIndex(
int endIndex)
263 mEndIndex = endIndex;
266 [[nodiscard]] Pass currentPass()
const
271 void setCurrentPass(Pass pass)
276 [[nodiscard]]
int idleInterval()
const
278 return mIdleInterval;
281 [[nodiscard]]
int chunkTimeout()
const
283 return mChunkTimeout;
286 [[nodiscard]]
int messageCheckCount()
const
288 return mMessageCheckCount;
293 return mInvariantIndexList;
296 [[nodiscard]]
bool disconnectUI()
const
298 return mDisconnectUI;
308 , d(new ModelPrivate(this))
310 d->mRecursionCounterForReset = 0;
311 d->mStorageModel =
nullptr;
313 d->mAggregation =
nullptr;
315 d->mSortOrder =
nullptr;
316 d->mFilter =
nullptr;
317 d->mPersistentSetManager =
nullptr;
318 d->mInLengthyJobBatch =
false;
319 d->mLastSelectedMessageInFolder =
nullptr;
323 d->mRootItem->setViewable(
nullptr,
true);
325 d->mFillStepTimer.setSingleShot(
true);
327 d->mModelForItemFunctions =
this;
329 d->viewItemJobStep();
332 d->mCachedTodayLabel =
i18n(
"Today");
333 d->mCachedYesterdayLabel =
i18n(
"Yesterday");
334 d->mCachedUnknownLabel =
i18nc(
"Unknown date",
"Unknown");
335 d->mCachedLastWeekLabel =
i18n(
"Last Week");
336 d->mCachedTwoWeeksAgoLabel =
i18n(
"Two Weeks Ago");
337 d->mCachedThreeWeeksAgoLabel =
i18n(
"Three Weeks Ago");
338 d->mCachedFourWeeksAgoLabel =
i18n(
"Four Weeks Ago");
339 d->mCachedFiveWeeksAgoLabel =
i18n(
"Five Weeks Ago");
344 d->checkIfDateChanged();
347 if (!_k_heartBeatTimer->isActive()) {
348 _k_heartBeatTimer->start(1min);
357 d->mOldestItem =
nullptr;
358 d->mNewestItem =
nullptr;
359 d->clearUnassignedMessageLists();
360 d->clearOrphanChildrenHash();
361 d->clearThreadingCacheReferencesIdMD5ToMessageItem();
362 d->clearThreadingCacheMessageSubjectMD5ToMessageItem();
363 delete d->mPersistentSetManager;
366 delete d->mInvariantRowMapper;
372 d->mAggregation = aggregation;
388 return d->mSortOrder;
396 connect(d->mFilter, &Filter::finished,
this, [
this]() {
397 d->slotApplyFilter();
401 d->slotApplyFilter();
404void ModelPrivate::slotApplyFilter()
406 auto childList = mRootItem->childItems();
414 for (
const auto child : std::as_const(*childList)) {
415 applyFilterToSubtree(child, idx);
421bool ModelPrivate::applyFilterToSubtree(
Item *item,
const QModelIndex &parentIndex)
426 if (!mModelForItemFunctions) {
427 qCWarning(MESSAGELIST_LOG) <<
"Cannot apply filter, the UI must be not disconnected.";
431 Q_ASSERT(item->isViewable());
437 bool childrenMatch =
false;
442 for (
const auto child : std::as_const(*childList)) {
443 if (applyFilterToSubtree(child, thisIndex)) {
444 childrenMatch =
true;
450 mView->setRowHidden(thisIndex.
row(), parentIndex,
false);
455 mView->setRowHidden(thisIndex.
row(), parentIndex,
false);
457 if (!mView->isExpanded(thisIndex)) {
458 mView->expand(thisIndex);
465 mView->setRowHidden(thisIndex.
row(), parentIndex,
false);
471 mView->setRowHidden(thisIndex.
row(), parentIndex,
true);
476int Model::columnCount(
const QModelIndex &parent)
const
481 if (
parent.column() > 0) {
484 return d->mTheme->columns().count();
516 return QStringLiteral(
"message/rfc822");
524 return mItem->accessibleText(d->mTheme, index.
column());
529 auto hItem =
static_cast<GroupHeaderItem *
>(item);
530 return hItem->label();
545 auto column = d->mTheme->column(section);
550 if (d->mStorageModel && column->isSenderOrReceiver() && (role ==
Qt::DisplayRole)) {
551 if (d->mStorageModelContainsOutboundMessages) {
557 const bool columnPixmapEmpty(column->pixmapName().isEmpty());
571 if (!d->mModelForItemFunctions) {
579 auto par = item->
parent();
581 if (item != d->mRootItem) {
587 const int index = par->indexOfChildItem(item);
596 if (!d->mModelForItemFunctions) {
600#ifdef READD_THIS_IF_YOU_WANT_TO_PASS_MODEL_TEST
608 item =
static_cast<const Item *
>(
parent.internalPointer());
616 if (
parent.column() > 0) {
620 Item *child = item->childItem(row);
629 Q_ASSERT(d->mModelForItemFunctions);
638 auto par = item->
parent();
643 return index(par, 0);
646int Model::rowCount(
const QModelIndex &parent)
const
648 if (!d->mModelForItemFunctions) {
654 item =
static_cast<const Item *
>(
parent.internalPointer());
662 if (!item->isViewable()) {
666 return item->childItemCount();
669class RecursionPreventer
672 RecursionPreventer(
int &counter)
678 ~RecursionPreventer()
683 [[nodiscard]]
bool isRecursive()
const
694 return d->mStorageModel;
697void ModelPrivate::clear()
699 q->beginResetModel();
700 if (mFillStepTimer.isActive()) {
701 mFillStepTimer.stop();
705 mPreSelectionMode = PreSelectNone;
706 mLastSelectedMessageInFolder =
nullptr;
707 mOldestItem =
nullptr;
708 mNewestItem =
nullptr;
712 mInvariantRowMapper->modelReset();
715 clearUnassignedMessageLists();
716 clearOrphanChildrenHash();
717 mGroupHeaderItemHash.clear();
718 mGroupHeadersThatNeedUpdate.clear();
719 mThreadingCacheMessageIdMD5ToMessageItem.clear();
720 mThreadingCacheMessageInReplyToIdMD5ToMessageItem.clear();
721 clearThreadingCacheReferencesIdMD5ToMessageItem();
722 clearThreadingCacheMessageSubjectMD5ToMessageItem();
723 mViewItemJobStepChunkTimeout = 100;
724 mViewItemJobStepIdleInterval = 10;
725 mViewItemJobStepMessageCheckCount = 10;
726 delete mPersistentSetManager;
727 mPersistentSetManager =
nullptr;
728 mCurrentItemToRestoreAfterViewItemJobStep =
nullptr;
734 mRootItem->killAllChildItems();
739 mView->selectionModel()->clearSelection();
746 RecursionPreventer preventer(d->mRecursionCounterForReset);
747 if (preventer.isRecursive()) {
753 if (d->mStorageModel) {
755 std::for_each(d->mStorageModelConnections.cbegin(), d->mStorageModelConnections.cend(), [](
const QMetaObject::Connection &c) ->
bool {
756 return QObject::disconnect(c);
758 d->mStorageModelConnections.clear();
761 const bool isReload = (d->mStorageModel ==
storageModel);
764 if (!d->mStorageModel) {
772 if (d->mThreadingCache.isEnabled() && !isReload) {
773 d->mThreadingCache.save();
776 qCDebug(MESSAGELIST_LOG) <<
"Identical folder reloaded, not saving old threading cache";
778 qCDebug(MESSAGELIST_LOG) <<
"Threading disabled in previous folder, not saving threading cache";
784 d->mThreadingCache.setEnabled(
true);
785 d->mThreadingCache.load(d->mStorageModel->id(), d->mAggregation);
789 d->mThreadingCache.setEnabled(
false);
790 qCDebug(MESSAGELIST_LOG) <<
"Threading disabled in folder" << d->mStorageModel->id() <<
", not using threading cache";
793 d->mPreSelectionMode = preSelectionMode;
794 d->mStorageModelContainsOutboundMessages = d->mStorageModel->containsOutboundMessages();
796 d->mStorageModelConnections = {
connect(d->mStorageModel,
800 d->slotStorageModelRowsInserted(parent, first, last);
806 d->slotStorageModelRowsRemoved(parent, first, last);
812 d->slotStorageModelLayoutChanged();
818 d->slotStorageModelLayoutChanged();
824 d->slotStorageModelDataChanged(topLeft, bottomRight);
827 d->slotStorageModelHeaderDataChanged(orientation, first, last);
830 if (d->mStorageModel->rowCount() == 0) {
898 bool canDoFirstSmallChunkWithDisconnectedUI = !d->mFilter;
901 bool canDoJobWithDisconnectedUI =
910 (d->mStorageModel->initialUnreadRowCountGuess() < 1000)));
912 switch (d->mAggregation->fillViewStrategy()) {
915 if ((!canDoJobWithDisconnectedUI) && (d->mStorageModel->rowCount() > 3000)) {
919 new ViewItemJob(d->mStorageModel->rowCount() - 1000, d->mStorageModel->rowCount() - 1, 200, 20, 100, canDoFirstSmallChunkWithDisconnectedUI);
920 d->mViewItemJobs.append(job1);
923 auto job2 =
new ViewItemJob(0, d->mStorageModel->rowCount() - 1001, 100, 50, 10,
false);
924 d->mViewItemJobs.append(job2);
932 auto job =
new ViewItemJob(0, d->mStorageModel->rowCount() - 1, 150, 30, 30, canDoJobWithDisconnectedUI);
933 d->mViewItemJobs.append(job);
938 if ((!canDoJobWithDisconnectedUI) && (d->mStorageModel->rowCount() > 3000)) {
941 new ViewItemJob(d->mStorageModel->rowCount() - 1000, d->mStorageModel->rowCount() - 1, 250, 0, 100, canDoFirstSmallChunkWithDisconnectedUI);
942 d->mViewItemJobs.append(job1);
943 auto job2 =
new ViewItemJob(0, d->mStorageModel->rowCount() - 1001, 200, 0, 10,
false);
944 d->mViewItemJobs.append(job2);
948 auto job =
new ViewItemJob(0, d->mStorageModel->rowCount() - 1, 250, 0, 100, canDoJobWithDisconnectedUI);
949 d->mViewItemJobs.append(job);
954 auto job =
new ViewItemJob(0, d->mStorageModel->rowCount() - 1, 60000, 0, 100000, canDoJobWithDisconnectedUI);
955 d->mViewItemJobs.append(job);
959 qCWarning(MESSAGELIST_LOG) <<
"Unrecognized fill view strategy";
966 d->viewItemJobStep();
969void ModelPrivate::checkIfDateChanged()
983 if (!mStorageModel) {
991 if (!mViewItemJobs.isEmpty()) {
1000 Manager::instance()->dateFormatter()->invalidateReferenceDate();
1001 q->setStorageModel(mStorageModel, PreSelectLastSelected);
1006 d->mPreSelectionMode = preSelect;
1007 d->mLastSelectedMessageInFolder =
nullptr;
1021void ModelPrivate::clearUnassignedMessageLists()
1038 Q_ASSERT((mOldestItem ==
nullptr) && (mNewestItem ==
nullptr));
1040 if (!mUnassignedMessageListForPass2.isEmpty()) {
1051 for (
const auto mi : std::as_const(mUnassignedMessageListForPass2)) {
1052 if (!mi->parent()) {
1057 for (
const auto mi : std::as_const(parentless)) {
1061 mUnassignedMessageListForPass2.
clear();
1063 mUnassignedMessageListForPass3.clear();
1064 mUnassignedMessageListForPass4.clear();
1070 if (!mUnassignedMessageListForPass3.isEmpty()) {
1077 if (!mUnassignedMessageListForPass4.isEmpty()) {
1081 for (
const auto mi : std::as_const(mUnassignedMessageListForPass3)) {
1082 if (!mi->parent()) {
1083 itemsToDelete.
insert(mi);
1086 for (
const auto mi : std::as_const(mUnassignedMessageListForPass4)) {
1087 if (!mi->parent()) {
1088 itemsToDelete.
insert(mi);
1091 for (
const auto mi : std::as_const(itemsToDelete)) {
1095 mUnassignedMessageListForPass3.
clear();
1096 mUnassignedMessageListForPass4.clear();
1103 for (
const auto mi : std::as_const(mUnassignedMessageListForPass3)) {
1104 if (!mi->parent()) {
1108 for (
const auto mi : std::as_const(parentless)) {
1112 mUnassignedMessageListForPass3.
clear();
1117 if (!mUnassignedMessageListForPass4.isEmpty()) {
1122 for (
const auto mi : std::as_const(mUnassignedMessageListForPass4)) {
1123 if (!mi->parent()) {
1127 for (
const auto mi : std::as_const(parentless)) {
1131 mUnassignedMessageListForPass4.
clear();
1136void ModelPrivate::clearThreadingCacheReferencesIdMD5ToMessageItem()
1138 qDeleteAll(mThreadingCacheMessageReferencesIdMD5ToMessageItem);
1139 mThreadingCacheMessageReferencesIdMD5ToMessageItem.clear();
1142void ModelPrivate::clearThreadingCacheMessageSubjectMD5ToMessageItem()
1144 qDeleteAll(mThreadingCacheMessageSubjectMD5ToMessageItem);
1145 mThreadingCacheMessageSubjectMD5ToMessageItem.clear();
1148void ModelPrivate::clearOrphanChildrenHash()
1150 qDeleteAll(mOrphanChildrenHash);
1151 mOrphanChildrenHash.clear();
1154void ModelPrivate::clearJobList()
1156 if (mViewItemJobs.isEmpty()) {
1160 if (mInLengthyJobBatch) {
1161 mInLengthyJobBatch =
false;
1164 qDeleteAll(mViewItemJobs);
1165 mViewItemJobs.clear();
1167 mModelForItemFunctions = q;
1170void ModelPrivate::attachGroup(GroupHeaderItem *ghi)
1173 if (((ghi)->childItemCount() > 0)
1174 && (ghi)->isViewable()
1175 && mModelForItemFunctions
1176 && mView->isExpanded(q->index(ghi, 0))
1178 saveExpandedStateOfSubtree(ghi);
1191#define INSERT_GROUP_WITH_COMPARATOR(_ItemComparator) \
1192 switch (mSortOrder->groupSortDirection()) { \
1193 case SortOrder::Ascending: \
1194 mRootItem->d_ptr->insertChildItem<_ItemComparator, true>(mModelForItemFunctions, ghi); \
1196 case SortOrder::Descending: \
1197 mRootItem->d_ptr->insertChildItem<_ItemComparator, false>(mModelForItemFunctions, ghi); \
1200 mRootItem->appendChildItem(mModelForItemFunctions, ghi); \
1204 switch (mSortOrder->groupSorting()) {
1206 INSERT_GROUP_WITH_COMPARATOR(ItemDateComparator)
1209 INSERT_GROUP_WITH_COMPARATOR(ItemMaxDateComparator)
1212 INSERT_GROUP_WITH_COMPARATOR(ItemSenderOrReceiverComparator)
1215 INSERT_GROUP_WITH_COMPARATOR(ItemSenderComparator)
1218 INSERT_GROUP_WITH_COMPARATOR(ItemReceiverComparator)
1221 mRootItem->appendChildItem(mModelForItemFunctions, ghi);
1224 mRootItem->appendChildItem(mModelForItemFunctions, ghi);
1230 if (mModelForItemFunctions) {
1231 syncExpandedStateOfSubtree(ghi);
1238 Q_ASSERT(mModelForItemFunctions);
1244void ModelPrivate::saveExpandedStateOfSubtree(
Item *root)
1246 Q_ASSERT(mModelForItemFunctions);
1255 for (
const auto mi : std::as_const(*children)) {
1256 if (mi->childItemCount() > 0
1258 && mView->isExpanded(q->index(mi, 0))) {
1259 saveExpandedStateOfSubtree(mi);
1264void ModelPrivate::syncExpandedStateOfSubtree(
Item *root)
1266 Q_ASSERT(mModelForItemFunctions);
1284 for (
const auto mi : std::as_const(*children)) {
1286 if (mi->childItemCount() > 0) {
1287 syncExpandedStateOfSubtree(mi);
1293void ModelPrivate::attachMessageToGroupHeader(
MessageItem *mi)
1299 switch (mAggregation->grouping()) {
1312 const int daysInWeek = 7;
1313 if (dDate.
isValid() && mTodayDate.isValid()) {
1314 daysAgo = dDate.
daysTo(mTodayDate);
1318 || (
static_cast<uint
>(date) ==
static_cast<uint
>(-1))) {
1319 groupLabel = mCachedUnknownLabel;
1320 }
else if (daysAgo == 0) {
1321 groupLabel = mCachedTodayLabel;
1322 }
else if (daysAgo == 1) {
1323 groupLabel = mCachedYesterdayLabel;
1324 }
else if (daysAgo > 1 && daysAgo < daysInWeek) {
1325 auto dayName = mCachedDayNameLabel.find(dDate.
dayOfWeek());
1326 if (dayName == mCachedDayNameLabel.end()) {
1329 groupLabel = *dayName;
1332 }
else if (dDate.
month() == mTodayDate.month()
1333 && dDate.
year() == mTodayDate.year()) {
1334 const int startOfWeekDaysAgo = (daysInWeek + mTodayDate.dayOfWeek() -
QLocale().firstDayOfWeek()) % daysInWeek;
1335 const int weeksAgo = ((daysAgo - startOfWeekDaysAgo) / daysInWeek) + 1;
1341 groupLabel = mCachedLastWeekLabel;
1344 groupLabel = mCachedTwoWeeksAgoLabel;
1347 groupLabel = mCachedThreeWeeksAgoLabel;
1350 groupLabel = mCachedFourWeeksAgoLabel;
1353 groupLabel = mCachedFiveWeeksAgoLabel;
1356 groupLabel = mCachedUnknownLabel;
1358 }
else if (dDate.
year() == mTodayDate.year()) {
1359 auto monthName = mCachedMonthNameLabel.find(dDate.
month());
1360 if (monthName == mCachedMonthNameLabel.end()) {
1363 groupLabel = *monthName;
1365 auto monthName = mCachedMonthNameLabel.find(dDate.
month());
1366 if (monthName == mCachedMonthNameLabel.end()) {
1369 groupLabel =
i18nc(
"Message Aggregation Group Header: Month name and Year number",
1394 attachMessageToParent(mRootItem, mi);
1399 attachMessageToParent(mRootItem, mi);
1403 GroupHeaderItem *ghi;
1405 ghi = mGroupHeaderItemHash.value(groupLabel,
nullptr);
1409 ghi =
new GroupHeaderItem(groupLabel);
1412 switch (mAggregation->groupExpandPolicy()) {
1422 if (mViewItemJobStepStartTime > ghi->
date()) {
1423 if ((mViewItemJobStepStartTime - ghi->
date()) < (3600 * 72)) {
1427 if ((ghi->
date() - mViewItemJobStepStartTime) < (3600 * 72)) {
1437 attachMessageToParent(ghi, mi);
1441 mGroupHeaderItemHash.insert(groupLabel, ghi);
1450 if (mi->
parent() == ghi) {
1454 attachMessageToParent(ghi, mi);
1458 mThreadingCache.updateParent(mi,
nullptr);
1479 bool bMessageWasThreadable =
false;
1489 pParent = mThreadingCacheMessageIdMD5ToMessageItem.value(md5,
nullptr);
1496 qCWarning(MESSAGELIST_LOG) <<
"Circular In-Reply-To reference loop detected in the message tree";
1505 bMessageWasThreadable =
true;
1525 md5 = mi->referencesIdMD5();
1527 pParent = mThreadingCacheMessageIdMD5ToMessageItem.value(md5,
nullptr);
1534 qCWarning(MESSAGELIST_LOG) <<
"Circular reference loop detected in the message tree";
1542 auto messagesWithTheSameReferences = mThreadingCacheMessageReferencesIdMD5ToMessageItem.value(md5,
nullptr);
1543 if (messagesWithTheSameReferences) {
1544 Q_ASSERT(!messagesWithTheSameReferences->isEmpty());
1546 pParent = messagesWithTheSameReferences->first();
1554 bMessageWasThreadable =
true;
1580 qCDebug(MESSAGELIST_LOG) <<
"Threading cache part dump";
1582 qCDebug(MESSAGELIST_LOG) <<
"Iterator pointing to end of the list";
1584 qCDebug(MESSAGELIST_LOG) <<
"Iterator pointing to " << *iter <<
" subject [" << (*iter)->subject() <<
"] date [" << (*iter)->date() <<
"]";
1588 qCDebug(MESSAGELIST_LOG) <<
"List element " << *it <<
" subject [" << (*it)->subject() <<
"] date [" << (*it)->date() <<
"]";
1591 qCDebug(MESSAGELIST_LOG) <<
"End of threading cache part dump";
1596 qCDebug(MESSAGELIST_LOG) <<
"Threading cache part dump";
1599 qCDebug(MESSAGELIST_LOG) <<
"List element " << *it <<
" subject [" << (*it)->subject() <<
"] date [" << (*it)->date() <<
"]";
1602 qCDebug(MESSAGELIST_LOG) <<
"End of threading cache part dump";
1608class MessageLessThanByDate
1624void ModelPrivate::addMessageToReferencesBasedThreadingCache(
MessageItem *mi)
1633 auto messagesWithTheSameReference = mThreadingCacheMessageReferencesIdMD5ToMessageItem.value(mi->referencesIdMD5(),
nullptr);
1635 if (!messagesWithTheSameReference) {
1637 mThreadingCacheMessageReferencesIdMD5ToMessageItem.insert(mi->referencesIdMD5(), messagesWithTheSameReference);
1638 messagesWithTheSameReference->append(mi);
1643 Q_ASSERT(!messagesWithTheSameReference->contains(mi));
1646 auto it = std::lower_bound(messagesWithTheSameReference->begin(), messagesWithTheSameReference->end(), mi, MessageLessThanByDate());
1647 messagesWithTheSameReference->insert(it, mi);
1650void ModelPrivate::removeMessageFromReferencesBasedThreadingCache(
MessageItem *mi)
1655 auto messagesWithTheSameReference = mThreadingCacheMessageReferencesIdMD5ToMessageItem.value(mi->referencesIdMD5(),
nullptr);
1658 Q_ASSERT(messagesWithTheSameReference);
1661 auto it = std::lower_bound(messagesWithTheSameReference->begin(), messagesWithTheSameReference->end(), mi, MessageLessThanByDate());
1664 Q_ASSERT(it != messagesWithTheSameReference->end());
1667 Q_ASSERT(*it == mi);
1670 messagesWithTheSameReference->erase(it);
1673 if (messagesWithTheSameReference->isEmpty()) {
1674 mThreadingCacheMessageReferencesIdMD5ToMessageItem.remove(mi->referencesIdMD5());
1675 delete messagesWithTheSameReference;
1679void ModelPrivate::addMessageToSubjectBasedThreadingCache(
MessageItem *mi)
1689 auto messagesWithTheSameStrippedSubject = mThreadingCacheMessageSubjectMD5ToMessageItem.value(mi->strippedSubjectMD5(),
nullptr);
1691 if (!messagesWithTheSameStrippedSubject) {
1694 mThreadingCacheMessageSubjectMD5ToMessageItem.insert(mi->strippedSubjectMD5(), messagesWithTheSameStrippedSubject);
1695 messagesWithTheSameStrippedSubject->append(mi);
1700 Q_ASSERT(!messagesWithTheSameStrippedSubject->contains(mi));
1703 auto it = std::lower_bound(messagesWithTheSameStrippedSubject->begin(), messagesWithTheSameStrippedSubject->end(), mi, MessageLessThanByDate());
1704 messagesWithTheSameStrippedSubject->insert(it, mi);
1707void ModelPrivate::removeMessageFromSubjectBasedThreadingCache(
MessageItem *mi)
1715 auto messagesWithTheSameStrippedSubject = mThreadingCacheMessageSubjectMD5ToMessageItem.value(mi->strippedSubjectMD5(),
nullptr);
1718 Q_ASSERT(messagesWithTheSameStrippedSubject);
1721 auto it = std::lower_bound(messagesWithTheSameStrippedSubject->begin(), messagesWithTheSameStrippedSubject->end(), mi, MessageLessThanByDate());
1724 Q_ASSERT(it != messagesWithTheSameStrippedSubject->end());
1727 Q_ASSERT(*it == mi);
1730 messagesWithTheSameStrippedSubject->erase(it);
1733 if (messagesWithTheSameStrippedSubject->isEmpty()) {
1734 mThreadingCacheMessageSubjectMD5ToMessageItem.remove(mi->strippedSubjectMD5());
1735 delete messagesWithTheSameStrippedSubject;
1749 Q_ASSERT(mi->subjectIsPrefixed());
1753 const QByteArray md5 = mi->strippedSubjectMD5();
1755 auto messagesWithTheSameStrippedSubject = mThreadingCacheMessageSubjectMD5ToMessageItem.value(md5,
nullptr);
1757 if (messagesWithTheSameStrippedSubject) {
1758 Q_ASSERT(!messagesWithTheSameStrippedSubject->isEmpty());
1762 auto maxTime = (time_t)0;
1771 for (
const auto it : std::as_const(*messagesWithTheSameStrippedSubject)) {
1772 int delta = mi->
date() - it->date();
1792 if (delta < 3628899) {
1794 if ((maxTime < it->date())) {
1803 maxTime = it->date();
1830template<
class ItemComparator>
1833 if ((messageSortDirection == SortOrder::Ascending) || (parent->mType ==
Item::Message)) {
1834 return parent->childItemNeedsReSorting<ItemComparator,
true>(messageItem);
1836 return parent->childItemNeedsReSorting<ItemComparator,
false>(messageItem);
1839bool ModelPrivate::handleItemPropertyChanges(
int propertyChangeMask,
Item *parent,
Item *item)
1861 (propertyChangeMask & MaxDateChanged) &&
1865 (propertyChangeMask & DateChanged) &&
1872 mGroupHeadersThatNeedUpdate.insert(
static_cast<GroupHeaderItem *
>(item),
static_cast<GroupHeaderItem *
>(item));
1881 switch (mSortOrder->messageSorting()) {
1883 if (propertyChangeMask & DateChanged) {
1884 if (messageItemNeedsReSorting<ItemDateComparator>(mSortOrder->messageSortDirection(), parent->d_ptr,
static_cast<MessageItem *
>(item))) {
1885 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
1890 if (propertyChangeMask & MaxDateChanged) {
1891 if (messageItemNeedsReSorting<ItemMaxDateComparator>(mSortOrder->messageSortDirection(), parent->d_ptr,
static_cast<MessageItem *
>(item))) {
1892 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
1897 if (propertyChangeMask & ActionItemStatusChanged) {
1898 if (messageItemNeedsReSorting<ItemActionItemStatusComparator>(mSortOrder->messageSortDirection(),
1901 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
1906 if (propertyChangeMask & UnreadStatusChanged) {
1907 if (messageItemNeedsReSorting<ItemUnreadStatusComparator>(mSortOrder->messageSortDirection(),
1910 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
1914 case SortOrder::SortMessagesByImportantStatus:
1915 if (propertyChangeMask & ImportantStatusChanged) {
1916 if (messageItemNeedsReSorting<ItemImportantStatusComparator>(mSortOrder->messageSortDirection(),
1919 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
1924 if (propertyChangeMask & AttachmentStatusChanged) {
1925 if (messageItemNeedsReSorting<ItemAttachmentStatusComparator>(mSortOrder->messageSortDirection(),
1928 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
1948 (propertyChangeMask & MaxDateChanged) &&
1952 (propertyChangeMask & DateChanged) &&
1957 attachMessageToGroupHeader(
static_cast<MessageItem *
>(item));
1969 switch (mSortOrder->messageSorting()) {
1971 if (propertyChangeMask & DateChanged) {
1972 if (messageItemNeedsReSorting<ItemDateComparator>(mSortOrder->messageSortDirection(), parent->d_ptr,
static_cast<MessageItem *
>(item))) {
1973 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
1978 if (propertyChangeMask & MaxDateChanged) {
1979 if (messageItemNeedsReSorting<ItemMaxDateComparator>(mSortOrder->messageSortDirection(), parent->d_ptr,
static_cast<MessageItem *
>(item))) {
1980 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
1985 if (propertyChangeMask & ActionItemStatusChanged) {
1986 if (messageItemNeedsReSorting<ItemActionItemStatusComparator>(mSortOrder->messageSortDirection(),
1989 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
1994 if (propertyChangeMask & UnreadStatusChanged) {
1995 if (messageItemNeedsReSorting<ItemUnreadStatusComparator>(mSortOrder->messageSortDirection(), parent->d_ptr,
static_cast<MessageItem *
>(item))) {
1996 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
2000 case SortOrder::SortMessagesByImportantStatus:
2001 if (propertyChangeMask & ImportantStatusChanged) {
2002 if (messageItemNeedsReSorting<ItemImportantStatusComparator>(mSortOrder->messageSortDirection(), parent->d_ptr,
static_cast<MessageItem *
>(item))) {
2003 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
2008 if (propertyChangeMask & AttachmentStatusChanged) {
2009 if (messageItemNeedsReSorting<ItemAttachmentStatusComparator>(mSortOrder->messageSortDirection(),
2012 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
2024void ModelPrivate::messageDetachedUpdateParentProperties(
Item *oldParent,
MessageItem *mi)
2026 Q_ASSERT(oldParent);
2028 Q_ASSERT(oldParent != mRootItem);
2038 int propertyChangeMask;
2040 if ((mi->
maxDate() == oldParent->maxDate()) && oldParent->recomputeMaxDate()) {
2041 propertyChangeMask = MaxDateChanged;
2058 if (!handleItemPropertyChanges(propertyChangeMask, grandParent, oldParent)) {
2063 oldParent = grandParent;
2069 if (oldParent->childItemCount() == 0) {
2070 mGroupHeadersThatNeedUpdate.insert(
static_cast<GroupHeaderItem *
>(oldParent),
static_cast<GroupHeaderItem *
>(oldParent));
2075void ModelPrivate::propagateItemPropertiesToParent(
Item *item)
2079 Q_ASSERT(pParent != mRootItem);
2087 int propertyChangeMask;
2089 if (item->maxDate() > pParent->maxDate()) {
2090 pParent->setMaxDate(item->maxDate());
2091 propertyChangeMask = MaxDateChanged;
2109 if (!handleItemPropertyChanges(propertyChangeMask, grandParent, pParent)) {
2114 pParent = grandParent;
2118void ModelPrivate::attachMessageToParent(
Item *pParent,
MessageItem *mi, AttachOptions attachOptions)
2125 bool oldParentWasTheSame;
2131 oldParentWasTheSame = oldParent == pParent;
2162 if (((mi)->childItemCount() > 0)
2163 && mModelForItemFunctions
2164 && mView->isExpanded(q->index(mi, 0))
2166 saveExpandedStateOfSubtree(mi);
2172 oldParent->takeChildItem(mModelForItemFunctions, mi);
2174 if ((!oldParentWasTheSame) && (oldParent != mRootItem)) {
2175 messageDetachedUpdateParentProperties(oldParent, mi);
2179 oldParentWasTheSame =
false;
2190 if (!oldParentWasTheSame) {
2191 switch (mi->threadingStatus()) {
2193 if (!mi->inReplyToIdMD5().
isEmpty()) {
2194 mThreadingCacheMessageInReplyToIdMD5ToMessageItem.remove(mi->inReplyToIdMD5(), mi);
2196 if (attachOptions == StoreInCache && pParent->type() ==
Item::Message) {
2197 mThreadingCache.updateParent(mi,
static_cast<MessageItem *
>(pParent));
2202 if (!mi->inReplyToIdMD5().
isEmpty()) {
2203 if (!mThreadingCacheMessageInReplyToIdMD5ToMessageItem.contains(mi->inReplyToIdMD5(), mi)) {
2204 mThreadingCacheMessageInReplyToIdMD5ToMessageItem.insert(mi->inReplyToIdMD5(), mi);
2210 Q_ASSERT(!mThreadingCacheMessageInReplyToIdMD5ToMessageItem.contains(mi->inReplyToIdMD5(), mi));
2219 if ((pParent->status().toQInt32() & mCachedWatchedOrIgnoredStatusBits)
2223 if (pParent->status().isWatched()) {
2224 int row = mInvariantRowMapper->modelInvariantIndexToModelIndexRow(mi);
2227 }
else if (pParent->status().isIgnored()) {
2228 int row = mInvariantRowMapper->modelInvariantIndexToModelIndexRow(mi);
2244#define INSERT_MESSAGE_WITH_COMPARATOR(_ItemComparator) \
2245 if ((mSortOrder->messageSortDirection() == SortOrder::Ascending) || (pParent->type() == Item::Message)) { \
2246 pParent->d_ptr->insertChildItem<_ItemComparator, true>(mModelForItemFunctions, mi); \
2248 pParent->d_ptr->insertChildItem<_ItemComparator, false>(mModelForItemFunctions, mi); \
2253 switch (mSortOrder->messageSorting()) {
2255 INSERT_MESSAGE_WITH_COMPARATOR(ItemDateComparator)
2258 INSERT_MESSAGE_WITH_COMPARATOR(ItemMaxDateComparator)
2261 INSERT_MESSAGE_WITH_COMPARATOR(ItemSizeComparator)
2264 INSERT_MESSAGE_WITH_COMPARATOR(ItemSenderOrReceiverComparator)
2267 INSERT_MESSAGE_WITH_COMPARATOR(ItemSenderComparator)
2270 INSERT_MESSAGE_WITH_COMPARATOR(ItemReceiverComparator)
2273 INSERT_MESSAGE_WITH_COMPARATOR(ItemSubjectComparator)
2276 INSERT_MESSAGE_WITH_COMPARATOR(ItemActionItemStatusComparator)
2279 INSERT_MESSAGE_WITH_COMPARATOR(ItemUnreadStatusComparator)
2281 case SortOrder::SortMessagesByImportantStatus:
2282 INSERT_MESSAGE_WITH_COMPARATOR(ItemImportantStatusComparator)
2285 INSERT_MESSAGE_WITH_COMPARATOR(ItemAttachmentStatusComparator)
2288 pParent->appendChildItem(mModelForItemFunctions, mi);
2291 pParent->appendChildItem(mModelForItemFunctions, mi);
2299 switch (mAggregation->threadExpandPolicy()) {
2302 if (childNeedsExpanding) {
2333 if (mModelForItemFunctions && pParent->isViewable()) {
2335 Item *parentToExpand = pParent;
2336 while (parentToExpand) {
2337 if (parentToExpand == mRootItem) {
2345 mView->expand(q->index(parentToExpand, 0));
2348 parentToExpand = parentToExpand->
parent();
2353 while (parentToExpand) {
2354 if (parentToExpand == mRootItem) {
2358 parentToExpand = parentToExpand->
parent();
2367 if (childNeedsExpanding) {
2369 if (mModelForItemFunctions) {
2370 syncExpandedStateOfSubtree(mi);
2377 Q_ASSERT(mModelForItemFunctions);
2380 if (applyFilterToSubtree(mi, q->index(pParent, 0))) {
2382 mView->ensureDisplayedWithParentsExpanded(mi);
2391 if (pParent == mRootItem) {
2399 if (oldParentWasTheSame) {
2407 propagateItemPropertiesToParent(mi);
2419ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass5(ViewItemJob *job,
QElapsedTimer elapsedTimer)
2424 int curIndex = job->currentIndex();
2426 auto it = mGroupHeadersThatNeedUpdate.begin();
2427 auto end = mGroupHeadersThatNeedUpdate.end();
2430 if ((*it)->childItemCount() == 0) {
2432 (*it)->parent()->takeChildItem(mModelForItemFunctions, *it);
2433 mGroupHeaderItemHash.remove((*it)->label());
2436 if (mCurrentItemToRestoreAfterViewItemJobStep == (*it)) {
2437 mCurrentItemToRestoreAfterViewItemJobStep =
nullptr;
2451 bool needsReSorting;
2454#define CHECK_IF_GROUP_NEEDS_RESORTING(_ItemDateComparator) \
2455 switch (mSortOrder->groupSortDirection()) { \
2456 case SortOrder::Ascending: \
2457 needsReSorting = (*it)->parent()->d_ptr->childItemNeedsReSorting<_ItemDateComparator, true>(*it); \
2459 case SortOrder::Descending: \
2460 needsReSorting = (*it)->parent()->d_ptr->childItemNeedsReSorting<_ItemDateComparator, false>(*it); \
2463 needsReSorting = false; \
2467 switch (mSortOrder->groupSorting()) {
2469 CHECK_IF_GROUP_NEEDS_RESORTING(ItemDateComparator)
2472 CHECK_IF_GROUP_NEEDS_RESORTING(ItemMaxDateComparator)
2475 CHECK_IF_GROUP_NEEDS_RESORTING(ItemSenderOrReceiverComparator)
2478 CHECK_IF_GROUP_NEEDS_RESORTING(ItemSenderComparator)
2481 CHECK_IF_GROUP_NEEDS_RESORTING(ItemReceiverComparator)
2484 needsReSorting =
false;
2488 needsReSorting =
false;
2492 if (needsReSorting) {
2497 it = mGroupHeadersThatNeedUpdate.erase(it);
2504 if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
2505 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
2506 if (it != mGroupHeadersThatNeedUpdate.end()) {
2507 job->setCurrentIndex(curIndex);
2508 return ViewItemJobInterrupted;
2514 return ViewItemJobCompleted;
2517ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass4(ViewItemJob *job,
QElapsedTimer elapsedTimer)
2527 int curIndex = job->currentIndex();
2528 int endIndex = job->endIndex();
2530 while (curIndex <= endIndex) {
2531 MessageItem *mi = mUnassignedMessageListForPass4[curIndex];
2535 attachMessageToGroupHeader(mi);
2544 if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
2545 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
2546 if (curIndex <= endIndex) {
2547 job->setCurrentIndex(curIndex);
2548 return ViewItemJobInterrupted;
2554 mUnassignedMessageListForPass4.clear();
2555 return ViewItemJobCompleted;
2558ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass3(ViewItemJob *job,
QElapsedTimer elapsedTimer)
2571 int curIndex = job->currentIndex();
2572 int endIndex = job->endIndex();
2574 while (curIndex <= endIndex) {
2576 auto mi = mUnassignedMessageListForPass3[curIndex];
2580 if (mi->subjectIsPrefixed()) {
2582 auto mparent = guessMessageParent(mi);
2588 attachMessageToParent(mparent, mi);
2589 if (!mparent->isViewable()) {
2591 auto topmost = mparent->topmostMessage();
2592 Q_ASSERT(!topmost->parent());
2594 attachMessageToGroupHeader(topmost);
2598 attachMessageToParent(mparent, mi);
2604 mUnassignedMessageListForPass4.append(mi);
2610 mUnassignedMessageListForPass4.append(mi);
2625 if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
2626 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
2627 if (curIndex <= endIndex) {
2628 job->setCurrentIndex(curIndex);
2629 return ViewItemJobInterrupted;
2635 mUnassignedMessageListForPass3.clear();
2636 return ViewItemJobCompleted;
2639ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass2(ViewItemJob *job,
QElapsedTimer elapsedTimer)
2654 int curIndex = job->currentIndex();
2655 int endIndex = job->endIndex();
2657 while (curIndex <= endIndex) {
2659 auto mi = mUnassignedMessageListForPass2[curIndex];
2665 auto mparent = mThreadingCache.parentForItem(mi, parentId);
2666 if (mparent && !mparent->hasAncestor(mi)) {
2668 attachMessageToParent(mparent, mi, SkipCacheUpdate);
2674 mThreadingCache.expireParent(mi);
2675 mparent = findMessageParent(mi);
2676 }
else if (parentId < 0) {
2677 mparent = findMessageParent(mi);
2688 attachMessageToParent(mparent, mi);
2689 if (!mparent->isViewable()) {
2691 auto topmost = mparent->topmostMessage();
2692 Q_ASSERT(!topmost->parent());
2694 attachMessageToGroupHeader(topmost);
2698 attachMessageToParent(mparent, mi);
2703 switch (mi->threadingStatus()) {
2707 mUnassignedMessageListForPass3.append(mi);
2710 mUnassignedMessageListForPass4.append(mi);
2715 mUnassignedMessageListForPass4.append(mi);
2719 qCWarning(MESSAGELIST_LOG) <<
"ERROR: Invalid message threading status returned by findMessageParent()!";
2730 qCWarning(MESSAGELIST_LOG) <<
"Non viewable message " << mi <<
" subject " << mi->
subject().
toUtf8().
data();
2740 if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
2741 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
2742 if (curIndex <= endIndex) {
2743 job->setCurrentIndex(curIndex);
2744 return ViewItemJobInterrupted;
2750 mUnassignedMessageListForPass2.clear();
2751 return ViewItemJobCompleted;
2754ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass1Fill(ViewItemJob *job,
QElapsedTimer elapsedTimer)
2766 bool bUseReceiver = mStorageModelContainsOutboundMessages;
2769 int curIndex = job->currentIndex();
2771 int endIndex = job->endIndex();
2773 unsigned long msgToSelect = mPreSelectionMode == PreSelectLastSelected ? mStorageModel->preSelectedMessage() : 0;
2777 while (curIndex <= endIndex) {
2783 Q_ASSERT(mi->
parent() ==
nullptr);
2786 if (!mStorageModel->initializeMessageItem(mi, curIndex, bUseReceiver)) {
2788 qCWarning(MESSAGELIST_LOG) <<
"Fill of the MessageItem at storage row index " << curIndex <<
" failed";
2794 if (msgToSelect != 0 && msgToSelect == mi->uniqueId()) {
2799 mLastSelectedMessageInFolder = mi;
2804 if (mi->
date() !=
static_cast<uint
>(-1)) {
2805 if (!mOldestItem || mOldestItem->date() > mi->
date()) {
2808 if (!mNewestItem || mNewestItem->date() < mi->
date()) {
2815 mInvariantRowMapper->createModelInvariantIndex(curIndex, mi);
2824 switch (mAggregation->threading()) {
2829 addMessageToReferencesBasedThreadingCache(mi);
2830 addMessageToSubjectBasedThreadingCache(mi);
2834 addMessageToReferencesBasedThreadingCache(mi);
2842 mThreadingCacheMessageIdMD5ToMessageItem.insert(mi->messageIdMD5(), mi);
2845 mThreadingCache.addItemToCache(mi);
2849 Item *pParent = mThreadingCache.parentForItem(mi, parentId);
2854 attachMessageToParent(pParent, mi);
2855 }
else if (parentId > 0) {
2860 mUnassignedMessageListForPass2.append(mi);
2861 }
else if (parentId == 0) {
2864 mUnassignedMessageListForPass4.append(mi);
2870 bool needsImmediateReAttach =
false;
2872 if (!mThreadingCacheMessageInReplyToIdMD5ToMessageItem.isEmpty()) {
2873 const auto lImperfectlyThreaded = mThreadingCacheMessageInReplyToIdMD5ToMessageItem.values(mi->messageIdMD5());
2874 for (
const auto it : lImperfectlyThreaded) {
2875 Q_ASSERT(it->parent());
2876 Q_ASSERT(it->parent() != mi);
2879 qCritical() <<
"Got message " << it <<
" with threading status" << it->threadingStatus();
2880 Q_ASSERT_X(
false,
"ModelPrivate::viewItemJobStepInternalForJobPass1Fill",
"Wrong threading status");
2887 if (it->isViewable()) {
2888 needsImmediateReAttach =
true;
2892 attachMessageToParent(mi, it);
2911 pParent = mThreadingCacheMessageIdMD5ToMessageItem.value(md5,
nullptr);
2920 && pParent->hasAncestor(mi)
2925 qCWarning(MESSAGELIST_LOG) <<
"Circular In-Reply-To reference loop detected in the message tree";
2926 mUnassignedMessageListForPass2.append(mi);
2930 attachMessageToParent(pParent, mi);
2936 mUnassignedMessageListForPass2.append(mi);
2941 bool mightHaveOtherMeansForThreading;
2943 switch (mAggregation->threading()) {
2945 mightHaveOtherMeansForThreading = mi->subjectIsPrefixed() || !mi->referencesIdMD5().
isEmpty();
2948 mightHaveOtherMeansForThreading = !mi->referencesIdMD5().
isEmpty();
2951 mightHaveOtherMeansForThreading =
false;
2956 mightHaveOtherMeansForThreading =
false;
2960 if (mightHaveOtherMeansForThreading) {
2962 mUnassignedMessageListForPass2.append(mi);
2974 attachMessageToGroupHeader(mi);
2979 mUnassignedMessageListForPass2.append(mi);
2984 if (needsImmediateReAttach && !mi->
isViewable()) {
2990 attachMessageToGroupHeader(topmost);
3000 attachMessageToParent(mRootItem, mi);
3002 attachMessageToGroupHeader(mi);
3010 if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
3011 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
3012 if (curIndex <= endIndex) {
3013 job->setCurrentIndex(curIndex);
3014 return ViewItemJobInterrupted;
3023 return ViewItemJobCompleted;
3026ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass1Cleanup(ViewItemJob *job,
QElapsedTimer elapsedTimer)
3028 Q_ASSERT(mModelForItemFunctions);
3040 int curIndex = job->currentIndex();
3042 int endIndex = job->endIndex();
3044 if (curIndex == job->startIndex()) {
3045 Q_ASSERT(mOrphanChildrenHash.isEmpty());
3048 while (curIndex <= endIndex) {
3050 auto dyingMessage =
dynamic_cast<MessageItem *
>(invalidatedMessages->
at(curIndex));
3052 Q_ASSERT(dyingMessage);
3057 if (dyingMessage == mLastSelectedMessageInFolder) {
3058 mLastSelectedMessageInFolder =
nullptr;
3059 mPreSelectionMode = PreSelectNone;
3063 if (mPersistentSetManager) {
3064 mPersistentSetManager->removeMessageItemFromAllSets(dyingMessage);
3065 if (mPersistentSetManager->setCount() < 1) {
3066 delete mPersistentSetManager;
3067 mPersistentSetManager =
nullptr;
3073 mThreadingCache.expireParent(dyingMessage);
3075 if (dyingMessage->parent()) {
3079 if (dyingMessage == mCurrentItemToRestoreAfterViewItemJobStep) {
3080 Q_ASSERT(dyingMessage->isViewable());
3085 mCurrentItemToRestoreAfterViewItemJobStep = mView->messageItemAfter(dyingMessage, MessageTypeAny,
false);
3087 if (!mCurrentItemToRestoreAfterViewItemJobStep) {
3091 mCurrentItemToRestoreAfterViewItemJobStep = mView->messageItemBefore(dyingMessage, MessageTypeAny,
false);
3094 Q_ASSERT((!mCurrentItemToRestoreAfterViewItemJobStep) || mCurrentItemToRestoreAfterViewItemJobStep->isViewable());
3097 if (dyingMessage->isViewable() && ((dyingMessage)->childItemCount() > 0)
3098 && mView->isExpanded(q->index(dyingMessage, 0))
3100 saveExpandedStateOfSubtree(dyingMessage);
3103 auto oldParent = dyingMessage->
parent();
3104 oldParent->takeChildItem(q, dyingMessage);
3110 if (oldParent != mRootItem) {
3111 messageDetachedUpdateParentProperties(oldParent, dyingMessage);
3117 mOrphanChildrenHash.remove(dyingMessage);
3123 Q_ASSERT(mOrphanChildrenHash.contains(dyingMessage));
3124 Q_ASSERT(dyingMessage != mCurrentItemToRestoreAfterViewItemJobStep);
3126 mOrphanChildrenHash.remove(dyingMessage);
3133 mThreadingCacheMessageIdMD5ToMessageItem.remove(dyingMessage->messageIdMD5());
3137 removeMessageFromReferencesBasedThreadingCache(dyingMessage);
3138 removeMessageFromSubjectBasedThreadingCache(dyingMessage);
3140 removeMessageFromReferencesBasedThreadingCache(dyingMessage);
3144 switch (dyingMessage->threadingStatus()) {
3147 if (!dyingMessage->inReplyToIdMD5().isEmpty()) {
3148 mThreadingCacheMessageInReplyToIdMD5ToMessageItem.remove(dyingMessage->inReplyToIdMD5());
3152 Q_ASSERT(!mThreadingCacheMessageInReplyToIdMD5ToMessageItem.contains(dyingMessage->inReplyToIdMD5(), dyingMessage));
3158 while (
auto childItem = dyingMessage->firstChildItem()) {
3159 auto childMessage =
dynamic_cast<MessageItem *
>(childItem);
3160 Q_ASSERT(childMessage);
3162 dyingMessage->takeChildItem(q, childMessage);
3168 if (!childMessage->inReplyToIdMD5().isEmpty()) {
3169 Q_ASSERT(!mThreadingCacheMessageInReplyToIdMD5ToMessageItem.contains(childMessage->inReplyToIdMD5(), childMessage));
3170 mThreadingCacheMessageInReplyToIdMD5ToMessageItem.insert(childMessage->inReplyToIdMD5(), childMessage);
3185 (childMessage == mCurrentItemToRestoreAfterViewItemJobStep)
3188 mCurrentItemToRestoreAfterViewItemJobStep &&
3189 mCurrentItemToRestoreAfterViewItemJobStep->hasAncestor(childMessage))) {
3190 attachMessageToGroupHeader(childMessage);
3192 Q_ASSERT(childMessage->isViewable());
3195 mOrphanChildrenHash.insert(childMessage, childMessage);
3198 if (mNewestItem == dyingMessage) {
3199 mNewestItem =
nullptr;
3201 if (mOldestItem == dyingMessage) {
3202 mOldestItem =
nullptr;
3205 delete dyingMessage;
3212 if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
3213 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
3214 if (curIndex <= endIndex) {
3215 job->setCurrentIndex(curIndex);
3216 return ViewItemJobInterrupted;
3224 job->setCurrentIndex(endIndex + 1);
3233 auto it = mOrphanChildrenHash.begin();
3234 auto end = mOrphanChildrenHash.end();
3239 mUnassignedMessageListForPass2.append(*it);
3241 it = mOrphanChildrenHash.erase(it);
3248 if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
3249 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
3250 if (it != mOrphanChildrenHash.end()) {
3251 return ViewItemJobInterrupted;
3257 return ViewItemJobCompleted;
3260ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass1Update(ViewItemJob *job,
QElapsedTimer elapsedTimer)
3262 Q_ASSERT(mModelForItemFunctions);
3267 auto messagesThatNeedUpdate = job->invariantIndexList();
3274 int curIndex = job->currentIndex();
3276 int endIndex = job->endIndex();
3278 while (curIndex <= endIndex) {
3280 auto message =
dynamic_cast<MessageItem *
>(messagesThatNeedUpdate->at(curIndex));
3284 int row = mInvariantRowMapper->modelInvariantIndexToModelIndexRow(message);
3288 Q_ASSERT(!message->isValid());
3294 time_t prevDate = message->date();
3295 time_t prevMaxDate = message->maxDate();
3296 bool toDoStatus = message->status().isToAct();
3297 bool prevUnreadStatus = !message->status().isRead();
3298 bool prevImportantStatus = message->status().isImportant();
3303 removeMessageFromReferencesBasedThreadingCache(message);
3304 removeMessageFromSubjectBasedThreadingCache(message);
3306 removeMessageFromReferencesBasedThreadingCache(message);
3310 mStorageModel->updateMessageItemData(message, row);
3312 Q_EMIT q->dataChanged(idx, idx);
3316 addMessageToReferencesBasedThreadingCache(message);
3317 addMessageToSubjectBasedThreadingCache(message);
3319 addMessageToReferencesBasedThreadingCache(message);
3322 int propertyChangeMask = 0;
3324 if (prevDate != message->date()) {
3325 propertyChangeMask |= DateChanged;
3327 if (prevMaxDate != message->maxDate()) {
3328 propertyChangeMask |= MaxDateChanged;
3330 if (toDoStatus != message->status().isToAct()) {
3331 propertyChangeMask |= ActionItemStatusChanged;
3333 if (prevUnreadStatus != (!message->status().isRead())) {
3334 propertyChangeMask |= UnreadStatusChanged;
3336 if (prevImportantStatus != (!message->status().isImportant())) {
3337 propertyChangeMask |= ImportantStatusChanged;
3340 if (propertyChangeMask) {
3345 Item *pParent = message->parent();
3347 if (pParent && (pParent != mRootItem)) {
3350 if (handleItemPropertyChanges(propertyChangeMask, pParent, message)) {
3351 Q_ASSERT(message->parent());
3357 propagateItemPropertiesToParent(message);
3363 if (mFilter && message->isViewable()) {
3365 Item *pTopMostNonRoot = message->topmostNonRoot();
3367 Q_ASSERT(pTopMostNonRoot);
3368 Q_ASSERT(pTopMostNonRoot != mRootItem);
3369 Q_ASSERT(pTopMostNonRoot->
parent() == mRootItem);
3381 applyFilterToSubtree(pTopMostNonRoot,
QModelIndex());
3393 if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
3394 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
3395 if (curIndex <= endIndex) {
3396 job->setCurrentIndex(curIndex);
3397 return ViewItemJobInterrupted;
3403 return ViewItemJobCompleted;
3406ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJob(ViewItemJob *job,
QElapsedTimer elapsedTimer)
3415 if (job->currentPass() == ViewItemJob::Pass1Fill) {
3417 switch (viewItemJobStepInternalForJobPass1Fill(job, elapsedTimer)) {
3418 case ViewItemJobInterrupted:
3420 return ViewItemJobInterrupted;
3422 case ViewItemJobCompleted:
3425 job->setCurrentPass(ViewItemJob::Pass2);
3426 job->setStartIndex(0);
3427 job->setEndIndex(mUnassignedMessageListForPass2.count() - 1);
3432 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
3433 return ViewItemJobInterrupted;
3438 qCWarning(MESSAGELIST_LOG) <<
"ERROR: returned an invalid result";
3442 }
else if (job->currentPass() == ViewItemJob::Pass1Cleanup) {
3444 switch (viewItemJobStepInternalForJobPass1Cleanup(job, elapsedTimer)) {
3445 case ViewItemJobInterrupted:
3447 return ViewItemJobInterrupted;
3449 case ViewItemJobCompleted:
3451 job->setCurrentPass(ViewItemJob::Pass2);
3452 job->setStartIndex(0);
3453 job->setEndIndex(mUnassignedMessageListForPass2.count() - 1);
3458 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
3459 return ViewItemJobInterrupted;
3464 qCWarning(MESSAGELIST_LOG) <<
"ERROR: returned an invalid result";
3468 }
else if (job->currentPass() == ViewItemJob::Pass1Update) {
3470 switch (viewItemJobStepInternalForJobPass1Update(job, elapsedTimer)) {
3471 case ViewItemJobInterrupted:
3473 return ViewItemJobInterrupted;
3475 case ViewItemJobCompleted:
3479 job->setCurrentPass(ViewItemJob::Pass5);
3480 job->setStartIndex(0);
3481 job->setEndIndex(mGroupHeadersThatNeedUpdate.count() - 1);
3486 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
3487 return ViewItemJobInterrupted;
3492 qCWarning(MESSAGELIST_LOG) <<
"ERROR: returned an invalid result";
3500 if (job->currentPass() == ViewItemJob::Pass2) {
3502 switch (viewItemJobStepInternalForJobPass2(job, elapsedTimer)) {
3503 case ViewItemJobInterrupted:
3505 return ViewItemJobInterrupted;
3507 case ViewItemJobCompleted:
3509 job->setCurrentPass(ViewItemJob::Pass3);
3510 job->setStartIndex(0);
3511 job->setEndIndex(mUnassignedMessageListForPass3.count() - 1);
3516 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
3517 return ViewItemJobInterrupted;
3523 qCWarning(MESSAGELIST_LOG) <<
"ERROR: returned an invalid result";
3529 if (job->currentPass() == ViewItemJob::Pass3) {
3531 switch (viewItemJobStepInternalForJobPass3(job, elapsedTimer)) {
3532 case ViewItemJobInterrupted:
3534 return ViewItemJobInterrupted;
3535 case ViewItemJobCompleted:
3537 job->setCurrentPass(ViewItemJob::Pass4);
3538 job->setStartIndex(0);
3539 job->setEndIndex(mUnassignedMessageListForPass4.count() - 1);
3544 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
3545 return ViewItemJobInterrupted;
3551 qCWarning(MESSAGELIST_LOG) <<
"ERROR: returned an invalid result";
3557 if (job->currentPass() == ViewItemJob::Pass4) {
3559 switch (viewItemJobStepInternalForJobPass4(job, elapsedTimer)) {
3560 case ViewItemJobInterrupted:
3562 return ViewItemJobInterrupted;
3563 case ViewItemJobCompleted:
3565 job->setCurrentPass(ViewItemJob::Pass5);
3566 job->setStartIndex(0);
3567 job->setEndIndex(mGroupHeadersThatNeedUpdate.count() - 1);
3572 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
3573 return ViewItemJobInterrupted;
3579 qCWarning(MESSAGELIST_LOG) <<
"ERROR: returned an invalid result";
3586 return viewItemJobStepInternalForJobPass5(job, elapsedTimer);
3589#ifdef KDEPIM_FOLDEROPEN_PROFILE
3595static const int numberOfPasses = ViewItemJob::LastIndex;
3599static int lastPass = -1;
3602static int totalMessages;
3605static int numElements[numberOfPasses];
3606static int totalTime[numberOfPasses];
3607static int chunks[numberOfPasses];
3610static int expandingTreeTime;
3611static int layoutChangeTime;
3614static const char *jobDescription[numberOfPasses] = {
"Creating items from messages and simple threading",
3615 "Removing messages",
3616 "Updating messages",
3617 "Additional Threading",
3618 "Subject-Based threading",
3620 "Group resorting + cleanup"};
3623static QTime firstStartTime;
3626static QTime currentJobStartTime;
3629static void resetStats()
3632 layoutChangeTime = 0;
3633 expandingTreeTime = 0;
3635 for (
int i = 0; i < numberOfPasses; ++i) {
3643void ModelPrivate::printStatistics()
3645 using namespace Stats;
3646 int totalTotalTime = 0;
3647 int completeTime = firstStartTime.elapsed();
3648 for (
int i = 0; i < numberOfPasses; ++i) {
3649 totalTotalTime += totalTime[i];
3652 float msgPerSecond = totalMessages / (totalTotalTime / 1000.0f);
3653 float msgPerSecondComplete = totalMessages / (completeTime / 1000.0f);
3655 int messagesWithSameSubjectAvg = 0;
3656 int messagesWithSameSubjectMax = 0;
3657 for (
const auto messages : std::as_const(mThreadingCacheMessageSubjectMD5ToMessageItem)) {
3658 if (messages->size() > messagesWithSameSubjectMax) {
3659 messagesWithSameSubjectMax = messages->size();
3661 messagesWithSameSubjectAvg += messages->size();
3663 messagesWithSameSubjectAvg = messagesWithSameSubjectAvg / (float)mThreadingCacheMessageSubjectMD5ToMessageItem.size();
3665 int totalThreads = 0;
3666 if (!mGroupHeaderItemHash.isEmpty()) {
3667 for (
const GroupHeaderItem *groupHeader : std::as_const(mGroupHeaderItemHash)) {
3668 totalThreads += groupHeader->childItemCount();
3671 totalThreads = mRootItem->childItemCount();
3674 qCDebug(MESSAGELIST_LOG) <<
"Finished filling the view with" << totalMessages <<
"messages";
3675 qCDebug(MESSAGELIST_LOG) <<
"That took" << totalTotalTime <<
"msecs inside the model and" << completeTime <<
"in total.";
3676 qCDebug(MESSAGELIST_LOG) << (totalTotalTime / (float)completeTime) * 100.0f <<
"percent of the time was spent in the model.";
3677 qCDebug(MESSAGELIST_LOG) <<
"Time for layoutChanged(), in msecs:" << layoutChangeTime <<
"(" << (layoutChangeTime / (float)totalTotalTime) * 100.0f
3679 qCDebug(MESSAGELIST_LOG) <<
"Time to expand tree, in msecs:" << expandingTreeTime <<
"(" << (expandingTreeTime / (float)totalTotalTime) * 100.0f
3681 qCDebug(MESSAGELIST_LOG) <<
"Number of messages per second in the model:" << msgPerSecond;
3682 qCDebug(MESSAGELIST_LOG) <<
"Number of messages per second in total:" << msgPerSecondComplete;
3683 qCDebug(MESSAGELIST_LOG) <<
"Number of threads:" << totalThreads;
3684 qCDebug(MESSAGELIST_LOG) <<
"Number of groups:" << mGroupHeaderItemHash.size();
3685 qCDebug(MESSAGELIST_LOG) <<
"Messages per thread:" << totalMessages / (float)totalThreads;
3686 qCDebug(MESSAGELIST_LOG) <<
"Threads per group:" << totalThreads / (float)mGroupHeaderItemHash.size();
3687 qCDebug(MESSAGELIST_LOG) <<
"Messages with the same subject:"
3688 <<
"Max:" << messagesWithSameSubjectMax <<
"Avg:" << messagesWithSameSubjectAvg;
3689 qCDebug(MESSAGELIST_LOG);
3690 qCDebug(MESSAGELIST_LOG) <<
"Now follows a breakdown of the jobs.";
3691 qCDebug(MESSAGELIST_LOG);
3692 for (
int i = 0; i < numberOfPasses; ++i) {
3693 if (totalTime[i] == 0) {
3696 float elementsPerSecond = numElements[i] / (totalTime[i] / 1000.0f);
3697 float percent = totalTime[i] / (float)totalTotalTime * 100.0f;
3698 qCDebug(MESSAGELIST_LOG) <<
"----------------------------------------------";
3699 qCDebug(MESSAGELIST_LOG) <<
"Job" << i + 1 <<
"(" << jobDescription[i] <<
")";
3700 qCDebug(MESSAGELIST_LOG) <<
"Share of complete time:" << percent <<
"percent";
3701 qCDebug(MESSAGELIST_LOG) <<
"Time in msecs:" << totalTime[i];
3702 qCDebug(MESSAGELIST_LOG) <<
"Number of elements:" << numElements[i];
3703 qCDebug(MESSAGELIST_LOG) <<
"Elements per second:" << elementsPerSecond;
3704 qCDebug(MESSAGELIST_LOG) <<
"Number of chunks:" << chunks[i];
3705 qCDebug(MESSAGELIST_LOG);
3708 qCDebug(MESSAGELIST_LOG) <<
"==========================================================";
3714ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternal()
3721 elapsedTimer.
start();
3723 while (!mViewItemJobs.isEmpty()) {
3725 ViewItemJob *job = mViewItemJobs.constFirst();
3727#ifdef KDEPIM_FOLDEROPEN_PROFILE
3732 const int currentPass = job->currentPass();
3733 const bool firstChunk = currentPass != Stats::lastPass;
3734 if (currentPass != Stats::lastPass && Stats::lastPass != -1) {
3735 Stats::totalTime[Stats::lastPass] = Stats::currentJobStartTime.elapsed();
3737 const bool firstJob = job->currentPass() == ViewItemJob::Pass1Fill && firstChunk;
3738 const int elements = job->endIndex() - job->startIndex();
3740 Stats::resetStats();
3741 Stats::totalMessages = elements;
3742 Stats::firstStartTime.restart();
3745 Stats::numElements[currentPass] = elements;
3746 Stats::currentJobStartTime.restart();
3748 Stats::chunks[currentPass]++;
3749 Stats::lastPass = currentPass;
3753 mViewItemJobStepIdleInterval = job->idleInterval();
3754 mViewItemJobStepChunkTimeout = job->chunkTimeout();
3755 mViewItemJobStepMessageCheckCount = job->messageCheckCount();
3757 if (job->disconnectUI()) {
3758 mModelForItemFunctions =
nullptr;
3769 mView->ignoreUpdateGeometries(
true);
3780 switch (viewItemJobStepInternalForJob(job, elapsedTimer)) {
3781 case ViewItemJobInterrupted:
3786 switch (job->currentPass()) {
3787 case ViewItemJob::Pass1Fill:
3788 case ViewItemJob::Pass1Cleanup:
3789 case ViewItemJob::Pass1Update:
3790 Q_EMIT q->statusMessage(
i18np(
"Processed 1 Message of %2",
3791 "Processed %1 Messages of %2",
3792 job->currentIndex() - job->startIndex(),
3793 job->endIndex() - job->startIndex() + 1));
3795 case ViewItemJob::Pass2:
3796 Q_EMIT q->statusMessage(
i18np(
"Threaded 1 Message of %2",
3797 "Threaded %1 Messages of %2",
3798 job->currentIndex() - job->startIndex(),
3799 job->endIndex() - job->startIndex() + 1));
3801 case ViewItemJob::Pass3:
3802 Q_EMIT q->statusMessage(
i18np(
"Threaded 1 Message of %2",
3803 "Threaded %1 Messages of %2",
3804 job->currentIndex() - job->startIndex(),
3805 job->endIndex() - job->startIndex() + 1));
3807 case ViewItemJob::Pass4:
3808 Q_EMIT q->statusMessage(
i18np(
"Grouped 1 Thread of %2",
3809 "Grouped %1 Threads of %2",
3810 job->currentIndex() - job->startIndex(),
3811 job->endIndex() - job->startIndex() + 1));
3813 case ViewItemJob::Pass5:
3814 Q_EMIT q->statusMessage(
i18np(
"Updated 1 Group of %2",
3815 "Updated %1 Groups of %2",
3816 job->currentIndex() - job->startIndex(),
3817 job->endIndex() - job->startIndex() + 1));
3823 if (!job->disconnectUI()) {
3824 mView->ignoreUpdateGeometries(
false);
3826 mView->updateGeometries();
3829 return ViewItemJobInterrupted;
3831 case ViewItemJobCompleted:
3835 if (job->disconnectUI()) {
3836 mModelForItemFunctions = q;
3840#ifdef KDEPIM_FOLDEROPEN_PROFILE
3841 QTime layoutChangedTimer;
3842 layoutChangedTimer.start();
3844 mView->modelAboutToEmitLayoutChanged();
3845 Q_EMIT q->layoutChanged();
3846 mView->modelEmittedLayoutChanged();
3848#ifdef KDEPIM_FOLDEROPEN_PROFILE
3849 Stats::layoutChangeTime = layoutChangedTimer.elapsed();
3850 QTime expandingTime;
3851 expandingTime.start();
3858 auto rootChildItems = mRootItem->childItems();
3859 if (rootChildItems) {
3860 for (
const auto it : std::as_const(*rootChildItems)) {
3862 syncExpandedStateOfSubtree(it);
3866#ifdef KDEPIM_FOLDEROPEN_PROFILE
3867 Stats::expandingTreeTime = expandingTime.elapsed();
3870 mView->ignoreUpdateGeometries(
false);
3872 mView->updateGeometries();
3876 delete mViewItemJobs.takeFirst();
3878#ifdef KDEPIM_FOLDEROPEN_PROFILE
3880 Stats::totalTime[currentPass] = Stats::currentJobStartTime.elapsed();
3888 if ((elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) || (elapsedTimer.
elapsed() < 0)) {
3889 if (!mViewItemJobs.isEmpty()) {
3890 return ViewItemJobInterrupted;
3898 qCWarning(MESSAGELIST_LOG) <<
"ERROR: returned an invalid result";
3906 Q_EMIT q->statusMessage(
i18nc(
"@info:status Finished view fill",
"Ready"));
3908 return ViewItemJobCompleted;
3911void ModelPrivate::viewItemJobStep()
3919 mViewItemJobStepStartTime = ::time(
nullptr);
3921 if (mFillStepTimer.isActive()) {
3922 mFillStepTimer.stop();
3925 if (!mStorageModel) {
3934 QModelIndex currentIndexBeforeStep = mView->currentIndex();
3938 mCurrentItemToRestoreAfterViewItemJobStep = currentItemBeforeStep;
3942 QRect rectBeforeViewItemJobStep;
3944 const bool lockView = mView->isScrollingLocked();
3947 if (mCurrentItemToRestoreAfterViewItemJobStep && (!lockView)) {
3948 rectBeforeViewItemJobStep = mView->visualRect(currentIndexBeforeStep);
3955 mView->ignoreCurrentChanges(
true);
3958 switch (viewItemJobStepInternal()) {
3959 case ViewItemJobInterrupted:
3961 if (!mInLengthyJobBatch) {
3962 mInLengthyJobBatch =
true;
3964 mFillStepTimer.start(mViewItemJobStepIdleInterval);
3967 case ViewItemJobCompleted:
3970 Q_ASSERT(mModelForItemFunctions);
3973 if (mInLengthyJobBatch) {
3974 mInLengthyJobBatch =
false;
3979 mView->modelFinishedLoading();
3984 if (mPreSelectionMode != PreSelectNone) {
3985 mView->ignoreCurrentChanges(
false);
3987 bool bSelectionDone =
false;
3989 switch (mPreSelectionMode) {
3990 case PreSelectLastSelected:
3993 case PreSelectFirstUnreadCentered:
3994 bSelectionDone = mView->selectFirstMessageItem(MessageTypeUnreadOnly,
true);
3996 case PreSelectOldestCentered:
3997 mView->setCurrentMessageItem(mOldestItem,
true );
3998 bSelectionDone =
true;
4000 case PreSelectNewestCentered:
4001 mView->setCurrentMessageItem(mNewestItem,
true );
4002 bSelectionDone =
true;
4008 qCWarning(MESSAGELIST_LOG) <<
"ERROR: Unrecognized pre-selection mode " <<
static_cast<int>(mPreSelectionMode);
4012 if ((!bSelectionDone) && (mPreSelectionMode != PreSelectNone)) {
4014 if (mLastSelectedMessageInFolder) {
4015 mView->setCurrentMessageItem(mLastSelectedMessageInFolder);
4016 bSelectionDone =
true;
4020 if (bSelectionDone) {
4021 mLastSelectedMessageInFolder =
nullptr;
4022 mPreSelectionMode = PreSelectNone;
4031 qCWarning(MESSAGELIST_LOG) <<
"ERROR: returned an invalid result";
4039 if (!mModelForItemFunctions) {
4040 mView->ignoreCurrentChanges(
false);
4046 if (mCurrentItemToRestoreAfterViewItemJobStep) {
4047 bool stillIgnoringCurrentChanges =
true;
4051 Q_ASSERT(mCurrentItemToRestoreAfterViewItemJobStep->isViewable());
4054 QModelIndex currentIndexAfterStep = mView->currentIndex();
4057 if (mCurrentItemToRestoreAfterViewItemJobStep != currentAfterStep) {
4059 if (mCurrentItemToRestoreAfterViewItemJobStep != currentItemBeforeStep) {
4064 stillIgnoringCurrentChanges =
false;
4065 mView->ignoreCurrentChanges(
false);
4073 qCDebug(MESSAGELIST_LOG) <<
"Gonna restore current here" << mCurrentItemToRestoreAfterViewItemJobStep->subject();
4074 mView->setCurrentIndex(q->index(mCurrentItemToRestoreAfterViewItemJobStep, 0));
4077 if (mCurrentItemToRestoreAfterViewItemJobStep != currentItemBeforeStep) {
4083 if (!mView->selectionModel()->hasSelection()) {
4084 stillIgnoringCurrentChanges =
false;
4085 mView->ignoreCurrentChanges(
false);
4087 qCDebug(MESSAGELIST_LOG) <<
"Gonna restore selection here" << mCurrentItemToRestoreAfterViewItemJobStep->subject();
4099 QRect rectAfterViewItemJobStep = mView->visualRect(q->index(mCurrentItemToRestoreAfterViewItemJobStep, 0));
4100 if (rectBeforeViewItemJobStep.
y() != rectAfterViewItemJobStep.
y()) {
4102 mView->verticalScrollBar()->setValue(mView->verticalScrollBar()->value() + rectAfterViewItemJobStep.
y() - rectBeforeViewItemJobStep.
y());
4107 if (stillIgnoringCurrentChanges) {
4108 mView->ignoreCurrentChanges(
false);
4116 mView->ignoreCurrentChanges(
false);
4118 if (currentItemBeforeStep) {
4125void ModelPrivate::slotStorageModelRowsInserted(
const QModelIndex &parent,
int from,
int to)
4131 Q_ASSERT(from <= to);
4133 int count = (to - from) + 1;
4135 mInvariantRowMapper->modelRowsInserted(from, count);
4139 int jobCount = mViewItemJobs.count();
4141 for (
int idx = 0; idx < jobCount; idx++) {
4142 ViewItemJob *job = mViewItemJobs.at(idx);
4144 if (job->currentPass() != ViewItemJob::Pass1Fill) {
4150 if (job->currentIndex() > job->endIndex()) {
4180 if (from > job->endIndex()) {
4185 if (from > job->currentIndex()) {
4191 auto newJob =
new ViewItemJob(from + count, job->endIndex() + count, job->chunkTimeout(), job->idleInterval(), job->messageCheckCount());
4193 Q_ASSERT(newJob->currentIndex() <= newJob->endIndex());
4197 mViewItemJobs.insert(idx, newJob);
4200 job->setEndIndex(from - 1);
4202 Q_ASSERT(job->currentIndex() <= job->endIndex());
4209 job->setCurrentIndex(job->currentIndex() + count);
4210 job->setEndIndex(job->endIndex() + count);
4212 Q_ASSERT(job->currentIndex() <= job->endIndex());
4215 bool newJobNeeded =
true;
4223 ViewItemJob *job = mViewItemJobs.at(jobCount - 1);
4224 if (job->currentPass() == ViewItemJob::Pass1Fill) {
4227 (from == (job->endIndex() + 1)) &&
4228 (job->currentIndex() <= job->endIndex())) {
4230 job->setEndIndex(to);
4231 Q_ASSERT(job->currentIndex() <= job->endIndex());
4232 newJobNeeded =
false;
4239 auto job =
new ViewItemJob(from, to, 100, 50, 10);
4240 mViewItemJobs.append(job);
4243 if (!mFillStepTimer.isActive()) {
4244 mFillStepTimer.start(mViewItemJobStepIdleInterval);
4248void ModelPrivate::slotStorageModelRowsRemoved(
const QModelIndex &parent,
int from,
int to)
4258 Q_ASSERT(from <= to);
4260 const int count = (to - from) + 1;
4262 int jobCount = mViewItemJobs.count();
4264 if (mRootItem && from == 0 && count == mRootItem->childItemCount() && jobCount == 0) {
4269 for (
int idx = 0; idx < jobCount; idx++) {
4270 ViewItemJob *job = mViewItemJobs.at(idx);
4272 if (job->currentPass() != ViewItemJob::Pass1Fill) {
4278 if (job->currentIndex() > job->endIndex()) {
4308 if (from > job->endIndex()) {
4313 if (from > job->currentIndex()) {
4317 job->setEndIndex(from - 1);
4319 Q_ASSERT(job->currentIndex() <= job->endIndex());
4321 if (to < job->endIndex()) {
4328 auto newJob =
new ViewItemJob(from, job->endIndex() - count, job->chunkTimeout(), job->idleInterval(), job->messageCheckCount());
4330 Q_ASSERT(newJob->currentIndex() < newJob->endIndex());
4334 mViewItemJobs.insert(idx, newJob);
4341 if (to >= job->endIndex()) {
4350 job->setCurrentIndex(job->endIndex() + 1);
4355 if (to >= job->currentIndex()) {
4360 job->setCurrentIndex(from);
4361 job->setEndIndex(job->endIndex() - count);
4363 Q_ASSERT(job->currentIndex() <= job->endIndex());
4369 job->setCurrentIndex(job->currentIndex() - count);
4370 job->setEndIndex(job->endIndex() - count);
4375 auto invalidatedIndexes = mInvariantRowMapper->modelRowsRemoved(from, count);
4377 if (invalidatedIndexes) {
4384 ViewItemJob *job = mViewItemJobs.at(jobCount - 1);
4385 if (job->currentPass() == ViewItemJob::Pass1Cleanup) {
4386 if ((job->currentIndex() <= job->endIndex()) && job->invariantIndexList()) {
4389 *(job->invariantIndexList()) += *invalidatedIndexes;
4390 job->setEndIndex(job->endIndex() + invalidatedIndexes->count());
4391 delete invalidatedIndexes;
4392 invalidatedIndexes =
nullptr;
4397 if (invalidatedIndexes) {
4402 auto job =
new ViewItemJob(ViewItemJob::Pass1Cleanup, invalidatedIndexes, 100, 50, 10);
4403 mViewItemJobs.append(job);
4406 if (!mFillStepTimer.isActive()) {
4407 mFillStepTimer.start(mViewItemJobStepIdleInterval);
4412void ModelPrivate::slotStorageModelLayoutChanged()
4414 qCDebug(MESSAGELIST_LOG) <<
"Storage model layout changed";
4416 q->setStorageModel(mStorageModel);
4417 qCDebug(MESSAGELIST_LOG) <<
"Storage model layout changed done";
4422 Q_ASSERT(mStorageModel);
4425 int to = toIndex.
row();
4427 Q_ASSERT(from <= to);
4429 int count = (to - from) + 1;
4431 int jobCount = mViewItemJobs.count();
4435 auto indexesThatNeedUpdate = mInvariantRowMapper->modelIndexRowRangeToModelInvariantIndexList(from, count);
4437 if (indexesThatNeedUpdate) {
4444 ViewItemJob *job = mViewItemJobs.at(jobCount - 1);
4445 if (job->currentPass() == ViewItemJob::Pass1Update) {
4446 if ((job->currentIndex() <= job->endIndex()) && job->invariantIndexList()) {
4448 *(job->invariantIndexList()) += *indexesThatNeedUpdate;
4449 job->setEndIndex(job->endIndex() + indexesThatNeedUpdate->count());
4450 delete indexesThatNeedUpdate;
4451 indexesThatNeedUpdate =
nullptr;
4456 if (indexesThatNeedUpdate) {
4459 auto job =
new ViewItemJob(ViewItemJob::Pass1Update, indexesThatNeedUpdate, 100, 50, 10);
4460 mViewItemJobs.append(job);
4463 if (!mFillStepTimer.isActive()) {
4464 mFillStepTimer.start(mViewItemJobStepIdleInterval);
4469void ModelPrivate::slotStorageModelHeaderDataChanged(
Qt::Orientation,
int,
int)
4471 if (mStorageModelContainsOutboundMessages != mStorageModel->containsOutboundMessages()) {
4472 mStorageModelContainsOutboundMessages = mStorageModel->containsOutboundMessages();
4473 Q_EMIT q->headerDataChanged(
Qt::Horizontal, 0, q->columnCount());
4483 Q_ASSERT(d->mModelForItemFunctions);
4499 if (
static_cast<MessageItem *
>(it)->aboutToBeRemoved()) {
4521 return storageModel()->mimeData(msgs);
4526 return d->mRootItem;
4536 if (!d->mStorageModel) {
4539 auto idx = d->mInvariantRowMapper->modelIndexRowToModelInvariantIndex(row);
4549 if (!d->mPersistentSetManager) {
4553 MessageItemSetReference ref = d->mPersistentSetManager->createSet();
4554 for (
const auto mi : items) {
4555 d->mPersistentSetManager->addMessageItem(ref, mi);
4563 if (d->mPersistentSetManager) {
4564 return d->mPersistentSetManager->messageItems(ref);
4571 if (!d->mPersistentSetManager) {
4575 d->mPersistentSetManager->removeSet(ref);
4577 if (d->mPersistentSetManager->setCount() < 1) {
4578 delete d->mPersistentSetManager;
4579 d->mPersistentSetManager =
nullptr;
4583#include "moc_model.cpp"
static const MessageStatus statusWatched()
static const MessageStatus statusIgnored()
A set of aggregation options that can be applied to the MessageList::Model in a single shot.
@ GroupBySender
Group by sender, always.
@ NoGrouping
Don't group messages at all.
@ GroupByDateRange
Use smart (thread leader) date ranges ("Today","Yesterday","Last Week"...)
@ GroupBySenderOrReceiver
Group by sender (incoming) or receiver (outgoing) field.
@ GroupByDate
Group the messages by the date of the thread leader.
@ GroupByReceiver
Group by receiver, always.
@ NeverExpandGroups
Never expand groups during a view fill algorithm.
@ ExpandRecentGroups
Makes sense only with GroupByDate or GroupByDateRange.
@ AlwaysExpandGroups
All groups are expanded as they are inserted.
@ NoThreading
Perform no threading at all.
@ PerfectReferencesAndSubject
Thread by all of the above and try to match subjects too.
@ PerfectOnly
Thread by "In-Reply-To" field only.
@ PerfectAndReferences
Thread by "In-Reply-To" and "References" fields.
@ AlwaysExpandThreads
Expand all threads (this might be very slow)
@ ExpandThreadsWithUnreadOrImportantMessages
Expand threads with "hot" messages (this includes new, unread, important, todo)
@ ExpandThreadsWithNewMessages
DEPRECATED. New message status no longer exists.
@ ExpandThreadsWithUnreadMessages
Expand threads with unread messages (this includes new)
@ NeverExpandThreads
Never expand any thread, this is fast.
@ TopmostMessage
The thread grouping is computed from the topmost message (very similar to least recent,...
@ MostRecentMessage
The thread grouping is computed from the most recent message.
@ FavorSpeed
Do larger chunks of work, zero intervals between chunks.
@ FavorInteractivity
Do small chunks of work, small intervals between chunks to allow for UI event processing.
@ BatchNoInteractivity
Do one large chunk, no interactivity at all.
This class is responsible of matching messages that should be displayed in the View.
A single item of the MessageList tree managed by MessageList::Model.
const QString & receiver() const
Returns the receiver associated to this item.
size_t size() const
Returns the size of this item (size of the Message, mainly)
@ ExpandNeeded
Must expand when this item becomes viewable.
@ NoExpandNeeded
No expand needed at all.
@ ExpandExecuted
Item already expanded.
bool useReceiver() const
Returns whether sender or receiver is supposed to be displayed.
const Akonadi::MessageStatus & status() const
Returns the status associated to this Item.
InitialExpandStatus initialExpandStatus() const
The initial expand status we have to honor when attaching to the viewable root.
void initialSetup(time_t date, size_t size, const QString &sender, const QString &receiver, bool useReceiver)
This is meant to be called right after the constructor.
bool hasAncestor(const Item *it) const
Return true if Item pointed by it is an ancestor of this item (that is, if it is its parent,...
time_t date() const
Returns the date of this item.
time_t maxDate() const
Returns the maximum date in the subtree originating from this item.
Type type() const
Returns the type of this item.
const QString & sender() const
Returns the sender associated to this item.
QString displayReceiver() const
Display receiver.
void setStatus(Akonadi::MessageStatus status)
Sets the status associated to this Item.
void takeChildItem(Model *model, Item *child)
Removes a child from this item's child list without deleting it.
QString displaySenderOrReceiver() const
Display sender or receiver.
Item * parent() const
Returns the parent Item in the tree, or 0 if this item isn't attached to the tree.
void setParent(Item *pParent)
Sets the parent for this item.
void setInitialExpandStatus(InitialExpandStatus initialExpandStatus)
Set the initial expand status we have to honor when attaching to the viewable root.
@ Message
This item is a MessageItem.
@ InvisibleRoot
This item is just Item and it's the only InvisibleRoot per Model.
@ GroupHeader
This item is a GroupHeaderItem.
int childItemCount() const
Returns the number of children of this Item.
const QString & subject() const
Returns the subject associated to this Item.
QString displaySender() const
Display sender.
bool isViewable() const
Is this item attached to the viewable root ?
This class manages sets of messageitem references.
@ NonThreadable
this message does not look as being threadable
@ ImperfectParentFound
this message found an imperfect parent to attach to (might be fixed later)
@ ParentMissing
this message might belong to a thread but its parent is actually missing
@ PerfectParentFound
this message found a perfect parent to attach to
This class is an optimizing helper for dealing with large flat QAbstractItemModel objects.
void setTheme(const Theme *theme)
Sets the Theme.
QMimeData * mimeData(const QModelIndexList &indexes) const override
Called when user initiates a drag from the messagelist.
void setStorageModel(StorageModel *storageModel, PreSelectionMode preSelectionMode=PreSelectLastSelected)
Sets the storage model from that the messages to be displayed should be fetched.
MessageItem * messageItemByStorageRow(int row) const
Returns the message item that is at the current storage row index or zero if no such storage item is ...
QList< MessageItem * > persistentSetCurrentMessageItemList(MessageItemSetReference ref)
Returns the list of MessageItems that are still existing in the set pointed by the specified referenc...
StorageModel * storageModel() const
Returns the StorageModel currently set.
MessageItemSetReference createPersistentSet(const QList< MessageItem * > &items)
Creates a persistent set for the specified MessageItems and returns its reference.
Item * rootItem() const
Returns the hidden root item that all the messages are (or will be) attached to.
void setAggregation(const Aggregation *aggregation)
Sets the Aggregation mode.
void setPreSelectionMode(PreSelectionMode preSelect)
Sets the pre-selection mode.
~Model() override
Destroys the mighty model along with the tree of items it manages.
void setSortOrder(const SortOrder *sortOrder)
Sets the sort order.
Model(View *pParent)
Creates the mighty Model attached to the specified View.
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
const SortOrder * sortOrder() const
Returns the sort order.
bool isLoading() const
Returns true if the view is currently loading, that is it's in the first (possibly lengthy) job batch...
void setFilter(const Filter *filter)
Sets the Filter to be applied on messages.
void deletePersistentSet(MessageItemSetReference ref)
Deletes the persistent set pointed by the specified reference.
A class which holds information about sorting, e.g.
SortDirection
The "generic" sort direction: used for groups and for messages If you add values here please look at ...
@ SortMessagesBySize
Sort the messages by size.
@ SortMessagesByAttachmentStatus
Sort the messages By "Important" flags of status.
@ SortMessagesByDateTime
Sort the messages by date and time.
@ SortMessagesByActionItemStatus
Sort the messages by the "Action Item" flag of status.
@ NoMessageSorting
Don't sort the messages at all.
@ SortMessagesBySenderOrReceiver
Sort the messages by sender or receiver.
@ SortMessagesBySender
Sort the messages by sender.
@ SortMessagesByReceiver
Sort the messages by receiver.
@ SortMessagesByDateTimeOfMostRecent
Sort the messages by date and time of the most recent message in subtree.
@ SortMessagesByUnreadStatus
Sort the messages by the "Unread" flags of status.
@ SortMessagesBySubject
Sort the messages by subject.
@ SortGroupsBySenderOrReceiver
Sort groups by sender or receiver (makes sense only with GroupBySenderOrReceiver)
@ SortGroupsBySender
Sort groups by sender (makes sense only with GroupBySender)
@ SortGroupsByReceiver
Sort groups by receiver (makes sense only with GroupByReceiver)
@ SortGroupsByDateTimeOfMostRecent
Sort groups by date/time of the most recent message.
@ NoGroupSorting
Don't sort the groups at all, add them as they come in.
@ SortGroupsByDateTime
Sort groups by date/time of the group.
The QAbstractItemModel based interface that you need to provide for your storage to work with Message...
@ PerfectThreadingOnly
Only the data for messageIdMD5 and inReplyToMD5 is needed.
@ PerfectThreadingReferencesAndSubject
All of the above plus subject stuff.
@ PerfectThreadingPlusReferences
messageIdMD5, inReplyToMD5, referencesIdMD5
The Theme class defines the visual appearance of the MessageList.
The MessageList::View is the real display of the message list.
Q_SCRIPTABLE CaptureState status()
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
AKONADICORE_EXPORT Collection fromIndex(const QModelIndex &index)
char * toString(const EngineQuery &query)
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
bool isValid(QStringView ifopt)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
The implementation independent part of the MessageList library.
PreSelectionMode
Pre-selection is the action of automatically selecting a message just after the folder has finished l...
QModelIndex createIndex(int row, int column, const void *ptr) const const
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles)
void layoutChanged(const QList< QPersistentModelIndex > &parents, QAbstractItemModel::LayoutChangeHint hint)
void rowsInserted(const QModelIndex &parent, int first, int last)
void rowsRemoved(const QModelIndex &parent, int first, int last)
bool isEmpty() const const
int dayOfWeek() const const
qint64 daysTo(QDate d) const const
bool isValid(int year, int month, int day)
void setSecsSinceEpoch(qint64 secs)
qint64 elapsed() const const
void restoreOverrideCursor()
void setOverrideCursor(const QCursor &cursor)
QIcon fromTheme(const QString &name)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
QString standaloneDayName(int day, FormatType type) const const
QString standaloneMonthName(int month, FormatType type) const const
QString toString(QDate date, FormatType format) const const
void * internalPointer() const const
bool isValid() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
QList< QQuickItem * > childItems() const const
iterator insert(const T &value)
QByteArray toUtf8() const const
QVariant fromValue(T &&value)