Source code

Revision control

Copy as Markdown

Other Tools

/* 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 "nsMsgThreadedDBView.h"
#include "mozilla/ProfilerMarkers.h"
#include "msgCore.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) {
AUTO_PROFILER_LABEL("nsMsgThreadedDBView::Open", MAILNEWS);
nsresult rv = nsMsgDBView::Open(folder, sortType, sortOrder, viewFlags);
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);
rv = InitThreadedView();
// 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() {
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);
}
}
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.
InitThreadedView();
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;
nsMsgViewIndex threadIndex =
ThreadIndexOfMsg(newKey, nsMsgViewIndex_None, &threadCount, &threadFlags);
nsCOMPtr<nsIMsgThread> threadHdr;
m_db->GetThreadContainingMsgHdr(newHdr, getter_AddRefs(threadHdr));
// Delegate to helper function and return early if the message isn't part
// of a thread that's already in the view.
if (threadIndex == nsMsgViewIndex_None) {
if (threadHdr) {
AddMsgToThreadNotInView(threadHdr, newHdr, ensureListed);
}
return NS_OK;
}
bool moveThread = false;
if (threadHdr &&
(m_sortType == nsMsgViewSortType::byDate ||
m_sortType == nsMsgViewSortType::byReceived) &&
!mSortThreadsByRoot) {
uint32_t newestMsgInThread = 0, msgDate = 0;
threadHdr->GetNewestMsgDate(&newestMsgInThread);
newHdr->GetDateInSeconds(&msgDate);
moveThread = (msgDate == newestMsgInThread);
}
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;
}
bool isExpanded = !(flags & nsMsgMessageFlags::Elided);
// Return early if the message won't be visible in the view and won't change
// the order of the threads.
if (!isExpanded && aParentKey != nsMsgKey_None && !moveThread) {
// Update the parent thread's unread and total counts.
NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
return NS_OK;
}
// At this point, we have to rebuild the thread structure cleanly from the
// database to avoid array desynchronization.
int32_t selectionCount = 0;
int32_t currentIndex = -1;
bool hasSelection =
mTreeSelection &&
((NS_SUCCEEDED(mTreeSelection->GetCurrentIndex(&currentIndex)) &&
currentIndex >= 0 && (uint32_t)currentIndex < GetSize()) ||
(NS_SUCCEEDED(mTreeSelection->GetRangeCount(&selectionCount)) &&
selectionCount > 0));
nsMsgKey preservedKey = nsMsgKey_None;
AutoTArray<nsMsgKey, 1> preservedSelection;
if (hasSelection) {
SaveAndClearSelection(&preservedKey, preservedSelection);
}
bool changesDisabled = mSuppressChangeNotification;
if (!changesDisabled) {
SetSuppressChangeNotifications(true);
}
auto cleanupGuard = mozilla::MakeScopeExit([&] {
if (hasSelection) {
RestoreSelection(preservedKey, preservedSelection);
}
if (!changesDisabled) {
SetSuppressChangeNotifications(false);
// Always Invalidate to ensure the screen reflects the final array state.
if (mTree) {
mTree->Invalidate();
}
if (mJSTree) {
mJSTree->InvalidateRange(0, GetSize() - 1);
}
}
});
// Scrub existing children from the view
if (isExpanded) {
CollapseByIndex(threadIndex, nullptr);
}
nsMsgViewIndex targetIndex = threadIndex;
// Handle root changes or chronological shifts directly
nsMsgViewIndex newIndex = nsMsgViewIndex_None;
nsresult rv = NS_OK;
if (aParentKey == nsMsgKey_None) {
// Restoring a root: swap out the promoted child for the true root.
rv = nsMsgDBView::RemoveByIndex(threadIndex);
NS_ENSURE_SUCCESS(rv, rv);
nsMsgDBView::AddHdr(newHdr, &newIndex);
} else if (moveThread) {
// Chronological shift: move the root to its new sorted position.
nsCOMPtr<nsIMsgDBHdr> existingRoot;
rv = nsMsgDBView::GetMsgHdrForViewIndex(threadIndex,
getter_AddRefs(existingRoot));
NS_ENSURE_SUCCESS(rv, rv);
rv = nsMsgDBView::RemoveByIndex(threadIndex);
NS_ENSURE_SUCCESS(rv, rv);
nsMsgDBView::AddHdr(existingRoot, &newIndex);
}
if (newIndex != nsMsgViewIndex_None) {
targetIndex = newIndex;
m_flags[targetIndex] |=
(MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN);
m_flags[targetIndex] |= nsMsgMessageFlags::Elided;
}
// Rebuild the thread children natively from the DB.
if (isExpanded) {
m_flags[targetIndex] |= nsMsgMessageFlags::Elided;
ExpandByIndex(targetIndex, nullptr);
}
return NS_OK;
}
NS_IMETHODIMP
nsMsgThreadedDBView::OnParentChanged(nsMsgKey aKeyChanged, nsMsgKey oldParent,
nsMsgKey newParent,
nsIDBChangeListener* aInstigator) {
return NS_OK;
}
nsresult nsMsgThreadedDBView::AddMsgToThreadNotInView(nsIMsgThread* threadHdr,
nsIMsgDBHdr* msgHdr,
bool ensureListed) {
if (!(m_viewFlags & nsMsgViewFlagsType::kShowIgnored)) {
uint32_t threadFlags;
threadHdr->GetFlags(&threadFlags);
if (threadFlags & nsMsgMessageFlags::Ignored) {
return NS_OK;
}
}
return nsMsgDBView::AddHdr(msgHdr);
}
// This method just removes the specified line from the view. It does
// NOT delete it from the database.
nsresult nsMsgThreadedDBView::RemoveByIndex(nsMsgViewIndex index) {
if (!IsValidIndex(index)) {
return NS_MSG_INVALID_DBVIEW_INDEX;
}
OnHeaderAddedOrDeleted();
if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) {
return nsMsgDBView::RemoveByIndex(index);
}
nsCOMPtr<nsIMsgThread> threadHdr;
GetThreadContainingIndex(index, getter_AddRefs(threadHdr));
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);
}
int32_t flags = m_flags[index];
// Check for an expanded thread header.
if ((flags & MSG_VIEW_FLAG_ISTHREAD) &&
!(flags & nsMsgMessageFlags::Elided) &&
(flags & MSG_VIEW_FLAG_HASCHILDREN)) {
if (numThreadChildren <= 0) {
return NS_ERROR_UNEXPECTED;
}
int32_t selectionCount = 0;
int32_t currentIndex = -1;
bool hasSelection =
mTreeSelection &&
((NS_SUCCEEDED(mTreeSelection->GetCurrentIndex(&currentIndex)) &&
currentIndex >= 0 && (uint32_t)currentIndex < GetSize()) ||
(NS_SUCCEEDED(mTreeSelection->GetRangeCount(&selectionCount)) &&
selectionCount > 0));
nsMsgKey preservedKey = nsMsgKey_None;
AutoTArray<nsMsgKey, 1> preservedSelection;
if (hasSelection) {
SaveAndClearSelection(&preservedKey, preservedSelection);
}
bool changesDisabled = mSuppressChangeNotification;
if (!changesDisabled) {
SetSuppressChangeNotifications(true);
}
auto cleanupGuard = mozilla::MakeScopeExit([&] {
if (hasSelection) {
RestoreSelection(preservedKey, preservedSelection);
}
if (!changesDisabled) {
SetSuppressChangeNotifications(false);
// Always Invalidate to ensure the screen reflects the final array
// state.
if (mTree) {
mTree->Invalidate();
}
if (mJSTree) {
mJSTree->InvalidateRange(0, GetSize() - 1);
}
}
});
// Scrub stale children natively.
CollapseByIndex(index, nullptr);
nsresult rv = nsMsgDBView::RemoveByIndex(index);
NS_ENSURE_SUCCESS(rv, rv);
// Add newly promoted root and let Mork natively calculate the layout and
// indentation.
nsCOMPtr<nsIMsgDBHdr> newRootHdr;
threadHdr->GetChildHdrAt(0, getter_AddRefs(newRootHdr));
if (!newRootHdr) {
return NS_ERROR_UNEXPECTED;
}
nsMsgViewIndex newIndex = nsMsgViewIndex_None;
nsMsgDBView::AddHdr(newRootHdr, &newIndex);
if (newIndex != nsMsgViewIndex_None) {
m_flags[newIndex] |= MSG_VIEW_FLAG_ISTHREAD;
if (numThreadChildren > 1) {
m_flags[newIndex] |=
MSG_VIEW_FLAG_HASCHILDREN | nsMsgMessageFlags::Elided;
ExpandByIndex(newIndex, nullptr);
}
}
return NS_OK;
}
// Check non-thread header with thread parent.
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) {
nsMsgKey msgKey;
if (NS_SUCCEEDED(threadHdr->GetChildKeyAt(0, &msgKey))) {
nsMsgViewIndex threadIndex = FindViewIndex(msgKey);
if (IsValidIndex(threadIndex)) {
if (numThreadChildren == 1) {
uint32_t flags = m_flags[threadIndex];
flags &= ~(nsMsgMessageFlags::Elided | MSG_VIEW_FLAG_HASCHILDREN);
m_flags[threadIndex] = flags;
}
// Notify about change in top level message in any case to have the
// count of total messages updated if shown.
NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
}
}
}
// Capture the level of the child we are about to delete
uint32_t deletedLevel = m_levels[index];
nsresult rv = nsMsgDBView::RemoveByIndex(index);
NS_ENSURE_SUCCESS(rv, rv);
// If the deleted child had children of its own, the DB reparents them to
// the deleted child's parent. Visually shift their indentation left by 1.
nsMsgViewIndex i = index;
while (i < GetSize() && m_levels[i] > deletedLevel) {
m_levels[i]--;
i++;
}
int32_t numChildrenShifted = i - index;
if (numChildrenShifted > 0) {
NoteChange(index, numChildrenShifted, nsMsgViewNotificationCode::changed);
}
return NS_OK;
}
// 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;
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);
return NS_OK;
}
// 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);
return nsMsgDBView::RemoveByIndex(index);
}
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;
}