summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/base/src/nsMsgThreadedDBView.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/base/src/nsMsgThreadedDBView.cpp')
-rw-r--r--comm/mailnews/base/src/nsMsgThreadedDBView.cpp899
1 files changed, 899 insertions, 0 deletions
diff --git a/comm/mailnews/base/src/nsMsgThreadedDBView.cpp b/comm/mailnews/base/src/nsMsgThreadedDBView.cpp
new file mode 100644
index 0000000000..84105bc4fc
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgThreadedDBView.cpp
@@ -0,0 +1,899 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsMsgThreadedDBView.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgThread.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIMsgSearchSession.h"
+#include "nsMsgMessageFlags.h"
+
+// Allocate this more to avoid reallocation on new mail.
+#define MSGHDR_CACHE_LOOK_AHEAD_SIZE 25
+// Max msghdr cache entries.
+#define MSGHDR_CACHE_MAX_SIZE 8192
+#define MSGHDR_CACHE_DEFAULT_SIZE 100
+
+nsMsgThreadedDBView::nsMsgThreadedDBView() {
+ /* member initializers and constructor code */
+ m_havePrevView = false;
+}
+
+nsMsgThreadedDBView::~nsMsgThreadedDBView() {} /* destructor code */
+
+NS_IMETHODIMP
+nsMsgThreadedDBView::Open(nsIMsgFolder* folder, nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder,
+ nsMsgViewFlagsTypeValue viewFlags, int32_t* pCount) {
+ nsresult rv =
+ nsMsgDBView::Open(folder, sortType, sortOrder, viewFlags, pCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!m_db) return NS_ERROR_NULL_POINTER;
+
+ // Preset msg hdr cache size for performance reason.
+ int32_t totalMessages, unreadMessages;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ PersistFolderInfo(getter_AddRefs(dbFolderInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Save off sort type and order, view type and flags.
+ dbFolderInfo->GetNumUnreadMessages(&unreadMessages);
+ dbFolderInfo->GetNumMessages(&totalMessages);
+ if (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly) {
+ // Set unread msg size + extra entries to avoid reallocation on new mail.
+ totalMessages = (uint32_t)unreadMessages + MSGHDR_CACHE_LOOK_AHEAD_SIZE;
+ } else {
+ if (totalMessages > MSGHDR_CACHE_MAX_SIZE)
+ // Use max default.
+ totalMessages = MSGHDR_CACHE_MAX_SIZE;
+ else if (totalMessages > 0)
+ // Allocate extra entries to avoid reallocation on new mail.
+ totalMessages += MSGHDR_CACHE_LOOK_AHEAD_SIZE;
+ }
+
+ // If total messages is 0, then we probably don't have any idea how many
+ // headers are in the db so we have no business setting the cache size.
+ if (totalMessages > 0) m_db->SetMsgHdrCacheSize((uint32_t)totalMessages);
+
+ int32_t count;
+ rv = InitThreadedView(count);
+ if (pCount) *pCount = count;
+
+ // This is a hack, but we're trying to find a way to correct
+ // incorrect total and unread msg counts w/o paying a big
+ // performance penalty. So, if we're not threaded, just add
+ // up the total and unread messages in the view and see if that
+ // matches what the db totals say. Except ignored threads are
+ // going to throw us off...hmm. Unless we just look at the
+ // unread counts which is what mostly tweaks people anyway...
+ int32_t unreadMsgsInView = 0;
+ if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) {
+ for (uint32_t i = m_flags.Length(); i > 0;) {
+ if (!(m_flags[--i] & nsMsgMessageFlags::Read)) ++unreadMsgsInView;
+ }
+
+ if (unreadMessages != unreadMsgsInView) m_db->SyncCounts();
+ }
+
+ m_db->SetMsgHdrCacheSize(MSGHDR_CACHE_DEFAULT_SIZE);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgThreadedDBView::Close() { return nsMsgDBView::Close(); }
+
+// Populate the view with the ids of the first message in each thread.
+nsresult nsMsgThreadedDBView::InitThreadedView(int32_t& count) {
+ count = 0;
+ m_keys.Clear();
+ m_flags.Clear();
+ m_levels.Clear();
+ m_prevKeys.Clear();
+ m_prevFlags.Clear();
+ m_prevLevels.Clear();
+ m_havePrevView = false;
+
+ bool unreadOnly = (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly);
+
+ nsCOMPtr<nsIMsgThreadEnumerator> threads;
+ nsresult rv = m_db->EnumerateThreads(getter_AddRefs(threads));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(rv = threads->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsIMsgThread> threadHdr;
+ rv = threads->GetNext(getter_AddRefs(threadHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numChildren;
+ if (unreadOnly)
+ threadHdr->GetNumUnreadChildren(&numChildren);
+ else
+ threadHdr->GetNumChildren(&numChildren);
+
+ if (numChildren == 0) {
+ continue; // An empty thread.
+ }
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ if (unreadOnly) {
+ rv = threadHdr->GetFirstUnreadChild(getter_AddRefs(msgHdr));
+ } else {
+ rv = threadHdr->GetRootHdr(getter_AddRefs(msgHdr));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Hook to allow derived classes to filter out unwanted threads.
+ if (!WantsThisThread(threadHdr)) {
+ continue;
+ }
+
+ uint32_t msgFlags;
+ msgHdr->GetFlags(&msgFlags);
+ // Turn off high byte of msg flags - used for view flags.
+ msgFlags &= ~MSG_VIEW_FLAGS;
+ // Turn off these flags on msg hdr - they belong in thread.
+ uint32_t newMsgFlagsUnused;
+ msgHdr->AndFlags(~(nsMsgMessageFlags::Watched), &newMsgFlagsUnused);
+ AdjustReadFlag(msgHdr, &msgFlags);
+ // Try adding in MSG_VIEW_FLAG_ISTHREAD flag for unreadonly view.
+ uint32_t threadFlags;
+ threadHdr->GetFlags(&threadFlags);
+ msgFlags |= MSG_VIEW_FLAG_ISTHREAD | threadFlags;
+ if (numChildren > 1) {
+ msgFlags |= MSG_VIEW_FLAG_HASCHILDREN;
+ }
+
+ if (!(m_viewFlags & nsMsgViewFlagsType::kShowIgnored)) {
+ // Skip ignored threads.
+ if (msgFlags & nsMsgMessageFlags::Ignored) {
+ continue;
+ }
+ // Skip ignored subthreads
+ bool killed;
+ msgHdr->GetIsKilled(&killed);
+ if (killed) {
+ continue;
+ }
+ }
+
+ // By default, make threads collapsed unless we're only viewing new msgs.
+ if (msgFlags & MSG_VIEW_FLAG_HASCHILDREN) {
+ msgFlags |= nsMsgMessageFlags::Elided;
+ }
+
+ // OK, now add it to the view!
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ m_keys.AppendElement(msgKey);
+ m_flags.AppendElement(msgFlags);
+ m_levels.AppendElement(0);
+
+ // We expand as we build the view, which allows us to insert at the end
+ // of the key array, instead of the middle, and is much faster.
+ if ((!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) ||
+ m_viewFlags & nsMsgViewFlagsType::kExpandAll) &&
+ msgFlags & nsMsgMessageFlags::Elided) {
+ ExpandByIndex(m_keys.Length() - 1, nullptr);
+ }
+
+ count++;
+ }
+
+ rv = InitSort(m_sortType, m_sortOrder);
+ SaveSortInfo(m_sortType, m_sortOrder);
+ return rv;
+}
+
+nsresult nsMsgThreadedDBView::SortThreads(nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder) {
+ NS_ASSERTION(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay,
+ "trying to sort unthreaded threads");
+
+ uint32_t numThreads = 0;
+ // The idea here is that copy the current view, then build up an m_keys and
+ // m_flags array of just the top level messages in the view, and then call
+ // nsMsgDBView::Sort(sortType, sortOrder).
+ // Then, we expand the threads in the result array that were expanded in the
+ // original view (perhaps by copying from the original view, but more likely
+ // just be calling expand).
+ for (uint32_t i = 0; i < m_keys.Length(); i++) {
+ if (m_flags[i] & MSG_VIEW_FLAG_ISTHREAD) {
+ if (numThreads < i) {
+ m_keys[numThreads] = m_keys[i];
+ m_flags[numThreads] = m_flags[i];
+ }
+
+ m_levels[numThreads] = 0;
+ numThreads++;
+ }
+ }
+
+ m_keys.SetLength(numThreads);
+ m_flags.SetLength(numThreads);
+ m_levels.SetLength(numThreads);
+ // m_viewFlags &= ~nsMsgViewFlagsType::kThreadedDisplay;
+ m_sortType = nsMsgViewSortType::byNone; // sort from scratch
+ nsMsgDBView::Sort(sortType, sortOrder);
+ m_viewFlags |= nsMsgViewFlagsType::kThreadedDisplay;
+ SetSuppressChangeNotifications(true);
+
+ // Loop through the original array, for each thread that's expanded,
+ // find it in the new array and expand the thread. We have to update
+ // MSG_VIEW_FLAG_HAS_CHILDREN because we may be going from a flat sort,
+ // which doesn't maintain that flag, to a threaded sort, which requires
+ // that flag.
+ for (uint32_t j = 0; j < m_keys.Length(); j++) {
+ uint32_t flags = m_flags[j];
+ if ((flags & (MSG_VIEW_FLAG_HASCHILDREN | nsMsgMessageFlags::Elided)) ==
+ MSG_VIEW_FLAG_HASCHILDREN) {
+ uint32_t numExpanded;
+ m_flags[j] = flags | nsMsgMessageFlags::Elided;
+ ExpandByIndex(j, &numExpanded);
+ j += numExpanded;
+ if (numExpanded > 0)
+ m_flags[j - numExpanded] = flags | MSG_VIEW_FLAG_HASCHILDREN;
+ } else if (flags & MSG_VIEW_FLAG_ISTHREAD &&
+ !(flags & MSG_VIEW_FLAG_HASCHILDREN)) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCOMPtr<nsIMsgThread> pThread;
+ m_db->GetMsgHdrForKey(m_keys[j], getter_AddRefs(msgHdr));
+ if (msgHdr) {
+ m_db->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(pThread));
+ if (pThread) {
+ uint32_t numChildren;
+ pThread->GetNumChildren(&numChildren);
+ if (numChildren > 1)
+ m_flags[j] =
+ flags | MSG_VIEW_FLAG_HASCHILDREN | nsMsgMessageFlags::Elided;
+ }
+ }
+ }
+ }
+
+ SetSuppressChangeNotifications(false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgThreadedDBView::Sort(nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder) {
+ nsresult rv;
+
+ int32_t rowCountBeforeSort = GetSize();
+
+ if (!rowCountBeforeSort) {
+ // Still need to setup our flags even when no articles - bug 98183.
+ m_sortType = sortType;
+ m_sortOrder = sortOrder;
+ if (sortType == nsMsgViewSortType::byThread &&
+ !(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) {
+ SetViewFlags(m_viewFlags | nsMsgViewFlagsType::kThreadedDisplay);
+ }
+
+ SaveSortInfo(sortType, sortOrder);
+ return NS_OK;
+ }
+
+ if (!m_checkedCustomColumns && CustomColumnsInSortAndNotRegistered())
+ return NS_OK;
+
+ // Sort threads by sort order.
+ bool sortThreads = m_viewFlags & (nsMsgViewFlagsType::kThreadedDisplay |
+ nsMsgViewFlagsType::kGroupBySort);
+
+ // If sort type is by thread, and we're already threaded, change sort type
+ // to byId.
+ if (sortType == nsMsgViewSortType::byThread &&
+ (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) != 0) {
+ sortType = nsMsgViewSortType::byId;
+ }
+
+ nsMsgKey preservedKey;
+ AutoTArray<nsMsgKey, 1> preservedSelection;
+ SaveAndClearSelection(&preservedKey, preservedSelection);
+ // If the client wants us to forget our cached id arrays, they
+ // should build a new view. If this isn't good enough, we
+ // need a method to do that.
+ if (sortType != m_sortType || !m_sortValid || sortThreads) {
+ SaveSortInfo(sortType, sortOrder);
+ if (sortType == nsMsgViewSortType::byThread) {
+ m_sortType = sortType;
+ m_viewFlags |= nsMsgViewFlagsType::kThreadedDisplay;
+ m_viewFlags &= ~nsMsgViewFlagsType::kGroupBySort;
+ if (m_havePrevView) {
+ // Restore saved id array and flags array.
+ m_keys = m_prevKeys.Clone();
+ m_flags = m_prevFlags.Clone();
+ m_levels = m_prevLevels.Clone();
+ m_sortValid = true;
+
+ // The sort may have changed the number of rows
+ // before we restore the selection, tell the tree
+ // do this before we call restore selection
+ // this is safe when there is no selection.
+ rv = AdjustRowCount(rowCountBeforeSort, GetSize());
+
+ RestoreSelection(preservedKey, preservedSelection);
+ if (mTree) mTree->Invalidate();
+ if (mJSTree) mJSTree->Invalidate();
+
+ return NS_OK;
+ } else {
+ // Set sort info in anticipation of what Init will do.
+ // Build up thread list.
+ int32_t unused; // count.
+ InitThreadedView(unused);
+ if (sortOrder != nsMsgViewSortOrder::ascending)
+ Sort(sortType, sortOrder);
+
+ // The sort may have changed the number of rows
+ // before we update the selection, tell the tree
+ // do this before we call restore selection
+ // this is safe when there is no selection.
+ rv = AdjustRowCount(rowCountBeforeSort, GetSize());
+
+ RestoreSelection(preservedKey, preservedSelection);
+ if (mTree) mTree->Invalidate();
+ if (mJSTree) mJSTree->Invalidate();
+
+ return NS_OK;
+ }
+ } else if (sortType != nsMsgViewSortType::byThread &&
+ (m_sortType == nsMsgViewSortType::byThread || sortThreads)
+ /* && !m_havePrevView*/) {
+ if (sortThreads) {
+ SortThreads(sortType, sortOrder);
+ // Hack so base class won't do anything.
+ sortType = nsMsgViewSortType::byThread;
+ } else {
+ // Going from SortByThread to non-thread sort - must build new key,
+ // level, and flags arrays.
+ m_prevKeys = m_keys.Clone();
+ m_prevFlags = m_flags.Clone();
+ m_prevLevels = m_levels.Clone();
+ // Do this before we sort, so that we'll use the cheap method
+ // of expanding.
+ m_viewFlags &= ~(nsMsgViewFlagsType::kThreadedDisplay |
+ nsMsgViewFlagsType::kGroupBySort);
+ ExpandAll();
+ // m_idArray.RemoveAll();
+ // m_flags.Clear();
+ m_havePrevView = true;
+ }
+ }
+ } else if (m_sortOrder != sortOrder) {
+ // Check for toggling the sort.
+ nsMsgDBView::Sort(sortType, sortOrder);
+ }
+
+ if (!sortThreads) {
+ // Call the base class in case we're not sorting by thread.
+ rv = nsMsgDBView::Sort(sortType, sortOrder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ SaveSortInfo(sortType, sortOrder);
+ }
+
+ // The sort may have changed the number of rows
+ // before we restore the selection, tell the tree
+ // do this before we call restore selection
+ // this is safe when there is no selection.
+ rv = AdjustRowCount(rowCountBeforeSort, GetSize());
+
+ RestoreSelection(preservedKey, preservedSelection);
+ if (mTree) mTree->Invalidate();
+ if (mJSTree) mJSTree->Invalidate();
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+void nsMsgThreadedDBView::OnExtraFlagChanged(nsMsgViewIndex index,
+ uint32_t extraFlag) {
+ if (IsValidIndex(index)) {
+ if (m_havePrevView) {
+ nsMsgKey keyChanged = m_keys[index];
+ nsMsgViewIndex prevViewIndex = m_prevKeys.IndexOf(keyChanged);
+ if (prevViewIndex != nsMsgViewIndex_None) {
+ uint32_t prevFlag = m_prevFlags[prevViewIndex];
+ // Don't want to change the elided bit, or has children or is thread.
+ if (prevFlag & nsMsgMessageFlags::Elided)
+ extraFlag |= nsMsgMessageFlags::Elided;
+ else
+ extraFlag &= ~nsMsgMessageFlags::Elided;
+
+ if (prevFlag & MSG_VIEW_FLAG_ISTHREAD)
+ extraFlag |= MSG_VIEW_FLAG_ISTHREAD;
+ else
+ extraFlag &= ~MSG_VIEW_FLAG_ISTHREAD;
+
+ if (prevFlag & MSG_VIEW_FLAG_HASCHILDREN)
+ extraFlag |= MSG_VIEW_FLAG_HASCHILDREN;
+ else
+ extraFlag &= ~MSG_VIEW_FLAG_HASCHILDREN;
+
+ // Will this be right?
+ m_prevFlags[prevViewIndex] = extraFlag;
+ }
+ }
+ }
+
+ // We don't really know what's changed, but to be on the safe side, set the
+ // sort invalid so that reverse sort will pick it up.
+ if (m_sortType == nsMsgViewSortType::byStatus ||
+ m_sortType == nsMsgViewSortType::byFlagged ||
+ m_sortType == nsMsgViewSortType::byUnread ||
+ m_sortType == nsMsgViewSortType::byPriority) {
+ m_sortValid = false;
+ }
+}
+
+void nsMsgThreadedDBView::OnHeaderAddedOrDeleted() { ClearPrevIdArray(); }
+
+void nsMsgThreadedDBView::ClearPrevIdArray() {
+ m_prevKeys.Clear();
+ m_prevLevels.Clear();
+ m_prevFlags.Clear();
+ m_havePrevView = false;
+}
+
+nsresult nsMsgThreadedDBView::InitSort(nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder) {
+ // Nothing to do.
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort) return NS_OK;
+
+ if (sortType == nsMsgViewSortType::byThread) {
+ // Sort top level threads by id.
+ nsMsgDBView::Sort(nsMsgViewSortType::byId, sortOrder);
+ m_sortType = nsMsgViewSortType::byThread;
+ m_viewFlags |= nsMsgViewFlagsType::kThreadedDisplay;
+ m_viewFlags &= ~nsMsgViewFlagsType::kGroupBySort;
+ // Persist the view flags.
+ SetViewFlags(m_viewFlags);
+ // m_db->SetSortInfo(m_sortType, sortOrder);
+ }
+ // else
+ // m_viewFlags &= ~nsMsgViewFlagsType::kThreadedDisplay;
+
+ // By default, the unread only view should have all threads expanded.
+ if ((m_viewFlags &
+ (nsMsgViewFlagsType::kUnreadOnly | nsMsgViewFlagsType::kExpandAll)) &&
+ (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) {
+ ExpandAll();
+ }
+
+ if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) {
+ // For now, expand all and do a flat sort.
+ ExpandAll();
+ }
+
+ Sort(sortType, sortOrder);
+ if (sortType != nsMsgViewSortType::byThread) {
+ // Forget prev view, since it has everything expanded.
+ ClearPrevIdArray();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgThreadedDBView::OnNewHeader(nsIMsgDBHdr* newHdr,
+ nsMsgKey aParentKey,
+ bool ensureListed) {
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+ return nsMsgGroupView::OnNewHeader(newHdr, aParentKey, ensureListed);
+
+ NS_ENSURE_TRUE(newHdr, NS_MSG_MESSAGE_NOT_FOUND);
+
+ nsMsgKey newKey;
+ newHdr->GetMessageKey(&newKey);
+
+ // Views can override this behaviour, which is to append to view.
+ // This is the mail behaviour, but threaded views want
+ // to insert in order...
+ uint32_t msgFlags;
+ newHdr->GetFlags(&msgFlags);
+ if (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly && !ensureListed &&
+ msgFlags & nsMsgMessageFlags::Read) {
+ return NS_OK;
+ }
+
+ // Currently, we only add the header in a threaded view if it's a thread.
+ // We used to check if this was the first header in the thread, but that's
+ // a bit harder in the unreadOnly view. But we'll catch it below.
+
+ // If not threaded display just add it to the view.
+ if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ return AddHdr(newHdr);
+
+ // Need to find the thread we added this to so we can change the hasnew flag
+ // added message to existing thread, but not to view.
+ // Fix flags on thread header.
+ int32_t threadCount;
+ uint32_t threadFlags;
+ bool moveThread = false;
+ nsMsgViewIndex threadIndex =
+ ThreadIndexOfMsg(newKey, nsMsgViewIndex_None, &threadCount, &threadFlags);
+ bool threadRootIsDisplayed = false;
+
+ nsCOMPtr<nsIMsgThread> threadHdr;
+ m_db->GetThreadContainingMsgHdr(newHdr, getter_AddRefs(threadHdr));
+ if (threadHdr && m_sortType == nsMsgViewSortType::byDate) {
+ uint32_t newestMsgInThread = 0, msgDate = 0;
+ threadHdr->GetNewestMsgDate(&newestMsgInThread);
+ newHdr->GetDateInSeconds(&msgDate);
+ moveThread = (msgDate == newestMsgInThread);
+ }
+
+ if (threadIndex != nsMsgViewIndex_None) {
+ threadRootIsDisplayed = (m_currentlyDisplayedViewIndex == threadIndex);
+ uint32_t flags = m_flags[threadIndex];
+ if (!(flags & MSG_VIEW_FLAG_HASCHILDREN)) {
+ flags |= MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_ISTHREAD;
+ if (!(m_viewFlags & nsMsgViewFlagsType::kUnreadOnly))
+ flags |= nsMsgMessageFlags::Elided;
+
+ m_flags[threadIndex] = flags;
+ }
+
+ if (!(flags & nsMsgMessageFlags::Elided)) {
+ // Thread is expanded.
+ // Insert child into thread.
+ // Levels of other hdrs may have changed!
+ uint32_t newFlags = msgFlags;
+ int32_t level = 0;
+ nsMsgViewIndex insertIndex = threadIndex;
+ if (aParentKey == nsMsgKey_None) {
+ newFlags |= MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN;
+ } else {
+ nsMsgViewIndex parentIndex =
+ FindParentInThread(aParentKey, threadIndex);
+ level = m_levels[parentIndex] + 1;
+ insertIndex = GetInsertInfoForNewHdr(newHdr, parentIndex, level);
+ }
+
+ InsertMsgHdrAt(insertIndex, newHdr, newKey, newFlags, level);
+ // The call to NoteChange() has to happen after we add the key as
+ // NoteChange() will call RowCountChanged() which will call our
+ // GetRowCount().
+ NoteChange(insertIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
+
+ if (aParentKey == nsMsgKey_None) {
+ // this header is the new king! try collapsing the existing thread,
+ // removing it, installing this header as king, and expanding it.
+ CollapseByIndex(threadIndex, nullptr);
+ // call base class, so child won't get promoted.
+ // nsMsgDBView::RemoveByIndex(threadIndex);
+ ExpandByIndex(threadIndex, nullptr);
+ }
+ } else if (aParentKey == nsMsgKey_None) {
+ // if we have a collapsed thread which just got a new
+ // top of thread, change the keys array.
+ m_keys[threadIndex] = newKey;
+ }
+
+ // If this message is new, the thread is collapsed, it is the
+ // root and it was displayed, expand it so that the user does
+ // not find that their message has magically turned into a summary.
+ if (msgFlags & nsMsgMessageFlags::New &&
+ m_flags[threadIndex] & nsMsgMessageFlags::Elided &&
+ threadRootIsDisplayed)
+ ExpandByIndex(threadIndex, nullptr);
+
+ if (moveThread)
+ MoveThreadAt(threadIndex);
+ else
+ // note change, to update the parent thread's unread and total counts
+ NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
+ } else if (threadHdr) {
+ // Adding msg to thread that's not in view.
+ AddMsgToThreadNotInView(threadHdr, newHdr, ensureListed);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgThreadedDBView::OnParentChanged(nsMsgKey aKeyChanged, nsMsgKey oldParent,
+ nsMsgKey newParent,
+ nsIDBChangeListener* aInstigator) {
+ // We need to adjust the level of the hdr whose parent changed, and
+ // invalidate that row, iff we're in threaded mode.
+#if 0
+ // This code never runs due to the if (false) and Clang complains about it
+ // so it is ifdefed out for now.
+ if (false && m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+ {
+ nsMsgViewIndex childIndex = FindViewIndex(aKeyChanged);
+ if (childIndex != nsMsgViewIndex_None)
+ {
+ nsMsgViewIndex parentIndex = FindViewIndex(newParent);
+ int32_t newParentLevel =
+ (parentIndex == nsMsgViewIndex_None) ? -1 : m_levels[parentIndex];
+
+ nsMsgViewIndex oldParentIndex = FindViewIndex(oldParent);
+
+ int32_t oldParentLevel =
+ (oldParentIndex != nsMsgViewIndex_None ||
+ newParent == nsMsgKey_None) ? m_levels[oldParentIndex] : -1 ;
+
+ int32_t levelChanged = m_levels[childIndex];
+ int32_t parentDelta = oldParentLevel - newParentLevel;
+ m_levels[childIndex] = (newParent == nsMsgKey_None) ? 0 : newParentLevel + 1;
+ if (parentDelta > 0)
+ {
+ for (nsMsgViewIndex viewIndex = childIndex + 1;
+ viewIndex < GetSize() && m_levels[viewIndex] > levelChanged;
+ viewIndex++)
+ {
+ m_levels[viewIndex] = m_levels[viewIndex] - parentDelta;
+ NoteChange(viewIndex, 1, nsMsgViewNotificationCode::changed);
+ }
+ }
+
+ NoteChange(childIndex, 1, nsMsgViewNotificationCode::changed);
+ }
+ }
+#endif
+ return NS_OK;
+}
+
+nsMsgViewIndex nsMsgThreadedDBView::GetInsertInfoForNewHdr(
+ nsIMsgDBHdr* newHdr, nsMsgViewIndex parentIndex, int32_t targetLevel) {
+ uint32_t viewSize = GetSize();
+ while (++parentIndex < viewSize) {
+ // Loop until we find a message at a level less than or equal to the
+ // parent level
+ if (m_levels[parentIndex] < targetLevel) break;
+ }
+
+ return parentIndex;
+}
+
+// This method removes the thread at threadIndex from the view
+// and puts it back in its new position, determined by the sort order.
+// And, if the selection is affected, save and restore the selection.
+void nsMsgThreadedDBView::MoveThreadAt(nsMsgViewIndex threadIndex) {
+ // We need to check if the thread is collapsed or not...
+ // We want to turn off tree notifications so that we don't
+ // reload the current message.
+ // We also need to invalidate the range between where the thread was
+ // and where it ended up.
+ bool changesDisabled = mSuppressChangeNotification;
+ if (!changesDisabled) SetSuppressChangeNotifications(true);
+
+ nsCOMPtr<nsIMsgDBHdr> threadHdr;
+
+ GetMsgHdrForViewIndex(threadIndex, getter_AddRefs(threadHdr));
+ int32_t childCount = 0;
+
+ nsMsgKey preservedKey;
+ AutoTArray<nsMsgKey, 1> preservedSelection;
+ int32_t selectionCount;
+ int32_t currentIndex;
+ bool hasSelection =
+ mTreeSelection &&
+ ((NS_SUCCEEDED(mTreeSelection->GetCurrentIndex(&currentIndex)) &&
+ currentIndex >= 0 && (uint32_t)currentIndex < GetSize()) ||
+ (NS_SUCCEEDED(mTreeSelection->GetRangeCount(&selectionCount)) &&
+ selectionCount > 0));
+ if (hasSelection) SaveAndClearSelection(&preservedKey, preservedSelection);
+
+ uint32_t saveFlags = m_flags[threadIndex];
+ bool threadIsExpanded = !(saveFlags & nsMsgMessageFlags::Elided);
+
+ if (threadIsExpanded) {
+ ExpansionDelta(threadIndex, &childCount);
+ childCount = -childCount;
+ }
+
+ nsTArray<nsMsgKey> threadKeys;
+ nsTArray<uint32_t> threadFlags;
+ nsTArray<uint8_t> threadLevels;
+
+ if (threadIsExpanded) {
+ threadKeys.SetCapacity(childCount);
+ threadFlags.SetCapacity(childCount);
+ threadLevels.SetCapacity(childCount);
+ for (nsMsgViewIndex index = threadIndex + 1;
+ index < GetSize() && m_levels[index]; index++) {
+ threadKeys.AppendElement(m_keys[index]);
+ threadFlags.AppendElement(m_flags[index]);
+ threadLevels.AppendElement(m_levels[index]);
+ }
+
+ uint32_t collapseCount;
+ CollapseByIndex(threadIndex, &collapseCount);
+ }
+
+ nsMsgDBView::RemoveByIndex(threadIndex);
+ nsMsgViewIndex newIndex = nsMsgViewIndex_None;
+ AddHdr(threadHdr, &newIndex);
+
+ // AddHdr doesn't always set newIndex, and getting it to do so
+ // is going to require some refactoring.
+ if (newIndex == nsMsgViewIndex_None) newIndex = FindHdr(threadHdr);
+
+ if (threadIsExpanded) {
+ m_keys.InsertElementsAt(newIndex + 1, threadKeys);
+ m_flags.InsertElementsAt(newIndex + 1, threadFlags);
+ m_levels.InsertElementsAt(newIndex + 1, threadLevels);
+ }
+
+ if (newIndex == nsMsgViewIndex_None) {
+ NS_WARNING("newIndex=-1 in MoveThreadAt");
+ newIndex = 0;
+ }
+
+ m_flags[newIndex] = saveFlags;
+ // Unfreeze selection.
+ if (hasSelection) RestoreSelection(preservedKey, preservedSelection);
+
+ if (!changesDisabled) SetSuppressChangeNotifications(false);
+
+ nsMsgViewIndex lowIndex = threadIndex < newIndex ? threadIndex : newIndex;
+ nsMsgViewIndex highIndex = lowIndex == threadIndex ? newIndex : threadIndex;
+
+ NoteChange(lowIndex, highIndex - lowIndex + childCount + 1,
+ nsMsgViewNotificationCode::changed);
+}
+
+nsresult nsMsgThreadedDBView::AddMsgToThreadNotInView(nsIMsgThread* threadHdr,
+ nsIMsgDBHdr* msgHdr,
+ bool ensureListed) {
+ nsresult rv = NS_OK;
+ uint32_t threadFlags;
+ threadHdr->GetFlags(&threadFlags);
+ if (!(threadFlags & nsMsgMessageFlags::Ignored)) {
+ bool msgKilled;
+ msgHdr->GetIsKilled(&msgKilled);
+ if (!msgKilled) rv = nsMsgDBView::AddHdr(msgHdr);
+ }
+
+ return rv;
+}
+
+// This method just removes the specified line from the view. It does
+// NOT delete it from the database.
+nsresult nsMsgThreadedDBView::RemoveByIndex(nsMsgViewIndex index) {
+ nsresult rv = NS_OK;
+ int32_t flags;
+
+ if (!IsValidIndex(index)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ OnHeaderAddedOrDeleted();
+
+ flags = m_flags[index];
+
+ if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ return nsMsgDBView::RemoveByIndex(index);
+
+ nsCOMPtr<nsIMsgThread> threadHdr;
+ GetThreadContainingIndex(index, getter_AddRefs(threadHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t numThreadChildren = 0;
+ // If we can't get a thread, it's already deleted and thus has 0 children.
+ if (threadHdr) threadHdr->GetNumChildren(&numThreadChildren);
+
+ // Check if we're the top level msg in the thread, and we're not collapsed.
+ if ((flags & MSG_VIEW_FLAG_ISTHREAD) &&
+ !(flags & nsMsgMessageFlags::Elided) &&
+ (flags & MSG_VIEW_FLAG_HASCHILDREN)) {
+ // Fix flags on thread header - newly promoted message should have
+ // flags set correctly.
+ if (threadHdr) {
+ nsMsgDBView::RemoveByIndex(index);
+ nsCOMPtr<nsIMsgThread> nextThreadHdr;
+ // Above RemoveByIndex may now make index out of bounds.
+ if (IsValidIndex(index) && numThreadChildren > 0) {
+ // unreadOnly
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = threadHdr->GetChildHdrAt(0, getter_AddRefs(msgHdr));
+ if (msgHdr != nullptr) {
+ uint32_t flag = 0;
+ msgHdr->GetFlags(&flag);
+ if (numThreadChildren > 1)
+ flag |= MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN;
+
+ m_flags[index] = flag;
+ m_levels[index] = 0;
+ }
+ }
+ }
+
+ return rv;
+ } else if (!(flags & MSG_VIEW_FLAG_ISTHREAD)) {
+ // We're not deleting the top level msg, but top level msg might be the
+ // only msg in thread now.
+ if (threadHdr && numThreadChildren == 1) {
+ nsMsgKey msgKey;
+ rv = threadHdr->GetChildKeyAt(0, &msgKey);
+ if (NS_SUCCEEDED(rv)) {
+ nsMsgViewIndex threadIndex = FindViewIndex(msgKey);
+ if (IsValidIndex(threadIndex)) {
+ uint32_t flags = m_flags[threadIndex];
+ flags &= ~(MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided |
+ MSG_VIEW_FLAG_HASCHILDREN);
+ m_flags[threadIndex] = flags;
+ NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
+ }
+ }
+ }
+
+ return nsMsgDBView::RemoveByIndex(index);
+ }
+
+ // Deleting collapsed thread header is special case. Child will be promoted,
+ // so just tell FE that line changed, not that it was deleted.
+ // Header has already been deleted from thread.
+ if (threadHdr && numThreadChildren > 0) {
+ // Change the id array and flags array to reflect the child header.
+ // If we're not deleting the header, we want the second header,
+ // Otherwise, the first one (which just got promoted).
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = threadHdr->GetChildHdrAt(0, getter_AddRefs(msgHdr));
+ if (msgHdr != nullptr) {
+ msgHdr->GetMessageKey(&m_keys[index]);
+ uint32_t flag = 0;
+ msgHdr->GetFlags(&flag);
+ flag |= MSG_VIEW_FLAG_ISTHREAD;
+
+ // If only hdr in thread (with one about to be deleted).
+ if (numThreadChildren == 1) {
+ // Adjust flags.
+ flag &= ~MSG_VIEW_FLAG_HASCHILDREN;
+ flag &= ~nsMsgMessageFlags::Elided;
+ // Tell FE that thread header needs to be repainted.
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+ } else {
+ flag |= MSG_VIEW_FLAG_HASCHILDREN;
+ flag |= nsMsgMessageFlags::Elided;
+ }
+
+ m_flags[index] = flag;
+ mIndicesToNoteChange.RemoveElement(index);
+ } else {
+ NS_ASSERTION(false, "couldn't find thread child");
+ }
+
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+ } else {
+ // We may have deleted a whole, collapsed thread - if so,
+ // ensure that the current index will be noted as changed.
+ if (!mIndicesToNoteChange.Contains(index))
+ mIndicesToNoteChange.AppendElement(index);
+
+ rv = nsMsgDBView::RemoveByIndex(index);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgThreadedDBView::GetViewType(nsMsgViewTypeValue* aViewType) {
+ NS_ENSURE_ARG_POINTER(aViewType);
+ *aViewType = nsMsgViewType::eShowAllThreads;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgThreadedDBView::CloneDBView(nsIMessenger* aMessengerInstance,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCmdUpdater,
+ nsIMsgDBView** _retval) {
+ nsMsgThreadedDBView* newMsgDBView = new nsMsgThreadedDBView();
+
+ if (!newMsgDBView) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv =
+ CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*_retval = newMsgDBView);
+ return NS_OK;
+}