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; }
nsMsgViewIndex nsMsgGroupView::ThreadIndexOfMsg(nsMsgKey msgKey, nsMsgViewIndex msgIndex /* = nsMsgViewIndex_None */, int32_t *pThreadCount /* = NULL */, uint32_t *pFlags /* = NULL */) { if (msgIndex != nsMsgViewIndex_None && GroupViewUsesDummyRow()) { // this case is all we care about at this point. if (m_flags[msgIndex] & MSG_VIEW_FLAG_ISTHREAD) return msgIndex; } return nsMsgDBView::ThreadIndexOfMsg(msgKey, msgIndex, pThreadCount, pFlags); }
nsresult nsMsgQuickSearchDBView::ExpansionDelta(nsMsgViewIndex index, int32_t *expansionDelta) { *expansionDelta = 0; if (index >= ((nsMsgViewIndex) m_keys.Length())) return NS_MSG_MESSAGE_NOT_FOUND; char flags = m_flags[index]; if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) return NS_OK; nsCOMPtr<nsIMsgThread> threadHdr; nsresult rv = GetThreadContainingIndex(index, getter_AddRefs(threadHdr)); NS_ENSURE_SUCCESS(rv, rv); uint32_t numChildren; threadHdr->GetNumChildren(&numChildren); nsCOMPtr<nsIMsgDBHdr> rootHdr; nsMsgKey rootKey; GetMsgHdrForViewIndex(index, getter_AddRefs(rootHdr)); rootHdr->GetMessageKey(&rootKey); // group threads can have the root key twice, one for the dummy row. bool rootKeySkipped = false; for (uint32_t i = 0; i < numChildren; i++) { nsCOMPtr<nsIMsgDBHdr> msgHdr; threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr)); if (msgHdr) { nsMsgKey msgKey; msgHdr->GetMessageKey(&msgKey); if (msgKey != rootKey || (GroupViewUsesDummyRow() && rootKeySkipped)) { // if this hdr is in the original view, add it to new view. if (m_origKeys.BinaryIndexOf(msgKey) != m_origKeys.NoIndex) (*expansionDelta)++; } else { rootKeySkipped = true; } } } if (! (flags & nsMsgMessageFlags::Elided)) *expansionDelta = - (*expansionDelta); return NS_OK; }
nsresult nsMsgQuickSearchDBView::ListCollapsedChildren(nsMsgViewIndex viewIndex, nsIMutableArray *messageArray) { nsCOMPtr<nsIMsgThread> threadHdr; nsresult rv = GetThreadContainingIndex(viewIndex, getter_AddRefs(threadHdr)); NS_ENSURE_SUCCESS(rv, rv); uint32_t numChildren; threadHdr->GetNumChildren(&numChildren); nsCOMPtr<nsIMsgDBHdr> rootHdr; nsMsgKey rootKey; GetMsgHdrForViewIndex(viewIndex, getter_AddRefs(rootHdr)); rootHdr->GetMessageKey(&rootKey); // group threads can have the root key twice, one for the dummy row. bool rootKeySkipped = false; for (uint32_t i = 0; i < numChildren; i++) { nsCOMPtr<nsIMsgDBHdr> msgHdr; threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr)); if (msgHdr) { nsMsgKey msgKey; msgHdr->GetMessageKey(&msgKey); if (msgKey != rootKey || (GroupViewUsesDummyRow() && rootKeySkipped)) { // if this hdr is in the original view, add it to new view. if (m_origKeys.BinaryIndexOf(msgKey) != m_origKeys.NoIndex) messageArray->AppendElement(msgHdr, false); } else { rootKeySkipped = true; } } } 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; }