// 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)); PRInt32 childCount = 0; nsMsgKey preservedKey; nsAutoTArray<nsMsgKey, 1> preservedSelection; PRInt32 selectionCount; PRInt32 currentIndex; bool hasSelection = mTree && mTreeSelection && ((NS_SUCCEEDED(mTreeSelection->GetCurrentIndex(¤tIndex)) && currentIndex >= 0 && (PRUint32)currentIndex < GetSize()) || (NS_SUCCEEDED(mTreeSelection->GetRangeCount(&selectionCount)) && selectionCount > 0)); if (hasSelection) SaveAndClearSelection(&preservedKey, preservedSelection); PRUint32 saveFlags = m_flags[threadIndex]; bool threadIsExpanded = !(saveFlags & nsMsgMessageFlags::Elided); if (threadIsExpanded) { ExpansionDelta(threadIndex, &childCount); childCount = -childCount; } nsTArray<nsMsgKey> threadKeys; nsTArray<PRUint32> threadFlags; nsTArray<PRUint8> 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]); } PRUint32 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); }
// 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 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; }