NS_IMETHODIMP nsMsgGroupView::OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted, nsMsgKey aParentKey, int32_t aFlags, nsIDBChangeListener *aInstigator) { if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort)) return nsMsgDBView::OnHdrDeleted(aHdrDeleted, aParentKey, aFlags, aInstigator); // 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); nsCOMPtr <nsIMsgThread> thread; nsMsgKey keyDeleted; aHdrDeleted->GetMessageKey(&keyDeleted); nsresult rv = GetThreadContainingMsgHdr(aHdrDeleted, getter_AddRefs(thread)); NS_ENSURE_SUCCESS(rv, rv); nsMsgViewIndex viewIndexOfThread = GetIndexOfFirstDisplayedKeyInThread( thread, true); // yes to dummy node thread->RemoveChildHdr(aHdrDeleted, nullptr); nsMsgGroupThread *groupThread = static_cast<nsMsgGroupThread *>((nsIMsgThread *) thread); bool rootDeleted = viewIndexOfThread != nsMsgKey_None && m_keys[viewIndexOfThread] == keyDeleted; rv = nsMsgDBView::OnHdrDeleted(aHdrDeleted, aParentKey, aFlags, aInstigator); if (groupThread->m_dummy) { if (!groupThread->NumRealChildren()) { thread->RemoveChildAt(0); // get rid of dummy if (viewIndexOfThread != nsMsgKey_None) { RemoveByIndex(viewIndexOfThread); if (m_deletingRows) mIndicesToNoteChange.AppendElement(viewIndexOfThread); } } else if (rootDeleted) { // reflect new thread root into view.dummy row. nsCOMPtr<nsIMsgDBHdr> hdr; thread->GetChildHdrAt(0, getter_AddRefs(hdr)); if (hdr) { nsMsgKey msgKey; hdr->GetMessageKey(&msgKey); SetMsgHdrAt(hdr, viewIndexOfThread, msgKey, m_flags[viewIndexOfThread], 0); } } } if (!groupThread->m_keys.Length()) { nsString hashKey; rv = HashHdr(aHdrDeleted, hashKey); if (NS_SUCCEEDED(rv)) m_groupsTable.Remove(hashKey); } return rv; }
NS_IMETHODIMP nsMsgGroupThread::RemoveChildHdr(nsIMsgDBHdr *child, nsIDBChangeAnnouncer *announcer) { uint32_t flags; nsMsgKey key; if (!child) return NS_ERROR_NULL_POINTER; child->GetFlags(&flags); child->GetMessageKey(&key); // if this was the newest msg, clear the newest msg date so we'll recalc. uint32_t date; child->GetDateInSeconds(&date); if (date == m_newestMsgDate) SetNewestMsgDate(0); if (!(flags & nsMsgMessageFlags::Read)) ChangeUnreadChildCount(-1); nsMsgViewIndex threadIndex = FindMsgHdr(child); bool wasFirstChild = threadIndex == 0; nsresult rv = RemoveChildAt(threadIndex); // if we're deleting the root of a dummy thread, need to update the threadKey // and the dummy header at position 0 if (m_dummy && wasFirstChild && m_keys.Length() > 1) { nsIMsgDBHdr *newRootChild; GetChildHdrAt(1, &newRootChild); SetMsgHdrAt(0, newRootChild); } return rv; }
nsresult nsMsgSearchDBView::ListIdsInThread(nsIMsgThread *threadHdr, nsMsgViewIndex startOfThreadViewIndex, uint32_t *pNumListed) { NS_ENSURE_ARG_POINTER(threadHdr); NS_ENSURE_ARG_POINTER(pNumListed); // these children ids should be in thread order. uint32_t i; nsMsgViewIndex viewIndex = startOfThreadViewIndex + 1; *pNumListed = 0; uint32_t numChildren; threadHdr->GetNumChildren(&numChildren); NS_ASSERTION(numChildren, "Empty thread in view/db"); if (!numChildren) return NS_OK; numChildren--; // account for the existing thread root if (!InsertEmptyRows(viewIndex, numChildren)) return NS_ERROR_OUT_OF_MEMORY; bool threadedView = m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay && !(m_viewFlags & nsMsgViewFlagsType::kGroupBySort); nsMsgXFViewThread *viewThread; if (threadedView) viewThread = static_cast<nsMsgXFViewThread*>(threadHdr); for (i = 1; i <= numChildren; i++) { nsCOMPtr<nsIMsgDBHdr> msgHdr; threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr)); if (msgHdr) { nsMsgKey msgKey; uint32_t msgFlags; msgHdr->GetMessageKey(&msgKey); msgHdr->GetFlags(&msgFlags); uint8_t level = (threadedView) ? viewThread->ChildLevelAt(i) : 1; SetMsgHdrAt(msgHdr, viewIndex, msgKey, msgFlags & ~MSG_VIEW_FLAGS, level); (*pNumListed)++; viewIndex++; } } 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; }