nsresult nsMsgQuickSearchDBView::ListIdsInThreadOrder(nsIMsgThread *threadHdr, nsMsgKey parentKey, uint32_t level, uint32_t callLevel, nsMsgKey keyToSkip, nsMsgViewIndex *viewIndex, uint32_t *pNumListed) { nsCOMPtr <nsISimpleEnumerator> msgEnumerator; nsresult rv = threadHdr->EnumerateMessages(parentKey, getter_AddRefs(msgEnumerator)); NS_ENSURE_SUCCESS(rv, rv); // We use the numChildren as a sanity check on the thread structure. uint32_t numChildren; (void) threadHdr->GetNumChildren(&numChildren); bool hasMore; nsCOMPtr <nsISupports> supports; nsCOMPtr <nsIMsgDBHdr> msgHdr; while (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv = msgEnumerator->HasMoreElements(&hasMore)) && hasMore) { rv = msgEnumerator->GetNext(getter_AddRefs(supports)); if (NS_SUCCEEDED(rv) && supports) { msgHdr = do_QueryInterface(supports); nsMsgKey msgKey; msgHdr->GetMessageKey(&msgKey); if (msgKey == keyToSkip) continue; // If we discover depths of more than numChildren, it means we have // some sort of circular thread relationship and we bail out of the // while loop before overflowing the stack with recursive calls. // Technically, this is an error, but forcing a database rebuild // is too destructive so we just return. if (*pNumListed > numChildren || callLevel > numChildren) { NS_ERROR("loop in message threading while listing children"); return NS_OK; } int32_t childLevel = level; if (m_origKeys.BinaryIndexOf(msgKey) != m_origKeys.NoIndex) { uint32_t msgFlags; msgHdr->GetFlags(&msgFlags); InsertMsgHdrAt(*viewIndex, msgHdr, msgKey, msgFlags & ~MSG_VIEW_FLAGS, level); (*pNumListed)++; (*viewIndex)++; childLevel++; } rv = ListIdsInThreadOrder(threadHdr, msgKey, childLevel, callLevel + 1, keyToSkip, viewIndex, pNumListed); } } return rv; }
nsresult nsMsgQuickSearchDBView::ListIdsInThread(nsIMsgThread *threadHdr, nsMsgViewIndex startOfThreadViewIndex, uint32_t *pNumListed) { if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay && ! (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)) { nsMsgKey parentKey = m_keys[startOfThreadViewIndex++]; return ListIdsInThreadOrder(threadHdr, parentKey, 1, &startOfThreadViewIndex, pNumListed); } uint32_t numChildren; threadHdr->GetNumChildren(&numChildren); uint32_t i; uint32_t viewIndex = startOfThreadViewIndex + 1; nsCOMPtr<nsIMsgDBHdr> rootHdr; nsMsgKey rootKey; uint32_t rootFlags = m_flags[startOfThreadViewIndex]; *pNumListed = 0; GetMsgHdrForViewIndex(startOfThreadViewIndex, getter_AddRefs(rootHdr)); rootHdr->GetMessageKey(&rootKey); // group threads can have the root key twice, one for the dummy row. bool rootKeySkipped = false; for (i = 0; i < numChildren; i++) { nsCOMPtr<nsIMsgDBHdr> msgHdr; threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr)); if (msgHdr != nullptr) { nsMsgKey msgKey; msgHdr->GetMessageKey(&msgKey); if (msgKey != rootKey || (GroupViewUsesDummyRow() && rootKeySkipped)) { nsMsgViewIndex threadRootIndex = m_origKeys.BinaryIndexOf(msgKey); // if this hdr is in the original view, add it to new view. if (threadRootIndex != nsMsgViewIndex_None) { uint32_t childFlags; msgHdr->GetFlags(&childFlags); InsertMsgHdrAt(viewIndex, msgHdr, msgKey, childFlags, FindLevelInThread(msgHdr, startOfThreadViewIndex, viewIndex)); if (! (rootFlags & MSG_VIEW_FLAG_HASCHILDREN)) m_flags[startOfThreadViewIndex] = rootFlags | MSG_VIEW_FLAG_HASCHILDREN; viewIndex++; (*pNumListed)++; } } else { rootKeySkipped = true; } } } return NS_OK; }
nsresult nsMsgSearchDBView::InsertHdrFromFolder(nsIMsgDBHdr *msgHdr, nsIMsgFolder *folder) { nsMsgViewIndex insertIndex = nsMsgViewIndex_None; // Threaded view always needs to go through AddHdrFromFolder since // it handles the xf view thread object creation. if (! (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) insertIndex = GetInsertIndex(msgHdr); if (insertIndex == nsMsgViewIndex_None) return AddHdrFromFolder(msgHdr, folder); nsMsgKey msgKey; uint32_t msgFlags; msgHdr->GetMessageKey(&msgKey); msgHdr->GetFlags(&msgFlags); InsertMsgHdrAt(insertIndex, msgHdr, msgKey, msgFlags, 0); // 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); return NS_OK; }
// 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 nsMsgSearchDBView::MoveThreadAt(nsMsgViewIndex threadIndex) { bool updatesSuppressed = mSuppressChangeNotification; // Turn off tree notifications so that we don't reload the current message. if (!updatesSuppressed) SetSuppressChangeNotifications(true); nsCOMPtr<nsIMsgDBHdr> threadHdr; GetMsgHdrForViewIndex(threadIndex, getter_AddRefs(threadHdr)); uint32_t saveFlags = m_flags[threadIndex]; bool threadIsExpanded = !(saveFlags & nsMsgMessageFlags::Elided); int32_t childCount = 0; nsMsgKey preservedKey; nsAutoTArray<nsMsgKey, 1> preservedSelection; int32_t selectionCount; int32_t currentIndex; bool hasSelection = mTree && mTreeSelection && ((NS_SUCCEEDED(mTreeSelection->GetCurrentIndex(¤tIndex)) && currentIndex >= 0 && (uint32_t)currentIndex < GetSize()) || (NS_SUCCEEDED(mTreeSelection->GetRangeCount(&selectionCount)) && selectionCount > 0)); if (hasSelection) SaveAndClearSelection(&preservedKey, preservedSelection); if (threadIsExpanded) { ExpansionDelta(threadIndex, &childCount); childCount = -childCount; } nsTArray<nsMsgKey> threadKeys; nsTArray<uint32_t> threadFlags; nsTArray<uint8_t> threadLevels; nsCOMArray<nsIMsgFolder> threadFolders; if (threadIsExpanded) { threadKeys.SetCapacity(childCount); threadFlags.SetCapacity(childCount); threadLevels.SetCapacity(childCount); threadFolders.SetCapacity(childCount); for (nsMsgViewIndex index = threadIndex + 1; index < (nsMsgViewIndex) GetSize() && m_levels[index]; index++) { threadKeys.AppendElement(m_keys[index]); threadFlags.AppendElement(m_flags[index]); threadLevels.AppendElement(m_levels[index]); threadFolders.AppendObject(m_folders[index]); } uint32_t collapseCount; CollapseByIndex(threadIndex, &collapseCount); } nsMsgDBView::RemoveByIndex(threadIndex); m_folders.RemoveObjectAt(threadIndex); nsMsgViewIndex newIndex = GetIndexForThread(threadHdr); NS_ASSERTION(newIndex == m_levels.Length() || !m_levels[newIndex], "inserting into middle of thread"); if (newIndex == nsMsgViewIndex_None) newIndex = 0; nsMsgKey msgKey; uint32_t msgFlags; threadHdr->GetMessageKey(&msgKey); threadHdr->GetFlags(&msgFlags); InsertMsgHdrAt(newIndex, threadHdr, msgKey, msgFlags, 0); if (threadIsExpanded) { m_keys.InsertElementsAt(newIndex + 1, threadKeys); m_flags.InsertElementsAt(newIndex + 1, threadFlags); m_levels.InsertElementsAt(newIndex + 1, threadLevels); m_folders.InsertObjectsAt(threadFolders, newIndex + 1); } m_flags[newIndex] = saveFlags; // unfreeze selection. if (hasSelection) RestoreSelection(preservedKey, preservedSelection); if (!updatesSuppressed) SetSuppressChangeNotifications(false); nsMsgViewIndex lowIndex = threadIndex < newIndex ? threadIndex : newIndex; nsMsgViewIndex highIndex = lowIndex == threadIndex ? newIndex : threadIndex; NoteChange(lowIndex, highIndex - lowIndex + childCount + 1, nsMsgViewNotificationCode::changed); }
nsresult nsMsgSearchDBView::AddHdrFromFolder(nsIMsgDBHdr *msgHdr, nsIMsgFolder *folder) { if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort) return nsMsgGroupView::OnNewHeader(msgHdr, nsMsgKey_None, true); nsMsgKey msgKey; uint32_t msgFlags; msgHdr->GetMessageKey(&msgKey); msgHdr->GetFlags(&msgFlags); if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) { nsCOMPtr<nsIMsgThread> thread; nsCOMPtr<nsIMsgDBHdr> threadRoot; // if we find an xf thread in the hash table corresponding to the new msg's // message id, a previous header must be a reference child of the new // message, which means we need to reparent later. bool msgIsReferredTo; GetXFThreadFromMsgHdr(msgHdr, getter_AddRefs(thread), &msgIsReferredTo); bool newThread = !thread; nsMsgXFViewThread *viewThread; if (!thread) { viewThread = new nsMsgXFViewThread(this, m_nextThreadId++); if (!viewThread) return NS_ERROR_OUT_OF_MEMORY; thread = do_QueryInterface(viewThread); } else { viewThread = static_cast<nsMsgXFViewThread*>(thread.get()); thread->GetChildHdrAt(0, getter_AddRefs(threadRoot)); } AddMsgToHashTables(msgHdr, thread); nsCOMPtr<nsIMsgDBHdr> parent; uint32_t posInThread; // We need to move threads in order to keep ourselves sorted // correctly. We want the index of the original thread...we can do this by // getting the root header before we add the new header, and finding that. if (newThread || !viewThread->MsgCount()) { viewThread->AddHdr(msgHdr, false, posInThread, getter_AddRefs(parent)); nsMsgViewIndex insertIndex = GetIndexForThread(msgHdr); NS_ASSERTION(insertIndex == m_levels.Length() || !m_levels[insertIndex], "inserting into middle of thread"); if (insertIndex == nsMsgViewIndex_None) return NS_ERROR_FAILURE; if (!(m_viewFlags & nsMsgViewFlagsType::kExpandAll)) msgFlags |= nsMsgMessageFlags::Elided; InsertMsgHdrAt(insertIndex, msgHdr, msgKey, msgFlags, 0); NoteChange(insertIndex, 1, nsMsgViewNotificationCode::insertOrDelete); } else { // get the thread root index before we add the header, because adding // the header can change the sort position. nsMsgViewIndex threadIndex = GetThreadRootIndex(threadRoot); viewThread->AddHdr(msgHdr, msgIsReferredTo, posInThread, getter_AddRefs(parent)); if (threadIndex == nsMsgViewIndex_None) { NS_ERROR("couldn't find thread index for newly inserted header"); return NS_OK; // not really OK, but not failure exactly. } NS_ASSERTION(!m_levels[threadIndex], "threadRoot incorrect, or level incorrect"); bool moveThread = false; if (m_sortType == nsMsgViewSortType::byDate) { uint32_t newestMsgInThread = 0, msgDate = 0; viewThread->GetNewestMsgDate(&newestMsgInThread); msgHdr->GetDateInSeconds(&msgDate); moveThread = (msgDate == newestMsgInThread); } OrExtraFlag(threadIndex, MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_ISTHREAD); if (!(m_flags[threadIndex] & nsMsgMessageFlags::Elided)) { if (parent) { // since we know posInThread, we just want to insert the new hdr // at threadIndex + posInThread, and then rebuild the view until we // get to a sibling of the new hdr. uint8_t newMsgLevel = viewThread->ChildLevelAt(posInThread); InsertMsgHdrAt(threadIndex + posInThread, msgHdr, msgKey, msgFlags, newMsgLevel); NoteChange(threadIndex + posInThread, 1, nsMsgViewNotificationCode::insertOrDelete); for (nsMsgViewIndex viewIndex = threadIndex + ++posInThread; posInThread < viewThread->MsgCount() && viewThread->ChildLevelAt(posInThread) > newMsgLevel; viewIndex++) { m_levels[viewIndex] = viewThread->ChildLevelAt(posInThread++); } } else // The new header is the root, so we need to adjust // all the children. { InsertMsgHdrAt(threadIndex, msgHdr, msgKey, msgFlags, 0); NoteChange(threadIndex, 1, nsMsgViewNotificationCode::insertOrDelete); nsMsgViewIndex i; for (i = threadIndex + 1; i < m_keys.Length() && (i == threadIndex + 1 || m_levels[i]); i++) m_levels[i] = m_levels[i] + 1; // turn off thread flags on old root. AndExtraFlag(threadIndex + 1, ~(MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided | MSG_VIEW_FLAG_HASCHILDREN)); NoteChange(threadIndex + 1, i - threadIndex + 1, nsMsgViewNotificationCode::changed); } } else if (!parent) { // new parent came into collapsed thread nsCOMPtr<nsIMsgFolder> msgFolder; msgHdr->GetFolder(getter_AddRefs(msgFolder)); m_keys[threadIndex] = msgKey; m_folders.ReplaceObjectAt(msgFolder, threadIndex); m_flags[threadIndex] = msgFlags | MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided | MSG_VIEW_FLAG_HASCHILDREN; NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed); } if (moveThread) MoveThreadAt(threadIndex); } } else { m_folders.AppendObject(folder); // nsMsgKey_None means it's not a valid hdr. if (msgKey != nsMsgKey_None) { msgHdr->GetFlags(&msgFlags); m_keys.AppendElement(msgKey); m_levels.AppendElement(0); m_flags.AppendElement(msgFlags); NoteChange(GetSize() - 1, 1, nsMsgViewNotificationCode::insertOrDelete); } } return NS_OK; }
NS_IMETHODIMP nsMsgSearchDBView::OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted, nsMsgKey aParentKey, int32_t aFlags, nsIDBChangeListener *aInstigator) { if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort) return nsMsgGroupView::OnHdrDeleted(aHdrDeleted, aParentKey, aFlags, aInstigator); if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) { nsMsgViewIndex deletedIndex = FindHdr(aHdrDeleted); uint32_t savedFlags = 0; if (deletedIndex != nsMsgViewIndex_None) { savedFlags = m_flags[deletedIndex]; RemoveByIndex(deletedIndex); } nsCOMPtr<nsIMsgThread> thread; GetXFThreadFromMsgHdr(aHdrDeleted, getter_AddRefs(thread)); if (thread) { nsMsgXFViewThread *viewThread = static_cast<nsMsgXFViewThread*>(thread.get()); viewThread->RemoveChildHdr(aHdrDeleted, nullptr); if (deletedIndex == nsMsgViewIndex_None && viewThread->MsgCount() == 1) { // remove the last child of a collapsed thread. Need to find the root, // and remove the thread flags on it. nsCOMPtr<nsIMsgDBHdr> rootHdr; thread->GetRootHdr(nullptr, getter_AddRefs(rootHdr)); if (rootHdr) { nsMsgViewIndex threadIndex = GetThreadRootIndex(rootHdr); if (threadIndex != nsMsgViewIndex_None) AndExtraFlag(threadIndex, ~(MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided | MSG_VIEW_FLAG_HASCHILDREN)); } } else if (savedFlags & MSG_VIEW_FLAG_HASCHILDREN) { if (savedFlags & nsMsgMessageFlags::Elided) { nsCOMPtr<nsIMsgDBHdr> rootHdr; nsresult rv = thread->GetRootHdr(nullptr, getter_AddRefs(rootHdr)); NS_ENSURE_SUCCESS(rv, rv); nsMsgKey msgKey; uint32_t msgFlags; rootHdr->GetMessageKey(&msgKey); rootHdr->GetFlags(&msgFlags); // promote the new thread root if (viewThread->MsgCount() > 1) msgFlags |= MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided | MSG_VIEW_FLAG_HASCHILDREN; InsertMsgHdrAt(deletedIndex, rootHdr, msgKey, msgFlags, 0); if (!m_deletingRows) NoteChange(deletedIndex, 1, nsMsgViewNotificationCode::insertOrDelete); } else if (viewThread->MsgCount() > 1) { OrExtraFlag(deletedIndex, MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN); } } } } else { return nsMsgDBView::OnHdrDeleted(aHdrDeleted, aParentKey, aFlags, aInstigator); } return NS_OK; }
nsresult nsMsgGroupView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, bool ensureListed) { if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort)) return nsMsgDBView::OnNewHeader(newHdr, aParentKey, ensureListed); // check if we're adding a header, and the current day has changed. If it has, we're just going to // close and re-open the view so things will be correctly categorized. if (m_dayChanged) return RebuildView(m_viewFlags); bool newThread; nsMsgGroupThread *thread = AddHdrToThread(newHdr, &newThread); if (thread) { // find the view index of (the root node of) the thread nsMsgViewIndex threadIndex = ThreadIndexOfMsgHdr(newHdr); // may need to fix thread counts if (threadIndex != nsMsgViewIndex_None) { if (newThread) { // AddHdrToThread creates the header elided, so we need to un-elide it // if we want it expanded. if(m_viewFlags & nsMsgViewFlagsType::kExpandAll) m_flags[threadIndex] &= ~nsMsgMessageFlags::Elided; } else { m_flags[threadIndex] |= MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_ISTHREAD; } int32_t numRowsToInvalidate = 1; // if the thread is expanded (not elided), we should add the header to // the view. if (! (m_flags[threadIndex] & nsMsgMessageFlags::Elided)) { uint32_t msgIndexInThread = thread->FindMsgHdr(newHdr); bool insertedAtThreadRoot = !msgIndexInThread; // Add any new display node and potentially fix-up changes in the root. // (If this is a new thread and we are not using a dummy row, the only // node to display is the root node which has already been added by // AddHdrToThread. And since there is just the one, no change in root // could have occurred, so we have nothing to do.) if (!newThread || GroupViewUsesDummyRow()) { // we never want to insert/update the root node, because // AddHdrToThread has already done that for us (in all cases). if (insertedAtThreadRoot) msgIndexInThread++; // If this header is the new parent of the thread... AND // If we are not using a dummy row, this means we need to append our // old node as the first child of the new root. // (If we are using a dummy row, the old node's "content" node already // exists (at position threadIndex + 1) and we need to insert the // "content" copy of the new root node there, pushing our old // "content" node down.) // Example mini-diagrams, wrapping the to-add thing with () // No dummy row; we had: [A], now we have [B], we want [B (A)]. // Dummy row; we had: [A A], now we have [B A], we want [B (B) A]. // (Coming into this we're adding 'B') if (!newThread && insertedAtThreadRoot && !GroupViewUsesDummyRow()) { // grab a copy of the old root node ('A') from the thread so we can // insert it. (offset msgIndexInThread=1 is the right thing; we are // non-dummy.) thread->GetChildHdrAt(msgIndexInThread, &newHdr); } // nothing to do for dummy case, we're already inserting 'B'. nsMsgKey msgKey; uint32_t msgFlags; newHdr->GetMessageKey(&msgKey); newHdr->GetFlags(&msgFlags); InsertMsgHdrAt(threadIndex + msgIndexInThread, newHdr, msgKey, msgFlags, 1); } // the call to NoteChange() has to happen after we add the key // as NoteChange() will call RowCountChanged() which will call our GetRowCount() // (msgIndexInThread states. new thread: 0, old thread at root: 1) if (newThread && GroupViewUsesDummyRow()) NoteChange(threadIndex, 2, nsMsgViewNotificationCode::insertOrDelete); else NoteChange(threadIndex + msgIndexInThread, 1, nsMsgViewNotificationCode::insertOrDelete); numRowsToInvalidate = msgIndexInThread; } // we still need the addition notification for new threads when elided else if (newThread) { NoteChange(threadIndex, 1, nsMsgViewNotificationCode::insertOrDelete); } NoteChange(threadIndex, numRowsToInvalidate, nsMsgViewNotificationCode::changed); } } // if thread is expanded, we need to add hdr to view... return NS_OK; }
nsMsgGroupThread *nsMsgGroupView::AddHdrToThread(nsIMsgDBHdr *msgHdr, bool *pNewThread) { nsMsgKey msgKey; uint32_t msgFlags; msgHdr->GetMessageKey(&msgKey); msgHdr->GetFlags(&msgFlags); nsString hashKey; nsresult rv = HashHdr(msgHdr, hashKey); if (NS_FAILED(rv)) return nullptr; // if (m_sortType == nsMsgViewSortType::byDate) // msgKey = ((nsPRUint32Key *) hashKey)->GetValue(); nsCOMPtr<nsIMsgThread> msgThread; m_groupsTable.Get(hashKey, getter_AddRefs(msgThread)); bool newThread = !msgThread; *pNewThread = newThread; nsMsgViewIndex viewIndexOfThread; // index of first message in thread in view nsMsgViewIndex threadInsertIndex; // index of newly added header in thread nsMsgGroupThread *foundThread = static_cast<nsMsgGroupThread *>(msgThread.get()); if (foundThread) { // find the view index of the root node of the thread in the view viewIndexOfThread = GetIndexOfFirstDisplayedKeyInThread(foundThread, true); if (viewIndexOfThread == nsMsgViewIndex_None) { // Something is wrong with the group table. Remove the old group and // insert a new one. m_groupsTable.Remove(hashKey); foundThread = nullptr; *pNewThread = newThread = true; } } // If the thread does not already exist, create one if (!foundThread) { foundThread = CreateGroupThread(m_db); msgThread = do_QueryInterface(foundThread); m_groupsTable.Put(hashKey, msgThread); if (GroupViewUsesDummyRow()) { foundThread->m_dummy = true; msgFlags |= MSG_VIEW_FLAG_DUMMY | MSG_VIEW_FLAG_HASCHILDREN; } viewIndexOfThread = GetInsertIndex(msgHdr); if (viewIndexOfThread == nsMsgViewIndex_None) viewIndexOfThread = m_keys.Length(); // add the thread root node to the view InsertMsgHdrAt(viewIndexOfThread, msgHdr, msgKey, msgFlags | MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided, 0); // For dummy rows, Have the header serve as the dummy node (it will be added // again for its actual content later.) if (GroupViewUsesDummyRow()) foundThread->InsertMsgHdrAt(0, msgHdr); // Calculate the (integer thread key); this really only needs to be done for // the byDate case where the expanded state of the groups can be easily // persisted and restored because of the bounded, consecutive value space // occupied. We calculate an integer value in all cases mainly because // it's the sanest choice available... // (The thread key needs to be an integer, so parse hash keys that are // stringified integers to real integers, and hash actual strings into // integers.) if ((m_sortType == nsMsgViewSortType::byAttachments) || (m_sortType == nsMsgViewSortType::byFlagged) || (m_sortType == nsMsgViewSortType::byPriority) || (m_sortType == nsMsgViewSortType::byStatus) || (m_sortType == nsMsgViewSortType::byReceived) || (m_sortType == nsMsgViewSortType::byDate)) foundThread->m_threadKey = atoi(NS_LossyConvertUTF16toASCII(hashKey).get()); else foundThread->m_threadKey = (nsMsgKey) PL_HashString(NS_LossyConvertUTF16toASCII(hashKey).get()); } // Add the message to the thread as an actual content-bearing header. // (If we use dummy rows, it was already added to the thread during creation.) threadInsertIndex = foundThread->AddChildFromGroupView(msgHdr, this); // check if new hdr became thread root if (!newThread && threadInsertIndex == 0) { // update the root node's header (in the view) to be the same as the root // node in the thread. SetMsgHdrAt(msgHdr, viewIndexOfThread, msgKey, (msgFlags & ~(nsMsgMessageFlags::Elided)) | // maintain elided flag and dummy flag (m_flags[viewIndexOfThread] & (nsMsgMessageFlags::Elided | MSG_VIEW_FLAG_DUMMY)) // ensure thread and has-children flags are set | MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN, 0); // update the content-bearing copy in the thread to match. (the root and // first nodes in the thread should always be the same header.) // note: the guy who used to be the root will still exist. If our list of // nodes was [A A], a new node B is introduced which sorts to be the first // node, giving us [B A A], our copy makes that [B B A], and things are // right in the world (since we want the first two headers to be the same // since one is our dummy and one is real.) if (GroupViewUsesDummyRow()) foundThread->SetMsgHdrAt(1, msgHdr); // replace the old duplicate dummy header. // we do not update the content-bearing copy in the view to match; we leave // that up to OnNewHeader, which is the piece of code who gets to care // about whether the thread's children are shown or not (elided) } return foundThread; }
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... PRUint32 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. PRInt32 threadCount; PRUint32 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) { PRUint32 newestMsgInThread = 0, msgDate = 0; threadHdr->GetNewestMsgDate(&newestMsgInThread); newHdr->GetDateInSeconds(&msgDate); moveThread = (msgDate == newestMsgInThread); } if (threadIndex != nsMsgViewIndex_None) { threadRootIsDisplayed = (m_currentlyDisplayedViewIndex == threadIndex); PRUint32 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! PRUint32 newFlags = msgFlags; PRInt32 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, nsnull); // call base class, so child won't get promoted. // nsMsgDBView::RemoveByIndex(threadIndex); ExpandByIndex(threadIndex, nsnull); } } 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, nsnull); 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; }