28#include "core/model.h"
29#include "core/filter.h"
30#include "core/groupheaderitem.h"
31#include "core/item_p.h"
32#include "core/messageitem.h"
33#include "core/messageitemsetmanager.h"
34#include "core/model_p.h"
35#include "core/modelinvariantrowmapper.h"
36#include "core/storagemodelbase.h"
37#include "core/theme.h"
39#include "messagelist_debug.h"
40#include <config-messagelist.h>
42#include "MessageCore/StringUtil"
43#include <Akonadi/Item>
44#include <Akonadi/MessageStatus>
46#include <KLocalizedString>
48#include <QApplication>
50#include <QElapsedTimer>
59using namespace std::chrono_literals;
65Q_GLOBAL_STATIC(
QTimer, _k_heartBeatTimer)
184 int mMessageCheckCount;
198 ViewItemJob(
int startIndex,
int endIndex,
int chunkTimeout,
int idleInterval,
int messageCheckCount,
bool disconnectUI =
false)
199 : mStartIndex(startIndex)
200 , mCurrentIndex(startIndex)
201 , mEndIndex(endIndex)
202 , mInvariantIndexList(nullptr)
203 , mChunkTimeout(chunkTimeout)
204 , mIdleInterval(idleInterval)
205 , mMessageCheckCount(messageCheckCount)
206 , mCurrentPass(Pass1Fill)
207 , mDisconnectUI(disconnectUI)
217 , mEndIndex(invariantIndexList->count() - 1)
218 , mInvariantIndexList(invariantIndexList)
219 , mChunkTimeout(chunkTimeout)
220 , mIdleInterval(idleInterval)
221 , mMessageCheckCount(messageCheckCount)
223 , mDisconnectUI(false)
229 delete mInvariantIndexList;
233 [[nodiscard]]
int startIndex()
const
238 void setStartIndex(
int startIndex)
240 mStartIndex = startIndex;
241 mCurrentIndex = startIndex;
244 [[nodiscard]]
int currentIndex()
const
246 return mCurrentIndex;
249 void setCurrentIndex(
int currentIndex)
251 mCurrentIndex = currentIndex;
254 [[nodiscard]]
int endIndex()
const
259 void setEndIndex(
int endIndex)
261 mEndIndex = endIndex;
264 [[nodiscard]] Pass currentPass()
const
269 void setCurrentPass(Pass pass)
274 [[nodiscard]]
int idleInterval()
const
276 return mIdleInterval;
279 [[nodiscard]]
int chunkTimeout()
const
281 return mChunkTimeout;
284 [[nodiscard]]
int messageCheckCount()
const
286 return mMessageCheckCount;
291 return mInvariantIndexList;
294 [[nodiscard]]
bool disconnectUI()
const
296 return mDisconnectUI;
306 , d(new ModelPrivate(this))
308 d->mRecursionCounterForReset = 0;
309 d->mStorageModel =
nullptr;
311 d->mAggregation =
nullptr;
313 d->mSortOrder =
nullptr;
314 d->mFilter =
nullptr;
315 d->mPersistentSetManager =
nullptr;
316 d->mInLengthyJobBatch =
false;
317 d->mLastSelectedMessageInFolder =
nullptr;
321 d->mRootItem->setViewable(
nullptr,
true);
323 d->mFillStepTimer.setSingleShot(
true);
325 d->mModelForItemFunctions =
this;
327 d->viewItemJobStep();
330 d->mCachedTodayLabel =
i18n(
"Today");
331 d->mCachedYesterdayLabel =
i18n(
"Yesterday");
332 d->mCachedUnknownLabel =
i18nc(
"Unknown date",
"Unknown");
333 d->mCachedLastWeekLabel =
i18n(
"Last Week");
334 d->mCachedTwoWeeksAgoLabel =
i18n(
"Two Weeks Ago");
335 d->mCachedThreeWeeksAgoLabel =
i18n(
"Three Weeks Ago");
336 d->mCachedFourWeeksAgoLabel =
i18n(
"Four Weeks Ago");
337 d->mCachedFiveWeeksAgoLabel =
i18n(
"Five Weeks Ago");
342 d->checkIfDateChanged();
355 d->mOldestItem =
nullptr;
356 d->mNewestItem =
nullptr;
357 d->clearUnassignedMessageLists();
358 d->clearOrphanChildrenHash();
359 d->clearThreadingCacheReferencesIdMD5ToMessageItem();
360 d->clearThreadingCacheMessageSubjectMD5ToMessageItem();
361 delete d->mPersistentSetManager;
364 delete d->mInvariantRowMapper;
370 d->mAggregation = aggregation;
386 return d->mSortOrder;
394 connect(d->mFilter, &Filter::finished,
this, [
this]() {
395 d->slotApplyFilter();
399 d->slotApplyFilter();
402void ModelPrivate::slotApplyFilter()
404 auto childList = mRootItem->childItems();
412 for (
const auto child :
std::as_const(*childList)) {
413 applyFilterToSubtree(child, idx);
419bool ModelPrivate::applyFilterToSubtree(
Item *item,
const QModelIndex &parentIndex)
424 if (!mModelForItemFunctions) {
425 qCWarning(MESSAGELIST_LOG) <<
"Cannot apply filter, the UI must be not disconnected.";
429 Q_ASSERT(item->isViewable());
435 bool childrenMatch =
false;
440 for (
const auto child :
std::as_const(*childList)) {
441 if (applyFilterToSubtree(child, thisIndex)) {
442 childrenMatch =
true;
448 mView->setRowHidden(thisIndex.
row(), parentIndex,
false);
453 mView->setRowHidden(thisIndex.
row(), parentIndex,
false);
455 if (!mView->isExpanded(thisIndex)) {
456 mView->expand(thisIndex);
463 mView->setRowHidden(thisIndex.
row(), parentIndex,
false);
469 mView->setRowHidden(thisIndex.
row(), parentIndex,
true);
474int Model::columnCount(
const QModelIndex &parent)
const
479 if (
parent.column() > 0) {
482 return d->mTheme->columns().count();
514 return QStringLiteral(
"message/rfc822");
522 return mItem->accessibleText(d->mTheme, index.
column());
527 auto hItem =
static_cast<GroupHeaderItem *
>(item);
528 return hItem->label();
543 auto column = d->mTheme->column(section);
548 if (d->mStorageModel && column->isSenderOrReceiver() && (role ==
Qt::DisplayRole)) {
549 if (d->mStorageModelContainsOutboundMessages) {
569 if (!d->mModelForItemFunctions) {
579 if (item != d->mRootItem) {
585 const int index =
par->indexOfChildItem(item);
594 if (!d->mModelForItemFunctions) {
598#ifdef READD_THIS_IF_YOU_WANT_TO_PASS_MODEL_TEST
606 item =
static_cast<const Item *
>(
parent.internalPointer());
614 if (
parent.column() > 0) {
618 Item *child = item->childItem(row);
627 Q_ASSERT(d->mModelForItemFunctions);
641 return index(
par, 0);
644int Model::rowCount(
const QModelIndex &parent)
const
646 if (!d->mModelForItemFunctions) {
652 item =
static_cast<const Item *
>(
parent.internalPointer());
660 if (!item->isViewable()) {
664 return item->childItemCount();
667class RecursionPreventer
670 RecursionPreventer(
int &counter)
676 ~RecursionPreventer()
681 [[nodiscard]]
bool isRecursive()
const
692 return d->mStorageModel;
695void ModelPrivate::clear()
697 q->beginResetModel();
698 if (mFillStepTimer.isActive()) {
699 mFillStepTimer.stop();
703 mPreSelectionMode = PreSelectNone;
704 mLastSelectedMessageInFolder =
nullptr;
705 mOldestItem =
nullptr;
706 mNewestItem =
nullptr;
710 mInvariantRowMapper->modelReset();
713 clearUnassignedMessageLists();
714 clearOrphanChildrenHash();
715 mGroupHeaderItemHash.clear();
716 mGroupHeadersThatNeedUpdate.clear();
717 mThreadingCacheMessageIdMD5ToMessageItem.clear();
718 mThreadingCacheMessageInReplyToIdMD5ToMessageItem.clear();
719 clearThreadingCacheReferencesIdMD5ToMessageItem();
720 clearThreadingCacheMessageSubjectMD5ToMessageItem();
721 mViewItemJobStepChunkTimeout = 100;
722 mViewItemJobStepIdleInterval = 10;
723 mViewItemJobStepMessageCheckCount = 10;
724 delete mPersistentSetManager;
725 mPersistentSetManager =
nullptr;
726 mCurrentItemToRestoreAfterViewItemJobStep =
nullptr;
732 mRootItem->killAllChildItems();
737 mView->selectionModel()->clearSelection();
744 RecursionPreventer
preventer(d->mRecursionCounterForReset);
751 if (d->mStorageModel) {
753 std::for_each(d->mStorageModelConnections.cbegin(), d->mStorageModelConnections.cend(), [](
const QMetaObject::Connection &c) ->
bool {
754 return QObject::disconnect(c);
756 d->mStorageModelConnections.clear();
762 if (!d->mStorageModel) {
770 if (d->mThreadingCache.isEnabled() && !
isReload) {
771 d->mThreadingCache.save();
774 qCDebug(
MESSAGELIST_LOG) <<
"Identical folder reloaded, not saving old threading cache";
776 qCDebug(
MESSAGELIST_LOG) <<
"Threading disabled in previous folder, not saving threading cache";
782 d->mThreadingCache.setEnabled(
true);
783 d->mThreadingCache.load(d->mStorageModel->id(), d->mAggregation);
787 d->mThreadingCache.setEnabled(
false);
788 qCDebug(
MESSAGELIST_LOG) <<
"Threading disabled in folder" << d->mStorageModel->id() <<
", not using threading cache";
792 d->mStorageModelContainsOutboundMessages = d->mStorageModel->containsOutboundMessages();
794 d->mStorageModelConnections = {
connect(d->mStorageModel,
798 d->slotStorageModelRowsInserted(parent, first, last);
804 d->slotStorageModelRowsRemoved(parent, first, last);
810 d->slotStorageModelLayoutChanged();
816 d->slotStorageModelLayoutChanged();
822 d->slotStorageModelDataChanged(topLeft, bottomRight);
825 d->slotStorageModelHeaderDataChanged(orientation, first, last);
828 if (d->mStorageModel->rowCount() == 0) {
908 (d->mStorageModel->initialUnreadRowCountGuess() < 1000)));
910 switch (d->mAggregation->fillViewStrategy()) {
918 d->mViewItemJobs.append(
job1);
921 auto job2 =
new ViewItemJob(0, d->mStorageModel->rowCount() - 1001, 100, 50, 10,
false);
922 d->mViewItemJobs.append(
job2);
931 d->mViewItemJobs.append(job);
940 d->mViewItemJobs.append(
job1);
941 auto job2 =
new ViewItemJob(0, d->mStorageModel->rowCount() - 1001, 200, 0, 10,
false);
942 d->mViewItemJobs.append(
job2);
947 d->mViewItemJobs.append(job);
953 d->mViewItemJobs.append(job);
964 d->viewItemJobStep();
967void ModelPrivate::checkIfDateChanged()
981 if (!mStorageModel) {
989 if (!mViewItemJobs.isEmpty()) {
998 q->setStorageModel(mStorageModel, PreSelectLastSelected);
1004 d->mLastSelectedMessageInFolder =
nullptr;
1018void ModelPrivate::clearUnassignedMessageLists()
1035 Q_ASSERT((mOldestItem ==
nullptr) && (mNewestItem ==
nullptr));
1037 if (!mUnassignedMessageListForPass2.isEmpty()) {
1048 for (
const auto mi :
std::as_const(mUnassignedMessageListForPass2)) {
1049 if (!mi->parent()) {
1054 for (
const auto mi :
std::as_const(parentless)) {
1058 mUnassignedMessageListForPass2.clear();
1060 mUnassignedMessageListForPass3.clear();
1061 mUnassignedMessageListForPass4.clear();
1067 if (!mUnassignedMessageListForPass3.isEmpty()) {
1074 if (!mUnassignedMessageListForPass4.isEmpty()) {
1078 for (
const auto mi :
std::as_const(mUnassignedMessageListForPass3)) {
1079 if (!mi->parent()) {
1080 itemsToDelete.
insert(mi);
1083 for (
const auto mi :
std::as_const(mUnassignedMessageListForPass4)) {
1084 if (!mi->parent()) {
1085 itemsToDelete.
insert(mi);
1088 for (
const auto mi :
std::as_const(itemsToDelete)) {
1092 mUnassignedMessageListForPass3.clear();
1093 mUnassignedMessageListForPass4.clear();
1100 for (
const auto mi :
std::as_const(mUnassignedMessageListForPass3)) {
1101 if (!mi->parent()) {
1105 for (
const auto mi :
std::as_const(parentless)) {
1109 mUnassignedMessageListForPass3.clear();
1114 if (!mUnassignedMessageListForPass4.isEmpty()) {
1119 for (
const auto mi :
std::as_const(mUnassignedMessageListForPass4)) {
1120 if (!mi->parent()) {
1124 for (
const auto mi :
std::as_const(parentless)) {
1128 mUnassignedMessageListForPass4.clear();
1133void ModelPrivate::clearThreadingCacheReferencesIdMD5ToMessageItem()
1135 qDeleteAll(mThreadingCacheMessageReferencesIdMD5ToMessageItem);
1136 mThreadingCacheMessageReferencesIdMD5ToMessageItem.clear();
1139void ModelPrivate::clearThreadingCacheMessageSubjectMD5ToMessageItem()
1141 qDeleteAll(mThreadingCacheMessageSubjectMD5ToMessageItem);
1142 mThreadingCacheMessageSubjectMD5ToMessageItem.clear();
1145void ModelPrivate::clearOrphanChildrenHash()
1147 qDeleteAll(mOrphanChildrenHash);
1148 mOrphanChildrenHash.clear();
1151void ModelPrivate::clearJobList()
1153 if (mViewItemJobs.isEmpty()) {
1157 if (mInLengthyJobBatch) {
1158 mInLengthyJobBatch =
false;
1161 qDeleteAll(mViewItemJobs);
1162 mViewItemJobs.clear();
1164 mModelForItemFunctions = q;
1167void ModelPrivate::attachGroup(GroupHeaderItem *ghi)
1170 if (((ghi)->childItemCount() > 0)
1171 && (ghi)->isViewable()
1172 && mModelForItemFunctions
1173 && mView->isExpanded(q->index(ghi, 0))
1175 saveExpandedStateOfSubtree(ghi);
1188#define INSERT_GROUP_WITH_COMPARATOR(_ItemComparator) \
1189 switch (mSortOrder->groupSortDirection()) { \
1190 case SortOrder::Ascending: \
1191 mRootItem->d_ptr->insertChildItem<_ItemComparator, true>(mModelForItemFunctions, ghi); \
1193 case SortOrder::Descending: \
1194 mRootItem->d_ptr->insertChildItem<_ItemComparator, false>(mModelForItemFunctions, ghi); \
1197 mRootItem->appendChildItem(mModelForItemFunctions, ghi); \
1201 switch (mSortOrder->groupSorting()) {
1203 INSERT_GROUP_WITH_COMPARATOR(ItemDateComparator)
1206 INSERT_GROUP_WITH_COMPARATOR(ItemMaxDateComparator)
1209 INSERT_GROUP_WITH_COMPARATOR(ItemSenderOrReceiverComparator)
1212 INSERT_GROUP_WITH_COMPARATOR(ItemSenderComparator)
1215 INSERT_GROUP_WITH_COMPARATOR(ItemReceiverComparator)
1218 mRootItem->appendChildItem(mModelForItemFunctions, ghi);
1221 mRootItem->appendChildItem(mModelForItemFunctions, ghi);
1227 if (mModelForItemFunctions) {
1228 syncExpandedStateOfSubtree(ghi);
1235 Q_ASSERT(mModelForItemFunctions);
1241void ModelPrivate::saveExpandedStateOfSubtree(
Item *root)
1243 Q_ASSERT(mModelForItemFunctions);
1252 for (
const auto mi :
std::as_const(*children)) {
1253 if (mi->childItemCount() > 0
1255 && mView->isExpanded(q->index(mi, 0))) {
1256 saveExpandedStateOfSubtree(mi);
1261void ModelPrivate::syncExpandedStateOfSubtree(
Item *root)
1263 Q_ASSERT(mModelForItemFunctions);
1281 for (
const auto mi :
std::as_const(*children)) {
1283 if (mi->childItemCount() > 0) {
1284 syncExpandedStateOfSubtree(mi);
1290void ModelPrivate::attachMessageToGroupHeader(
MessageItem *mi)
1296 switch (mAggregation->grouping()) {
1309 const int daysInWeek = 7;
1310 if (dDate.
isValid() && mTodayDate.isValid()) {
1311 daysAgo = dDate.
daysTo(mTodayDate);
1315 || (
static_cast<uint
>(date) ==
static_cast<uint
>(-1))) {
1316 groupLabel = mCachedUnknownLabel;
1317 }
else if (daysAgo == 0) {
1318 groupLabel = mCachedTodayLabel;
1319 }
else if (daysAgo == 1) {
1320 groupLabel = mCachedYesterdayLabel;
1321 }
else if (daysAgo > 1 && daysAgo < daysInWeek) {
1322 auto dayName = mCachedDayNameLabel.find(dDate.
dayOfWeek());
1323 if (dayName == mCachedDayNameLabel.end()) {
1326 groupLabel = *dayName;
1329 }
else if (dDate.
month() == mTodayDate.month()
1330 && dDate.
year() == mTodayDate.year()) {
1331 const int startOfWeekDaysAgo = (daysInWeek + mTodayDate.dayOfWeek() -
QLocale().
firstDayOfWeek()) % daysInWeek;
1332 const int weeksAgo = ((daysAgo - startOfWeekDaysAgo) / daysInWeek) + 1;
1338 groupLabel = mCachedLastWeekLabel;
1341 groupLabel = mCachedTwoWeeksAgoLabel;
1344 groupLabel = mCachedThreeWeeksAgoLabel;
1347 groupLabel = mCachedFourWeeksAgoLabel;
1350 groupLabel = mCachedFiveWeeksAgoLabel;
1353 groupLabel = mCachedUnknownLabel;
1355 }
else if (dDate.
year() == mTodayDate.year()) {
1356 auto monthName = mCachedMonthNameLabel.find(dDate.
month());
1357 if (monthName == mCachedMonthNameLabel.end()) {
1360 groupLabel = *monthName;
1362 auto monthName = mCachedMonthNameLabel.find(dDate.
month());
1363 if (monthName == mCachedMonthNameLabel.end()) {
1366 groupLabel =
i18nc(
"Message Aggregation Group Header: Month name and Year number",
1391 attachMessageToParent(mRootItem, mi);
1396 attachMessageToParent(mRootItem, mi);
1400 GroupHeaderItem *ghi;
1402 ghi = mGroupHeaderItemHash.value(groupLabel,
nullptr);
1406 ghi =
new GroupHeaderItem(groupLabel);
1409 switch (mAggregation->groupExpandPolicy()) {
1419 if (mViewItemJobStepStartTime > ghi->
date()) {
1420 if ((mViewItemJobStepStartTime - ghi->
date()) < (3600 * 72)) {
1424 if ((ghi->
date() - mViewItemJobStepStartTime) < (3600 * 72)) {
1434 attachMessageToParent(ghi, mi);
1438 mGroupHeaderItemHash.insert(groupLabel, ghi);
1447 if (mi->
parent() == ghi) {
1451 attachMessageToParent(ghi, mi);
1455 mThreadingCache.updateParent(mi,
nullptr);
1476 bool bMessageWasThreadable =
false;
1486 pParent = mThreadingCacheMessageIdMD5ToMessageItem.value(md5,
nullptr);
1493 qCWarning(MESSAGELIST_LOG) <<
"Circular In-Reply-To reference loop detected in the message tree";
1502 bMessageWasThreadable =
true;
1522 md5 = mi->referencesIdMD5();
1524 pParent = mThreadingCacheMessageIdMD5ToMessageItem.value(md5,
nullptr);
1531 qCWarning(MESSAGELIST_LOG) <<
"Circular reference loop detected in the message tree";
1539 auto messagesWithTheSameReferences = mThreadingCacheMessageReferencesIdMD5ToMessageItem.value(md5,
nullptr);
1540 if (messagesWithTheSameReferences) {
1541 Q_ASSERT(!messagesWithTheSameReferences->isEmpty());
1543 pParent = messagesWithTheSameReferences->first();
1551 bMessageWasThreadable =
true;
1577 qCDebug(MESSAGELIST_LOG) <<
"Threading cache part dump";
1579 qCDebug(MESSAGELIST_LOG) <<
"Iterator pointing to end of the list";
1581 qCDebug(MESSAGELIST_LOG) <<
"Iterator pointing to " << *iter <<
" subject [" << (*iter)->subject() <<
"] date [" << (*iter)->date() <<
"]";
1585 qCDebug(MESSAGELIST_LOG) <<
"List element " << *it <<
" subject [" << (*it)->subject() <<
"] date [" << (*it)->date() <<
"]";
1588 qCDebug(MESSAGELIST_LOG) <<
"End of threading cache part dump";
1593 qCDebug(MESSAGELIST_LOG) <<
"Threading cache part dump";
1596 qCDebug(MESSAGELIST_LOG) <<
"List element " << *it <<
" subject [" << (*it)->subject() <<
"] date [" << (*it)->date() <<
"]";
1599 qCDebug(MESSAGELIST_LOG) <<
"End of threading cache part dump";
1605class MessageLessThanByDate
1621void ModelPrivate::addMessageToReferencesBasedThreadingCache(
MessageItem *mi)
1630 auto messagesWithTheSameReference = mThreadingCacheMessageReferencesIdMD5ToMessageItem.value(mi->referencesIdMD5(),
nullptr);
1632 if (!messagesWithTheSameReference) {
1634 mThreadingCacheMessageReferencesIdMD5ToMessageItem.insert(mi->referencesIdMD5(), messagesWithTheSameReference);
1635 messagesWithTheSameReference->append(mi);
1640 Q_ASSERT(!messagesWithTheSameReference->contains(mi));
1643 auto it = std::lower_bound(messagesWithTheSameReference->begin(), messagesWithTheSameReference->end(), mi, MessageLessThanByDate());
1644 messagesWithTheSameReference->insert(it, mi);
1647void ModelPrivate::removeMessageFromReferencesBasedThreadingCache(
MessageItem *mi)
1652 auto messagesWithTheSameReference = mThreadingCacheMessageReferencesIdMD5ToMessageItem.value(mi->referencesIdMD5(),
nullptr);
1655 Q_ASSERT(messagesWithTheSameReference);
1658 auto it = std::lower_bound(messagesWithTheSameReference->begin(), messagesWithTheSameReference->end(), mi, MessageLessThanByDate());
1661 Q_ASSERT(it != messagesWithTheSameReference->end());
1664 Q_ASSERT(*it == mi);
1667 messagesWithTheSameReference->erase(it);
1670 if (messagesWithTheSameReference->isEmpty()) {
1671 mThreadingCacheMessageReferencesIdMD5ToMessageItem.remove(mi->referencesIdMD5());
1672 delete messagesWithTheSameReference;
1676void ModelPrivate::addMessageToSubjectBasedThreadingCache(
MessageItem *mi)
1686 auto messagesWithTheSameStrippedSubject = mThreadingCacheMessageSubjectMD5ToMessageItem.value(mi->strippedSubjectMD5(),
nullptr);
1688 if (!messagesWithTheSameStrippedSubject) {
1691 mThreadingCacheMessageSubjectMD5ToMessageItem.insert(mi->strippedSubjectMD5(), messagesWithTheSameStrippedSubject);
1692 messagesWithTheSameStrippedSubject->append(mi);
1697 Q_ASSERT(!messagesWithTheSameStrippedSubject->contains(mi));
1700 auto it = std::lower_bound(messagesWithTheSameStrippedSubject->begin(), messagesWithTheSameStrippedSubject->end(), mi, MessageLessThanByDate());
1701 messagesWithTheSameStrippedSubject->insert(it, mi);
1704void ModelPrivate::removeMessageFromSubjectBasedThreadingCache(
MessageItem *mi)
1712 auto messagesWithTheSameStrippedSubject = mThreadingCacheMessageSubjectMD5ToMessageItem.value(mi->strippedSubjectMD5(),
nullptr);
1715 Q_ASSERT(messagesWithTheSameStrippedSubject);
1718 auto it = std::lower_bound(messagesWithTheSameStrippedSubject->begin(), messagesWithTheSameStrippedSubject->end(), mi, MessageLessThanByDate());
1721 Q_ASSERT(it != messagesWithTheSameStrippedSubject->end());
1724 Q_ASSERT(*it == mi);
1727 messagesWithTheSameStrippedSubject->erase(it);
1730 if (messagesWithTheSameStrippedSubject->isEmpty()) {
1731 mThreadingCacheMessageSubjectMD5ToMessageItem.remove(mi->strippedSubjectMD5());
1732 delete messagesWithTheSameStrippedSubject;
1746 Q_ASSERT(mi->subjectIsPrefixed());
1750 const QByteArray md5 = mi->strippedSubjectMD5();
1752 auto messagesWithTheSameStrippedSubject = mThreadingCacheMessageSubjectMD5ToMessageItem.value(md5,
nullptr);
1754 if (messagesWithTheSameStrippedSubject) {
1755 Q_ASSERT(!messagesWithTheSameStrippedSubject->isEmpty());
1759 auto maxTime = (time_t)0;
1768 for (
const auto it :
std::as_const(*messagesWithTheSameStrippedSubject)) {
1769 int delta = mi->
date() - it->date();
1789 if (delta < 3628899) {
1791 if ((maxTime < it->date())) {
1800 maxTime = it->date();
1827template<
class ItemComparator>
1830 if ((messageSortDirection == SortOrder::Ascending) || (parent->mType ==
Item::Message)) {
1831 return parent->childItemNeedsReSorting<ItemComparator,
true>(messageItem);
1833 return parent->childItemNeedsReSorting<ItemComparator,
false>(messageItem);
1836bool ModelPrivate::handleItemPropertyChanges(
int propertyChangeMask,
Item *parent,
Item *item)
1858 (propertyChangeMask & MaxDateChanged) &&
1862 (propertyChangeMask & DateChanged) &&
1869 mGroupHeadersThatNeedUpdate.insert(
static_cast<GroupHeaderItem *
>(item),
static_cast<GroupHeaderItem *
>(item));
1878 switch (mSortOrder->messageSorting()) {
1880 if (propertyChangeMask & DateChanged) {
1881 if (messageItemNeedsReSorting<ItemDateComparator>(mSortOrder->messageSortDirection(), parent->d_ptr,
static_cast<MessageItem *
>(item))) {
1882 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
1887 if (propertyChangeMask & MaxDateChanged) {
1888 if (messageItemNeedsReSorting<ItemMaxDateComparator>(mSortOrder->messageSortDirection(), parent->d_ptr,
static_cast<MessageItem *
>(item))) {
1889 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
1894 if (propertyChangeMask & ActionItemStatusChanged) {
1895 if (messageItemNeedsReSorting<ItemActionItemStatusComparator>(mSortOrder->messageSortDirection(),
1898 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
1903 if (propertyChangeMask & UnreadStatusChanged) {
1904 if (messageItemNeedsReSorting<ItemUnreadStatusComparator>(mSortOrder->messageSortDirection(),
1907 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
1911 case SortOrder::SortMessagesByImportantStatus:
1912 if (propertyChangeMask & ImportantStatusChanged) {
1913 if (messageItemNeedsReSorting<ItemImportantStatusComparator>(mSortOrder->messageSortDirection(),
1916 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
1921 if (propertyChangeMask & AttachmentStatusChanged) {
1922 if (messageItemNeedsReSorting<ItemAttachmentStatusComparator>(mSortOrder->messageSortDirection(),
1925 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
1945 (propertyChangeMask & MaxDateChanged) &&
1949 (propertyChangeMask & DateChanged) &&
1954 attachMessageToGroupHeader(
static_cast<MessageItem *
>(item));
1966 switch (mSortOrder->messageSorting()) {
1968 if (propertyChangeMask & DateChanged) {
1969 if (messageItemNeedsReSorting<ItemDateComparator>(mSortOrder->messageSortDirection(), parent->d_ptr,
static_cast<MessageItem *
>(item))) {
1970 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
1975 if (propertyChangeMask & MaxDateChanged) {
1976 if (messageItemNeedsReSorting<ItemMaxDateComparator>(mSortOrder->messageSortDirection(), parent->d_ptr,
static_cast<MessageItem *
>(item))) {
1977 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
1982 if (propertyChangeMask & ActionItemStatusChanged) {
1983 if (messageItemNeedsReSorting<ItemActionItemStatusComparator>(mSortOrder->messageSortDirection(),
1986 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
1991 if (propertyChangeMask & UnreadStatusChanged) {
1992 if (messageItemNeedsReSorting<ItemUnreadStatusComparator>(mSortOrder->messageSortDirection(), parent->d_ptr,
static_cast<MessageItem *
>(item))) {
1993 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
1997 case SortOrder::SortMessagesByImportantStatus:
1998 if (propertyChangeMask & ImportantStatusChanged) {
1999 if (messageItemNeedsReSorting<ItemImportantStatusComparator>(mSortOrder->messageSortDirection(), parent->d_ptr,
static_cast<MessageItem *
>(item))) {
2000 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
2005 if (propertyChangeMask & AttachmentStatusChanged) {
2006 if (messageItemNeedsReSorting<ItemAttachmentStatusComparator>(mSortOrder->messageSortDirection(),
2009 attachMessageToParent(parent,
static_cast<MessageItem *
>(item));
2021void ModelPrivate::messageDetachedUpdateParentProperties(
Item *oldParent,
MessageItem *mi)
2023 Q_ASSERT(oldParent);
2025 Q_ASSERT(oldParent != mRootItem);
2035 int propertyChangeMask;
2037 if ((mi->
maxDate() == oldParent->maxDate()) && oldParent->recomputeMaxDate()) {
2038 propertyChangeMask = MaxDateChanged;
2055 if (!handleItemPropertyChanges(propertyChangeMask, grandParent, oldParent)) {
2060 oldParent = grandParent;
2066 if (oldParent->childItemCount() == 0) {
2067 mGroupHeadersThatNeedUpdate.insert(
static_cast<GroupHeaderItem *
>(oldParent),
static_cast<GroupHeaderItem *
>(oldParent));
2072void ModelPrivate::propagateItemPropertiesToParent(
Item *item)
2076 Q_ASSERT(pParent != mRootItem);
2084 int propertyChangeMask;
2086 if (item->maxDate() > pParent->maxDate()) {
2087 pParent->setMaxDate(item->maxDate());
2088 propertyChangeMask = MaxDateChanged;
2106 if (!handleItemPropertyChanges(propertyChangeMask, grandParent, pParent)) {
2111 pParent = grandParent;
2115void ModelPrivate::attachMessageToParent(
Item *pParent,
MessageItem *mi, AttachOptions attachOptions)
2122 bool oldParentWasTheSame;
2128 oldParentWasTheSame = oldParent == pParent;
2159 if (((mi)->childItemCount() > 0)
2160 && mModelForItemFunctions
2161 && mView->isExpanded(q->index(mi, 0))
2163 saveExpandedStateOfSubtree(mi);
2169 oldParent->takeChildItem(mModelForItemFunctions, mi);
2171 if ((!oldParentWasTheSame) && (oldParent != mRootItem)) {
2172 messageDetachedUpdateParentProperties(oldParent, mi);
2176 oldParentWasTheSame =
false;
2187 if (!oldParentWasTheSame) {
2188 switch (mi->threadingStatus()) {
2190 if (!mi->inReplyToIdMD5().
isEmpty()) {
2191 mThreadingCacheMessageInReplyToIdMD5ToMessageItem.remove(mi->inReplyToIdMD5(), mi);
2193 if (attachOptions == StoreInCache && pParent->type() ==
Item::Message) {
2194 mThreadingCache.updateParent(mi,
static_cast<MessageItem *
>(pParent));
2199 if (!mi->inReplyToIdMD5().
isEmpty()) {
2200 if (!mThreadingCacheMessageInReplyToIdMD5ToMessageItem.contains(mi->inReplyToIdMD5(), mi)) {
2201 mThreadingCacheMessageInReplyToIdMD5ToMessageItem.insert(mi->inReplyToIdMD5(), mi);
2207 Q_ASSERT(!mThreadingCacheMessageInReplyToIdMD5ToMessageItem.contains(mi->inReplyToIdMD5(), mi));
2216 if ((pParent->status().toQInt32() & mCachedWatchedOrIgnoredStatusBits)
2220 if (pParent->status().isWatched()) {
2221 int row = mInvariantRowMapper->modelInvariantIndexToModelIndexRow(mi);
2224 }
else if (pParent->status().isIgnored()) {
2225 int row = mInvariantRowMapper->modelInvariantIndexToModelIndexRow(mi);
2241#define INSERT_MESSAGE_WITH_COMPARATOR(_ItemComparator) \
2242 if ((mSortOrder->messageSortDirection() == SortOrder::Ascending) || (pParent->type() == Item::Message)) { \
2243 pParent->d_ptr->insertChildItem<_ItemComparator, true>(mModelForItemFunctions, mi); \
2245 pParent->d_ptr->insertChildItem<_ItemComparator, false>(mModelForItemFunctions, mi); \
2250 switch (mSortOrder->messageSorting()) {
2252 INSERT_MESSAGE_WITH_COMPARATOR(ItemDateComparator)
2255 INSERT_MESSAGE_WITH_COMPARATOR(ItemMaxDateComparator)
2258 INSERT_MESSAGE_WITH_COMPARATOR(ItemSizeComparator)
2261 INSERT_MESSAGE_WITH_COMPARATOR(ItemSenderOrReceiverComparator)
2264 INSERT_MESSAGE_WITH_COMPARATOR(ItemSenderComparator)
2267 INSERT_MESSAGE_WITH_COMPARATOR(ItemReceiverComparator)
2270 INSERT_MESSAGE_WITH_COMPARATOR(ItemSubjectComparator)
2273 INSERT_MESSAGE_WITH_COMPARATOR(ItemActionItemStatusComparator)
2276 INSERT_MESSAGE_WITH_COMPARATOR(ItemUnreadStatusComparator)
2278 case SortOrder::SortMessagesByImportantStatus:
2279 INSERT_MESSAGE_WITH_COMPARATOR(ItemImportantStatusComparator)
2282 INSERT_MESSAGE_WITH_COMPARATOR(ItemAttachmentStatusComparator)
2285 pParent->appendChildItem(mModelForItemFunctions, mi);
2288 pParent->appendChildItem(mModelForItemFunctions, mi);
2296 switch (mAggregation->threadExpandPolicy()) {
2299 if (childNeedsExpanding) {
2330 if (mModelForItemFunctions && pParent->isViewable()) {
2332 Item *parentToExpand = pParent;
2333 while (parentToExpand) {
2334 if (parentToExpand == mRootItem) {
2342 mView->expand(q->index(parentToExpand, 0));
2345 parentToExpand = parentToExpand->
parent();
2350 while (parentToExpand) {
2351 if (parentToExpand == mRootItem) {
2355 parentToExpand = parentToExpand->
parent();
2364 if (childNeedsExpanding) {
2366 if (mModelForItemFunctions) {
2367 syncExpandedStateOfSubtree(mi);
2374 Q_ASSERT(mModelForItemFunctions);
2377 if (applyFilterToSubtree(mi, q->index(pParent, 0))) {
2379 mView->ensureDisplayedWithParentsExpanded(mi);
2388 if (pParent == mRootItem) {
2396 if (oldParentWasTheSame) {
2404 propagateItemPropertiesToParent(mi);
2416ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass5(ViewItemJob *job,
QElapsedTimer elapsedTimer)
2421 int curIndex = job->currentIndex();
2423 auto it = mGroupHeadersThatNeedUpdate.begin();
2424 auto end = mGroupHeadersThatNeedUpdate.end();
2427 if ((*it)->childItemCount() == 0) {
2429 (*it)->parent()->takeChildItem(mModelForItemFunctions, *it);
2430 mGroupHeaderItemHash.remove((*it)->label());
2433 if (mCurrentItemToRestoreAfterViewItemJobStep == (*it)) {
2434 mCurrentItemToRestoreAfterViewItemJobStep =
nullptr;
2448 bool needsReSorting;
2451#define CHECK_IF_GROUP_NEEDS_RESORTING(_ItemDateComparator) \
2452 switch (mSortOrder->groupSortDirection()) { \
2453 case SortOrder::Ascending: \
2454 needsReSorting = (*it)->parent()->d_ptr->childItemNeedsReSorting<_ItemDateComparator, true>(*it); \
2456 case SortOrder::Descending: \
2457 needsReSorting = (*it)->parent()->d_ptr->childItemNeedsReSorting<_ItemDateComparator, false>(*it); \
2460 needsReSorting = false; \
2464 switch (mSortOrder->groupSorting()) {
2466 CHECK_IF_GROUP_NEEDS_RESORTING(ItemDateComparator)
2469 CHECK_IF_GROUP_NEEDS_RESORTING(ItemMaxDateComparator)
2472 CHECK_IF_GROUP_NEEDS_RESORTING(ItemSenderOrReceiverComparator)
2475 CHECK_IF_GROUP_NEEDS_RESORTING(ItemSenderComparator)
2478 CHECK_IF_GROUP_NEEDS_RESORTING(ItemReceiverComparator)
2481 needsReSorting =
false;
2485 needsReSorting =
false;
2489 if (needsReSorting) {
2494 it = mGroupHeadersThatNeedUpdate.erase(it);
2501 if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
2502 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
2503 if (it != mGroupHeadersThatNeedUpdate.end()) {
2504 job->setCurrentIndex(curIndex);
2505 return ViewItemJobInterrupted;
2511 return ViewItemJobCompleted;
2514ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass4(ViewItemJob *job,
QElapsedTimer elapsedTimer)
2524 int curIndex = job->currentIndex();
2525 int endIndex = job->endIndex();
2527 while (curIndex <= endIndex) {
2528 MessageItem *mi = mUnassignedMessageListForPass4[curIndex];
2532 attachMessageToGroupHeader(mi);
2541 if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
2542 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
2543 if (curIndex <= endIndex) {
2544 job->setCurrentIndex(curIndex);
2545 return ViewItemJobInterrupted;
2551 mUnassignedMessageListForPass4.clear();
2552 return ViewItemJobCompleted;
2555ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass3(ViewItemJob *job,
QElapsedTimer elapsedTimer)
2568 int curIndex = job->currentIndex();
2569 int endIndex = job->endIndex();
2571 while (curIndex <= endIndex) {
2573 auto mi = mUnassignedMessageListForPass3[curIndex];
2577 if (mi->subjectIsPrefixed()) {
2579 auto mparent = guessMessageParent(mi);
2585 attachMessageToParent(mparent, mi);
2586 if (!mparent->isViewable()) {
2588 auto topmost = mparent->topmostMessage();
2589 Q_ASSERT(!topmost->parent());
2591 attachMessageToGroupHeader(topmost);
2595 attachMessageToParent(mparent, mi);
2601 mUnassignedMessageListForPass4.append(mi);
2607 mUnassignedMessageListForPass4.append(mi);
2622 if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
2623 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
2624 if (curIndex <= endIndex) {
2625 job->setCurrentIndex(curIndex);
2626 return ViewItemJobInterrupted;
2632 mUnassignedMessageListForPass3.clear();
2633 return ViewItemJobCompleted;
2636ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass2(ViewItemJob *job,
QElapsedTimer elapsedTimer)
2651 int curIndex = job->currentIndex();
2652 int endIndex = job->endIndex();
2654 while (curIndex <= endIndex) {
2656 auto mi = mUnassignedMessageListForPass2[curIndex];
2662 auto mparent = mThreadingCache.parentForItem(mi, parentId);
2663 if (mparent && !mparent->hasAncestor(mi)) {
2665 attachMessageToParent(mparent, mi, SkipCacheUpdate);
2671 mThreadingCache.expireParent(mi);
2672 mparent = findMessageParent(mi);
2673 }
else if (parentId < 0) {
2674 mparent = findMessageParent(mi);
2685 attachMessageToParent(mparent, mi);
2686 if (!mparent->isViewable()) {
2688 auto topmost = mparent->topmostMessage();
2689 Q_ASSERT(!topmost->parent());
2691 attachMessageToGroupHeader(topmost);
2695 attachMessageToParent(mparent, mi);
2700 switch (mi->threadingStatus()) {
2704 mUnassignedMessageListForPass3.append(mi);
2707 mUnassignedMessageListForPass4.append(mi);
2712 mUnassignedMessageListForPass4.append(mi);
2716 qCWarning(MESSAGELIST_LOG) <<
"ERROR: Invalid message threading status returned by findMessageParent()!";
2727 qCWarning(MESSAGELIST_LOG) <<
"Non viewable message " << mi <<
" subject " << mi->
subject().
toUtf8().
data();
2737 if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
2738 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
2739 if (curIndex <= endIndex) {
2740 job->setCurrentIndex(curIndex);
2741 return ViewItemJobInterrupted;
2747 mUnassignedMessageListForPass2.clear();
2748 return ViewItemJobCompleted;
2751ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass1Fill(ViewItemJob *job,
QElapsedTimer elapsedTimer)
2763 bool bUseReceiver = mStorageModelContainsOutboundMessages;
2766 int curIndex = job->currentIndex();
2768 int endIndex = job->endIndex();
2770 unsigned long msgToSelect = mPreSelectionMode == PreSelectLastSelected ? mStorageModel->preSelectedMessage() : 0;
2774 while (curIndex <= endIndex) {
2780 Q_ASSERT(mi->
parent() ==
nullptr);
2783 if (!mStorageModel->initializeMessageItem(mi, curIndex, bUseReceiver)) {
2785 qCWarning(MESSAGELIST_LOG) <<
"Fill of the MessageItem at storage row index " << curIndex <<
" failed";
2791 if (msgToSelect != 0 && msgToSelect == mi->uniqueId()) {
2796 mLastSelectedMessageInFolder = mi;
2801 if (mi->
date() !=
static_cast<uint
>(-1)) {
2802 if (!mOldestItem || mOldestItem->date() > mi->
date()) {
2805 if (!mNewestItem || mNewestItem->date() < mi->
date()) {
2812 mInvariantRowMapper->createModelInvariantIndex(curIndex, mi);
2821 switch (mAggregation->threading()) {
2826 addMessageToReferencesBasedThreadingCache(mi);
2827 addMessageToSubjectBasedThreadingCache(mi);
2831 addMessageToReferencesBasedThreadingCache(mi);
2839 mThreadingCacheMessageIdMD5ToMessageItem.insert(mi->messageIdMD5(), mi);
2842 mThreadingCache.addItemToCache(mi);
2846 Item *pParent = mThreadingCache.parentForItem(mi, parentId);
2851 attachMessageToParent(pParent, mi);
2852 }
else if (parentId > 0) {
2857 mUnassignedMessageListForPass2.append(mi);
2858 }
else if (parentId == 0) {
2861 mUnassignedMessageListForPass4.append(mi);
2867 bool needsImmediateReAttach =
false;
2869 if (!mThreadingCacheMessageInReplyToIdMD5ToMessageItem.isEmpty()) {
2870 const auto lImperfectlyThreaded = mThreadingCacheMessageInReplyToIdMD5ToMessageItem.values(mi->messageIdMD5());
2871 for (
const auto it : lImperfectlyThreaded) {
2872 Q_ASSERT(it->parent());
2873 Q_ASSERT(it->parent() != mi);
2876 qCritical() <<
"Got message " << it <<
" with threading status" << it->threadingStatus();
2877 Q_ASSERT_X(
false,
"ModelPrivate::viewItemJobStepInternalForJobPass1Fill",
"Wrong threading status");
2884 if (it->isViewable()) {
2885 needsImmediateReAttach =
true;
2889 attachMessageToParent(mi, it);
2908 pParent = mThreadingCacheMessageIdMD5ToMessageItem.value(md5,
nullptr);
2917 && pParent->hasAncestor(mi)
2922 qCWarning(MESSAGELIST_LOG) <<
"Circular In-Reply-To reference loop detected in the message tree";
2923 mUnassignedMessageListForPass2.append(mi);
2927 attachMessageToParent(pParent, mi);
2933 mUnassignedMessageListForPass2.append(mi);
2938 bool mightHaveOtherMeansForThreading;
2940 switch (mAggregation->threading()) {
2942 mightHaveOtherMeansForThreading = mi->subjectIsPrefixed() || !mi->referencesIdMD5().
isEmpty();
2945 mightHaveOtherMeansForThreading = !mi->referencesIdMD5().
isEmpty();
2948 mightHaveOtherMeansForThreading =
false;
2953 mightHaveOtherMeansForThreading =
false;
2957 if (mightHaveOtherMeansForThreading) {
2959 mUnassignedMessageListForPass2.append(mi);
2971 attachMessageToGroupHeader(mi);
2976 mUnassignedMessageListForPass2.append(mi);
2981 if (needsImmediateReAttach && !mi->
isViewable()) {
2987 attachMessageToGroupHeader(topmost);
2997 attachMessageToParent(mRootItem, mi);
2999 attachMessageToGroupHeader(mi);
3007 if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
3008 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
3009 if (curIndex <= endIndex) {
3010 job->setCurrentIndex(curIndex);
3011 return ViewItemJobInterrupted;
3020 return ViewItemJobCompleted;
3023ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass1Cleanup(ViewItemJob *job,
QElapsedTimer elapsedTimer)
3025 Q_ASSERT(mModelForItemFunctions);
3037 int curIndex = job->currentIndex();
3039 int endIndex = job->endIndex();
3041 if (curIndex == job->startIndex()) {
3042 Q_ASSERT(mOrphanChildrenHash.isEmpty());
3045 while (curIndex <= endIndex) {
3047 auto dyingMessage =
dynamic_cast<MessageItem *
>(invalidatedMessages->
at(curIndex));
3049 Q_ASSERT(dyingMessage);
3054 if (dyingMessage == mLastSelectedMessageInFolder) {
3055 mLastSelectedMessageInFolder =
nullptr;
3056 mPreSelectionMode = PreSelectNone;
3060 if (mPersistentSetManager) {
3061 mPersistentSetManager->removeMessageItemFromAllSets(dyingMessage);
3062 if (mPersistentSetManager->setCount() < 1) {
3063 delete mPersistentSetManager;
3064 mPersistentSetManager =
nullptr;
3070 mThreadingCache.expireParent(dyingMessage);
3072 if (dyingMessage->parent()) {
3076 if (dyingMessage == mCurrentItemToRestoreAfterViewItemJobStep) {
3077 Q_ASSERT(dyingMessage->isViewable());
3082 mCurrentItemToRestoreAfterViewItemJobStep = mView->messageItemAfter(dyingMessage, MessageTypeAny,
false);
3084 if (!mCurrentItemToRestoreAfterViewItemJobStep) {
3088 mCurrentItemToRestoreAfterViewItemJobStep = mView->messageItemBefore(dyingMessage, MessageTypeAny,
false);
3091 Q_ASSERT((!mCurrentItemToRestoreAfterViewItemJobStep) || mCurrentItemToRestoreAfterViewItemJobStep->isViewable());
3094 if (dyingMessage->isViewable() && ((dyingMessage)->childItemCount() > 0)
3095 && mView->isExpanded(q->index(dyingMessage, 0))
3097 saveExpandedStateOfSubtree(dyingMessage);
3100 auto oldParent = dyingMessage->
parent();
3101 oldParent->takeChildItem(q, dyingMessage);
3107 if (oldParent != mRootItem) {
3108 messageDetachedUpdateParentProperties(oldParent, dyingMessage);
3114 mOrphanChildrenHash.remove(dyingMessage);
3120 Q_ASSERT(mOrphanChildrenHash.contains(dyingMessage));
3121 Q_ASSERT(dyingMessage != mCurrentItemToRestoreAfterViewItemJobStep);
3123 mOrphanChildrenHash.remove(dyingMessage);
3130 mThreadingCacheMessageIdMD5ToMessageItem.remove(dyingMessage->messageIdMD5());
3134 removeMessageFromReferencesBasedThreadingCache(dyingMessage);
3135 removeMessageFromSubjectBasedThreadingCache(dyingMessage);
3137 removeMessageFromReferencesBasedThreadingCache(dyingMessage);
3141 switch (dyingMessage->threadingStatus()) {
3144 if (!dyingMessage->inReplyToIdMD5().isEmpty()) {
3145 mThreadingCacheMessageInReplyToIdMD5ToMessageItem.remove(dyingMessage->inReplyToIdMD5());
3149 Q_ASSERT(!mThreadingCacheMessageInReplyToIdMD5ToMessageItem.contains(dyingMessage->inReplyToIdMD5(), dyingMessage));
3155 while (
auto childItem = dyingMessage->firstChildItem()) {
3156 auto childMessage =
dynamic_cast<MessageItem *
>(childItem);
3157 Q_ASSERT(childMessage);
3159 dyingMessage->takeChildItem(q, childMessage);
3165 if (!childMessage->inReplyToIdMD5().isEmpty()) {
3166 Q_ASSERT(!mThreadingCacheMessageInReplyToIdMD5ToMessageItem.contains(childMessage->inReplyToIdMD5(), childMessage));
3167 mThreadingCacheMessageInReplyToIdMD5ToMessageItem.insert(childMessage->inReplyToIdMD5(), childMessage);
3182 (childMessage == mCurrentItemToRestoreAfterViewItemJobStep)
3185 mCurrentItemToRestoreAfterViewItemJobStep &&
3186 mCurrentItemToRestoreAfterViewItemJobStep->hasAncestor(childMessage))) {
3187 attachMessageToGroupHeader(childMessage);
3189 Q_ASSERT(childMessage->isViewable());
3192 mOrphanChildrenHash.insert(childMessage, childMessage);
3195 if (mNewestItem == dyingMessage) {
3196 mNewestItem =
nullptr;
3198 if (mOldestItem == dyingMessage) {
3199 mOldestItem =
nullptr;
3202 delete dyingMessage;
3209 if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
3210 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
3211 if (curIndex <= endIndex) {
3212 job->setCurrentIndex(curIndex);
3213 return ViewItemJobInterrupted;
3221 job->setCurrentIndex(endIndex + 1);
3230 auto it = mOrphanChildrenHash.begin();
3231 auto end = mOrphanChildrenHash.end();
3236 mUnassignedMessageListForPass2.append(*it);
3238 it = mOrphanChildrenHash.erase(it);
3245 if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
3246 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
3247 if (it != mOrphanChildrenHash.end()) {
3248 return ViewItemJobInterrupted;
3254 return ViewItemJobCompleted;
3257ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass1Update(ViewItemJob *job,
QElapsedTimer elapsedTimer)
3259 Q_ASSERT(mModelForItemFunctions);
3264 auto messagesThatNeedUpdate = job->invariantIndexList();
3271 int curIndex = job->currentIndex();
3273 int endIndex = job->endIndex();
3275 while (curIndex <= endIndex) {
3277 auto message =
dynamic_cast<MessageItem *
>(messagesThatNeedUpdate->at(curIndex));
3281 int row = mInvariantRowMapper->modelInvariantIndexToModelIndexRow(message);
3285 Q_ASSERT(!message->isValid());
3291 time_t prevDate = message->date();
3292 time_t prevMaxDate = message->maxDate();
3293 bool toDoStatus = message->status().isToAct();
3294 bool prevUnreadStatus = !message->status().isRead();
3295 bool prevImportantStatus = message->status().isImportant();
3300 removeMessageFromReferencesBasedThreadingCache(message);
3301 removeMessageFromSubjectBasedThreadingCache(message);
3303 removeMessageFromReferencesBasedThreadingCache(message);
3307 mStorageModel->updateMessageItemData(message, row);
3309 Q_EMIT q->dataChanged(idx, idx);
3313 addMessageToReferencesBasedThreadingCache(message);
3314 addMessageToSubjectBasedThreadingCache(message);
3316 addMessageToReferencesBasedThreadingCache(message);
3319 int propertyChangeMask = 0;
3321 if (prevDate != message->date()) {
3322 propertyChangeMask |= DateChanged;
3324 if (prevMaxDate != message->maxDate()) {
3325 propertyChangeMask |= MaxDateChanged;
3327 if (toDoStatus != message->status().isToAct()) {
3328 propertyChangeMask |= ActionItemStatusChanged;
3330 if (prevUnreadStatus != (!message->status().isRead())) {
3331 propertyChangeMask |= UnreadStatusChanged;
3333 if (prevImportantStatus != (!message->status().isImportant())) {
3334 propertyChangeMask |= ImportantStatusChanged;
3337 if (propertyChangeMask) {
3342 Item *pParent = message->parent();
3344 if (pParent && (pParent != mRootItem)) {
3347 if (handleItemPropertyChanges(propertyChangeMask, pParent, message)) {
3348 Q_ASSERT(message->parent());
3354 propagateItemPropertiesToParent(message);
3360 if (mFilter && message->isViewable()) {
3362 Item *pTopMostNonRoot = message->topmostNonRoot();
3364 Q_ASSERT(pTopMostNonRoot);
3365 Q_ASSERT(pTopMostNonRoot != mRootItem);
3366 Q_ASSERT(pTopMostNonRoot->
parent() == mRootItem);
3378 applyFilterToSubtree(pTopMostNonRoot,
QModelIndex());
3390 if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
3391 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
3392 if (curIndex <= endIndex) {
3393 job->setCurrentIndex(curIndex);
3394 return ViewItemJobInterrupted;
3400 return ViewItemJobCompleted;
3403ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJob(ViewItemJob *job,
QElapsedTimer elapsedTimer)
3412 if (job->currentPass() == ViewItemJob::Pass1Fill) {
3414 switch (viewItemJobStepInternalForJobPass1Fill(job, elapsedTimer)) {
3415 case ViewItemJobInterrupted:
3417 return ViewItemJobInterrupted;
3419 case ViewItemJobCompleted:
3422 job->setCurrentPass(ViewItemJob::Pass2);
3423 job->setStartIndex(0);
3424 job->setEndIndex(mUnassignedMessageListForPass2.count() - 1);
3429 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
3430 return ViewItemJobInterrupted;
3435 qCWarning(MESSAGELIST_LOG) <<
"ERROR: returned an invalid result";
3439 }
else if (job->currentPass() == ViewItemJob::Pass1Cleanup) {
3441 switch (viewItemJobStepInternalForJobPass1Cleanup(job, elapsedTimer)) {
3442 case ViewItemJobInterrupted:
3444 return ViewItemJobInterrupted;
3446 case ViewItemJobCompleted:
3448 job->setCurrentPass(ViewItemJob::Pass2);
3449 job->setStartIndex(0);
3450 job->setEndIndex(mUnassignedMessageListForPass2.count() - 1);
3455 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
3456 return ViewItemJobInterrupted;
3461 qCWarning(MESSAGELIST_LOG) <<
"ERROR: returned an invalid result";
3465 }
else if (job->currentPass() == ViewItemJob::Pass1Update) {
3467 switch (viewItemJobStepInternalForJobPass1Update(job, elapsedTimer)) {
3468 case ViewItemJobInterrupted:
3470 return ViewItemJobInterrupted;
3472 case ViewItemJobCompleted:
3476 job->setCurrentPass(ViewItemJob::Pass5);
3477 job->setStartIndex(0);
3478 job->setEndIndex(mGroupHeadersThatNeedUpdate.count() - 1);
3483 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
3484 return ViewItemJobInterrupted;
3489 qCWarning(MESSAGELIST_LOG) <<
"ERROR: returned an invalid result";
3497 if (job->currentPass() == ViewItemJob::Pass2) {
3499 switch (viewItemJobStepInternalForJobPass2(job, elapsedTimer)) {
3500 case ViewItemJobInterrupted:
3502 return ViewItemJobInterrupted;
3504 case ViewItemJobCompleted:
3506 job->setCurrentPass(ViewItemJob::Pass3);
3507 job->setStartIndex(0);
3508 job->setEndIndex(mUnassignedMessageListForPass3.count() - 1);
3513 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
3514 return ViewItemJobInterrupted;
3520 qCWarning(MESSAGELIST_LOG) <<
"ERROR: returned an invalid result";
3526 if (job->currentPass() == ViewItemJob::Pass3) {
3528 switch (viewItemJobStepInternalForJobPass3(job, elapsedTimer)) {
3529 case ViewItemJobInterrupted:
3531 return ViewItemJobInterrupted;
3532 case ViewItemJobCompleted:
3534 job->setCurrentPass(ViewItemJob::Pass4);
3535 job->setStartIndex(0);
3536 job->setEndIndex(mUnassignedMessageListForPass4.count() - 1);
3541 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
3542 return ViewItemJobInterrupted;
3548 qCWarning(MESSAGELIST_LOG) <<
"ERROR: returned an invalid result";
3554 if (job->currentPass() == ViewItemJob::Pass4) {
3556 switch (viewItemJobStepInternalForJobPass4(job, elapsedTimer)) {
3557 case ViewItemJobInterrupted:
3559 return ViewItemJobInterrupted;
3560 case ViewItemJobCompleted:
3562 job->setCurrentPass(ViewItemJob::Pass5);
3563 job->setStartIndex(0);
3564 job->setEndIndex(mGroupHeadersThatNeedUpdate.count() - 1);
3569 if (elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) {
3570 return ViewItemJobInterrupted;
3576 qCWarning(MESSAGELIST_LOG) <<
"ERROR: returned an invalid result";
3583 return viewItemJobStepInternalForJobPass5(job, elapsedTimer);
3586#ifdef KDEPIM_FOLDEROPEN_PROFILE
3592static const int numberOfPasses = ViewItemJob::LastIndex;
3596static int lastPass = -1;
3599static int totalMessages;
3602static int numElements[numberOfPasses];
3603static int totalTime[numberOfPasses];
3604static int chunks[numberOfPasses];
3607static int expandingTreeTime;
3608static int layoutChangeTime;
3611static const char *jobDescription[numberOfPasses] = {
"Creating items from messages and simple threading",
3612 "Removing messages",
3613 "Updating messages",
3614 "Additional Threading",
3615 "Subject-Based threading",
3617 "Group resorting + cleanup"};
3620static QTime firstStartTime;
3623static QTime currentJobStartTime;
3626static void resetStats()
3629 layoutChangeTime = 0;
3630 expandingTreeTime = 0;
3632 for (
int i = 0; i < numberOfPasses; ++i) {
3640void ModelPrivate::printStatistics()
3642 using namespace Stats;
3643 int totalTotalTime = 0;
3644 int completeTime = firstStartTime.elapsed();
3645 for (
int i = 0; i < numberOfPasses; ++i) {
3646 totalTotalTime += totalTime[i];
3649 float msgPerSecond = totalMessages / (totalTotalTime / 1000.0f);
3650 float msgPerSecondComplete = totalMessages / (completeTime / 1000.0f);
3652 int messagesWithSameSubjectAvg = 0;
3653 int messagesWithSameSubjectMax = 0;
3654 for (
const auto messages :
std::as_const(mThreadingCacheMessageSubjectMD5ToMessageItem)) {
3655 if (messages->size() > messagesWithSameSubjectMax) {
3656 messagesWithSameSubjectMax = messages->size();
3658 messagesWithSameSubjectAvg += messages->size();
3660 messagesWithSameSubjectAvg = messagesWithSameSubjectAvg / (float)mThreadingCacheMessageSubjectMD5ToMessageItem.size();
3662 int totalThreads = 0;
3663 if (!mGroupHeaderItemHash.isEmpty()) {
3664 for (
const GroupHeaderItem *groupHeader :
std::as_const(mGroupHeaderItemHash)) {
3665 totalThreads += groupHeader->childItemCount();
3668 totalThreads = mRootItem->childItemCount();
3671 qCDebug(MESSAGELIST_LOG) <<
"Finished filling the view with" << totalMessages <<
"messages";
3672 qCDebug(MESSAGELIST_LOG) <<
"That took" << totalTotalTime <<
"msecs inside the model and" << completeTime <<
"in total.";
3673 qCDebug(MESSAGELIST_LOG) << (totalTotalTime / (float)completeTime) * 100.0f <<
"percent of the time was spent in the model.";
3674 qCDebug(MESSAGELIST_LOG) <<
"Time for layoutChanged(), in msecs:" << layoutChangeTime <<
"(" << (layoutChangeTime / (float)totalTotalTime) * 100.0f
3676 qCDebug(MESSAGELIST_LOG) <<
"Time to expand tree, in msecs:" << expandingTreeTime <<
"(" << (expandingTreeTime / (float)totalTotalTime) * 100.0f
3678 qCDebug(MESSAGELIST_LOG) <<
"Number of messages per second in the model:" << msgPerSecond;
3679 qCDebug(MESSAGELIST_LOG) <<
"Number of messages per second in total:" << msgPerSecondComplete;
3680 qCDebug(MESSAGELIST_LOG) <<
"Number of threads:" << totalThreads;
3681 qCDebug(MESSAGELIST_LOG) <<
"Number of groups:" << mGroupHeaderItemHash.size();
3682 qCDebug(MESSAGELIST_LOG) <<
"Messages per thread:" << totalMessages / (float)totalThreads;
3683 qCDebug(MESSAGELIST_LOG) <<
"Threads per group:" << totalThreads / (float)mGroupHeaderItemHash.size();
3684 qCDebug(MESSAGELIST_LOG) <<
"Messages with the same subject:"
3685 <<
"Max:" << messagesWithSameSubjectMax <<
"Avg:" << messagesWithSameSubjectAvg;
3686 qCDebug(MESSAGELIST_LOG);
3687 qCDebug(MESSAGELIST_LOG) <<
"Now follows a breakdown of the jobs.";
3688 qCDebug(MESSAGELIST_LOG);
3689 for (
int i = 0; i < numberOfPasses; ++i) {
3690 if (totalTime[i] == 0) {
3693 float elementsPerSecond = numElements[i] / (totalTime[i] / 1000.0f);
3694 float percent = totalTime[i] / (float)totalTotalTime * 100.0f;
3695 qCDebug(MESSAGELIST_LOG) <<
"----------------------------------------------";
3696 qCDebug(MESSAGELIST_LOG) <<
"Job" << i + 1 <<
"(" << jobDescription[i] <<
")";
3697 qCDebug(MESSAGELIST_LOG) <<
"Share of complete time:" << percent <<
"percent";
3698 qCDebug(MESSAGELIST_LOG) <<
"Time in msecs:" << totalTime[i];
3699 qCDebug(MESSAGELIST_LOG) <<
"Number of elements:" << numElements[i];
3700 qCDebug(MESSAGELIST_LOG) <<
"Elements per second:" << elementsPerSecond;
3701 qCDebug(MESSAGELIST_LOG) <<
"Number of chunks:" << chunks[i];
3702 qCDebug(MESSAGELIST_LOG);
3705 qCDebug(MESSAGELIST_LOG) <<
"==========================================================";
3711ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternal()
3718 elapsedTimer.
start();
3720 while (!mViewItemJobs.isEmpty()) {
3722 ViewItemJob *job = mViewItemJobs.constFirst();
3724#ifdef KDEPIM_FOLDEROPEN_PROFILE
3729 const int currentPass = job->currentPass();
3730 const bool firstChunk = currentPass != Stats::lastPass;
3731 if (currentPass != Stats::lastPass && Stats::lastPass != -1) {
3732 Stats::totalTime[Stats::lastPass] = Stats::currentJobStartTime.elapsed();
3734 const bool firstJob = job->currentPass() == ViewItemJob::Pass1Fill && firstChunk;
3735 const int elements = job->endIndex() - job->startIndex();
3737 Stats::resetStats();
3738 Stats::totalMessages = elements;
3739 Stats::firstStartTime.restart();
3742 Stats::numElements[currentPass] = elements;
3743 Stats::currentJobStartTime.restart();
3745 Stats::chunks[currentPass]++;
3746 Stats::lastPass = currentPass;
3750 mViewItemJobStepIdleInterval = job->idleInterval();
3751 mViewItemJobStepChunkTimeout = job->chunkTimeout();
3752 mViewItemJobStepMessageCheckCount = job->messageCheckCount();
3754 if (job->disconnectUI()) {
3755 mModelForItemFunctions =
nullptr;
3766 mView->ignoreUpdateGeometries(
true);
3777 switch (viewItemJobStepInternalForJob(job, elapsedTimer)) {
3778 case ViewItemJobInterrupted:
3783 switch (job->currentPass()) {
3784 case ViewItemJob::Pass1Fill:
3785 case ViewItemJob::Pass1Cleanup:
3786 case ViewItemJob::Pass1Update:
3787 Q_EMIT q->statusMessage(
i18np(
"Processed 1 Message of %2",
3788 "Processed %1 Messages of %2",
3789 job->currentIndex() - job->startIndex(),
3790 job->endIndex() - job->startIndex() + 1));
3792 case ViewItemJob::Pass2:
3793 Q_EMIT q->statusMessage(
i18np(
"Threaded 1 Message of %2",
3794 "Threaded %1 Messages of %2",
3795 job->currentIndex() - job->startIndex(),
3796 job->endIndex() - job->startIndex() + 1));
3798 case ViewItemJob::Pass3:
3799 Q_EMIT q->statusMessage(
i18np(
"Threaded 1 Message of %2",
3800 "Threaded %1 Messages of %2",
3801 job->currentIndex() - job->startIndex(),
3802 job->endIndex() - job->startIndex() + 1));
3804 case ViewItemJob::Pass4:
3805 Q_EMIT q->statusMessage(
i18np(
"Grouped 1 Thread of %2",
3806 "Grouped %1 Threads of %2",
3807 job->currentIndex() - job->startIndex(),
3808 job->endIndex() - job->startIndex() + 1));
3810 case ViewItemJob::Pass5:
3811 Q_EMIT q->statusMessage(
i18np(
"Updated 1 Group of %2",
3812 "Updated %1 Groups of %2",
3813 job->currentIndex() - job->startIndex(),
3814 job->endIndex() - job->startIndex() + 1));
3820 if (!job->disconnectUI()) {
3821 mView->ignoreUpdateGeometries(
false);
3823 mView->updateGeometries();
3826 return ViewItemJobInterrupted;
3828 case ViewItemJobCompleted:
3832 if (job->disconnectUI()) {
3833 mModelForItemFunctions = q;
3837#ifdef KDEPIM_FOLDEROPEN_PROFILE
3838 QTime layoutChangedTimer;
3839 layoutChangedTimer.start();
3841 mView->modelAboutToEmitLayoutChanged();
3842 Q_EMIT q->layoutChanged();
3843 mView->modelEmittedLayoutChanged();
3845#ifdef KDEPIM_FOLDEROPEN_PROFILE
3846 Stats::layoutChangeTime = layoutChangedTimer.elapsed();
3847 QTime expandingTime;
3848 expandingTime.start();
3855 auto rootChildItems = mRootItem->childItems();
3856 if (rootChildItems) {
3857 for (
const auto it :
std::as_const(*rootChildItems)) {
3859 syncExpandedStateOfSubtree(it);
3863#ifdef KDEPIM_FOLDEROPEN_PROFILE
3864 Stats::expandingTreeTime = expandingTime.elapsed();
3867 mView->ignoreUpdateGeometries(
false);
3869 mView->updateGeometries();
3873 delete mViewItemJobs.takeFirst();
3875#ifdef KDEPIM_FOLDEROPEN_PROFILE
3877 Stats::totalTime[currentPass] = Stats::currentJobStartTime.elapsed();
3885 if ((elapsedTimer.
elapsed() > mViewItemJobStepChunkTimeout) || (elapsedTimer.
elapsed() < 0)) {
3886 if (!mViewItemJobs.isEmpty()) {
3887 return ViewItemJobInterrupted;
3895 qCWarning(MESSAGELIST_LOG) <<
"ERROR: returned an invalid result";
3903 Q_EMIT q->statusMessage(
i18nc(
"@info:status Finished view fill",
"Ready"));
3905 return ViewItemJobCompleted;
3908void ModelPrivate::viewItemJobStep()
3916 mViewItemJobStepStartTime = ::time(
nullptr);
3918 if (mFillStepTimer.isActive()) {
3919 mFillStepTimer.stop();
3922 if (!mStorageModel) {
3931 QModelIndex currentIndexBeforeStep = mView->currentIndex();
3935 mCurrentItemToRestoreAfterViewItemJobStep = currentItemBeforeStep;
3939 QRect rectBeforeViewItemJobStep;
3941 const bool lockView = mView->isScrollingLocked();
3944 if (mCurrentItemToRestoreAfterViewItemJobStep && (!lockView)) {
3945 rectBeforeViewItemJobStep = mView->visualRect(currentIndexBeforeStep);
3952 mView->ignoreCurrentChanges(
true);
3955 switch (viewItemJobStepInternal()) {
3956 case ViewItemJobInterrupted:
3958 if (!mInLengthyJobBatch) {
3959 mInLengthyJobBatch =
true;
3961 mFillStepTimer.start(mViewItemJobStepIdleInterval);
3964 case ViewItemJobCompleted:
3967 Q_ASSERT(mModelForItemFunctions);
3970 if (mInLengthyJobBatch) {
3971 mInLengthyJobBatch =
false;
3976 mView->modelFinishedLoading();
3981 if (mPreSelectionMode != PreSelectNone) {
3982 mView->ignoreCurrentChanges(
false);
3984 bool bSelectionDone =
false;
3986 switch (mPreSelectionMode) {
3987 case PreSelectLastSelected:
3990 case PreSelectFirstUnreadCentered:
3991 bSelectionDone = mView->selectFirstMessageItem(MessageTypeUnreadOnly,
true);
3993 case PreSelectOldestCentered:
3994 mView->setCurrentMessageItem(mOldestItem,
true );
3995 bSelectionDone =
true;
3997 case PreSelectNewestCentered:
3998 mView->setCurrentMessageItem(mNewestItem,
true );
3999 bSelectionDone =
true;
4005 qCWarning(MESSAGELIST_LOG) <<
"ERROR: Unrecognized pre-selection mode " <<
static_cast<int>(mPreSelectionMode);
4009 if ((!bSelectionDone) && (mPreSelectionMode != PreSelectNone)) {
4011 if (mLastSelectedMessageInFolder) {
4012 mView->setCurrentMessageItem(mLastSelectedMessageInFolder);
4013 bSelectionDone =
true;
4017 if (bSelectionDone) {
4018 mLastSelectedMessageInFolder =
nullptr;
4019 mPreSelectionMode = PreSelectNone;
4028 qCWarning(MESSAGELIST_LOG) <<
"ERROR: returned an invalid result";
4036 if (!mModelForItemFunctions) {
4037 mView->ignoreCurrentChanges(
false);
4043 if (mCurrentItemToRestoreAfterViewItemJobStep) {
4044 bool stillIgnoringCurrentChanges =
true;
4048 Q_ASSERT(mCurrentItemToRestoreAfterViewItemJobStep->isViewable());
4051 QModelIndex currentIndexAfterStep = mView->currentIndex();
4054 if (mCurrentItemToRestoreAfterViewItemJobStep != currentAfterStep) {
4056 if (mCurrentItemToRestoreAfterViewItemJobStep != currentItemBeforeStep) {
4061 stillIgnoringCurrentChanges =
false;
4062 mView->ignoreCurrentChanges(
false);
4070 qCDebug(MESSAGELIST_LOG) <<
"Gonna restore current here" << mCurrentItemToRestoreAfterViewItemJobStep->subject();
4071 mView->setCurrentIndex(q->index(mCurrentItemToRestoreAfterViewItemJobStep, 0));
4074 if (mCurrentItemToRestoreAfterViewItemJobStep != currentItemBeforeStep) {
4080 if (!mView->selectionModel()->hasSelection()) {
4081 stillIgnoringCurrentChanges =
false;
4082 mView->ignoreCurrentChanges(
false);
4084 qCDebug(MESSAGELIST_LOG) <<
"Gonna restore selection here" << mCurrentItemToRestoreAfterViewItemJobStep->subject();
4096 QRect rectAfterViewItemJobStep = mView->visualRect(q->index(mCurrentItemToRestoreAfterViewItemJobStep, 0));
4097 if (rectBeforeViewItemJobStep.
y() != rectAfterViewItemJobStep.
y()) {
4099 mView->verticalScrollBar()->setValue(mView->verticalScrollBar()->value() + rectAfterViewItemJobStep.
y() - rectBeforeViewItemJobStep.
y());
4104 if (stillIgnoringCurrentChanges) {
4105 mView->ignoreCurrentChanges(
false);
4113 mView->ignoreCurrentChanges(
false);
4115 if (currentItemBeforeStep) {
4122void ModelPrivate::slotStorageModelRowsInserted(
const QModelIndex &parent,
int from,
int to)
4128 Q_ASSERT(from <= to);
4130 int count = (to - from) + 1;
4132 mInvariantRowMapper->modelRowsInserted(from, count);
4136 int jobCount = mViewItemJobs.count();
4138 for (
int idx = 0; idx < jobCount; idx++) {
4139 ViewItemJob *job = mViewItemJobs.at(idx);
4141 if (job->currentPass() != ViewItemJob::Pass1Fill) {
4147 if (job->currentIndex() > job->endIndex()) {
4177 if (from > job->endIndex()) {
4182 if (from > job->currentIndex()) {
4188 auto newJob =
new ViewItemJob(from + count, job->endIndex() + count, job->chunkTimeout(), job->idleInterval(), job->messageCheckCount());
4190 Q_ASSERT(newJob->currentIndex() <= newJob->endIndex());
4194 mViewItemJobs.insert(idx, newJob);
4197 job->setEndIndex(from - 1);
4199 Q_ASSERT(job->currentIndex() <= job->endIndex());
4206 job->setCurrentIndex(job->currentIndex() + count);
4207 job->setEndIndex(job->endIndex() + count);
4209 Q_ASSERT(job->currentIndex() <= job->endIndex());
4212 bool newJobNeeded =
true;
4220 ViewItemJob *job = mViewItemJobs.at(jobCount - 1);
4221 if (job->currentPass() == ViewItemJob::Pass1Fill) {
4224 (from == (job->endIndex() + 1)) &&
4225 (job->currentIndex() <= job->endIndex())) {
4227 job->setEndIndex(to);
4228 Q_ASSERT(job->currentIndex() <= job->endIndex());
4229 newJobNeeded =
false;
4236 auto job =
new ViewItemJob(from, to, 100, 50, 10);
4237 mViewItemJobs.append(job);
4240 if (!mFillStepTimer.isActive()) {
4241 mFillStepTimer.start(mViewItemJobStepIdleInterval);
4245void ModelPrivate::slotStorageModelRowsRemoved(
const QModelIndex &parent,
int from,
int to)
4255 Q_ASSERT(from <= to);
4257 const int count = (to - from) + 1;
4259 int jobCount = mViewItemJobs.count();
4261 if (mRootItem && from == 0 && count == mRootItem->childItemCount() && jobCount == 0) {
4266 for (
int idx = 0; idx < jobCount; idx++) {
4267 ViewItemJob *job = mViewItemJobs.at(idx);
4269 if (job->currentPass() != ViewItemJob::Pass1Fill) {
4275 if (job->currentIndex() > job->endIndex()) {
4305 if (from > job->endIndex()) {
4310 if (from > job->currentIndex()) {
4314 job->setEndIndex(from - 1);
4316 Q_ASSERT(job->currentIndex() <= job->endIndex());
4318 if (to < job->endIndex()) {
4325 auto newJob =
new ViewItemJob(from, job->endIndex() - count, job->chunkTimeout(), job->idleInterval(), job->messageCheckCount());
4327 Q_ASSERT(newJob->currentIndex() < newJob->endIndex());
4331 mViewItemJobs.insert(idx, newJob);
4338 if (to >= job->endIndex()) {
4347 job->setCurrentIndex(job->endIndex() + 1);
4352 if (to >= job->currentIndex()) {
4357 job->setCurrentIndex(from);
4358 job->setEndIndex(job->endIndex() - count);
4360 Q_ASSERT(job->currentIndex() <= job->endIndex());
4366 job->setCurrentIndex(job->currentIndex() - count);
4367 job->setEndIndex(job->endIndex() - count);
4372 auto invalidatedIndexes = mInvariantRowMapper->modelRowsRemoved(from, count);
4374 if (invalidatedIndexes) {
4381 ViewItemJob *job = mViewItemJobs.at(jobCount - 1);
4382 if (job->currentPass() == ViewItemJob::Pass1Cleanup) {
4383 if ((job->currentIndex() <= job->endIndex()) && job->invariantIndexList()) {
4386 *(job->invariantIndexList()) += *invalidatedIndexes;
4387 job->setEndIndex(job->endIndex() + invalidatedIndexes->count());
4388 delete invalidatedIndexes;
4389 invalidatedIndexes =
nullptr;
4394 if (invalidatedIndexes) {
4399 auto job =
new ViewItemJob(ViewItemJob::Pass1Cleanup, invalidatedIndexes, 100, 50, 10);
4400 mViewItemJobs.append(job);
4403 if (!mFillStepTimer.isActive()) {
4404 mFillStepTimer.start(mViewItemJobStepIdleInterval);
4409void ModelPrivate::slotStorageModelLayoutChanged()
4411 qCDebug(MESSAGELIST_LOG) <<
"Storage model layout changed";
4413 q->setStorageModel(mStorageModel);
4414 qCDebug(MESSAGELIST_LOG) <<
"Storage model layout changed done";
4419 Q_ASSERT(mStorageModel);
4422 int to = toIndex.
row();
4424 Q_ASSERT(from <= to);
4426 int count = (to - from) + 1;
4428 int jobCount = mViewItemJobs.count();
4432 auto indexesThatNeedUpdate = mInvariantRowMapper->modelIndexRowRangeToModelInvariantIndexList(from, count);
4434 if (indexesThatNeedUpdate) {
4441 ViewItemJob *job = mViewItemJobs.at(jobCount - 1);
4442 if (job->currentPass() == ViewItemJob::Pass1Update) {
4443 if ((job->currentIndex() <= job->endIndex()) && job->invariantIndexList()) {
4445 *(job->invariantIndexList()) += *indexesThatNeedUpdate;
4446 job->setEndIndex(job->endIndex() + indexesThatNeedUpdate->count());
4447 delete indexesThatNeedUpdate;
4448 indexesThatNeedUpdate =
nullptr;
4453 if (indexesThatNeedUpdate) {
4456 auto job =
new ViewItemJob(ViewItemJob::Pass1Update, indexesThatNeedUpdate, 100, 50, 10);
4457 mViewItemJobs.append(job);
4460 if (!mFillStepTimer.isActive()) {
4461 mFillStepTimer.start(mViewItemJobStepIdleInterval);
4466void ModelPrivate::slotStorageModelHeaderDataChanged(
Qt::Orientation,
int,
int)
4468 if (mStorageModelContainsOutboundMessages != mStorageModel->containsOutboundMessages()) {
4469 mStorageModelContainsOutboundMessages = mStorageModel->containsOutboundMessages();
4470 Q_EMIT q->headerDataChanged(
Qt::Horizontal, 0, q->columnCount());
4480 Q_ASSERT(d->mModelForItemFunctions);
4518 return storageModel()->mimeData(msgs);
4523 return d->mRootItem;
4533 if (!d->mStorageModel) {
4536 auto idx = d->mInvariantRowMapper->modelIndexRowToModelInvariantIndex(row);
4546 if (!d->mPersistentSetManager) {
4550 MessageItemSetReference ref = d->mPersistentSetManager->createSet();
4551 for (
const auto mi : items) {
4552 d->mPersistentSetManager->addMessageItem(ref,
mi);
4560 if (d->mPersistentSetManager) {
4561 return d->mPersistentSetManager->messageItems(ref);
4568 if (!d->mPersistentSetManager) {
4572 d->mPersistentSetManager->removeSet(ref);
4574 if (d->mPersistentSetManager->setCount() < 1) {
4575 delete d->mPersistentSetManager;
4576 d->mPersistentSetManager =
nullptr;
4580#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)
bool isValid(QStringView ifopt)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
const QList< QKeySequence > & end()
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
Qt::DayOfWeek firstDayOfWeek() 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
T qobject_cast(QObject *object)
QList< QQuickItem * > childItems() const const
iterator insert(const T &value)
QString & insert(qsizetype position, QChar ch)
QByteArray toUtf8() const const
QVariant fromValue(T &&value)