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 nsMsgGroupView::GetThreadContainingMsgHdr(nsIMsgDBHdr *msgHdr, nsIMsgThread **pThread) { if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort)) return nsMsgDBView::GetThreadContainingMsgHdr(msgHdr, pThread); nsString hashKey; nsresult rv = HashHdr(msgHdr, hashKey); *pThread = nullptr; if (NS_SUCCEEDED(rv)) { nsCOMPtr<nsIMsgThread> thread; m_groupsTable.Get(hashKey, getter_AddRefs(thread)); thread.swap(*pThread); } return (*pThread) ? NS_OK : NS_ERROR_FAILURE; }
NS_IMETHODIMP nsMsgGroupView::GetCellProperties(int32_t aRow, nsITreeColumn *aCol, nsAString& aProperties) { if (!IsValidIndex(aRow)) return NS_MSG_INVALID_DBVIEW_INDEX; if (m_flags[aRow] & MSG_VIEW_FLAG_DUMMY) { aProperties.AssignLiteral("dummy read"); if (!(m_flags[aRow] & nsMsgMessageFlags::Elided)) return NS_OK; // Set unread property if a collapsed group thread has unread. nsCOMPtr <nsIMsgDBHdr> msgHdr; nsresult rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr)); NS_ENSURE_SUCCESS(rv, rv); nsString hashKey; rv = HashHdr(msgHdr, hashKey); if (NS_FAILED(rv)) return NS_OK; nsCOMPtr<nsIMsgThread> msgThread; m_groupsTable.Get(hashKey, getter_AddRefs(msgThread)); nsMsgGroupThread *groupThread = static_cast<nsMsgGroupThread *>(msgThread.get()); if (!groupThread) return NS_OK; uint32_t numUnrMsg = 0; groupThread->GetNumUnreadChildren(&numUnrMsg); if (numUnrMsg > 0) aProperties.AppendLiteral(" hasUnread"); return NS_OK; } return nsMsgDBView::GetCellProperties(aRow, aCol, aProperties); }
NS_IMETHODIMP nsMsgGroupView::CellTextForColumn(int32_t aRow, const char16_t *aColumnName, nsAString &aValue) { if (!IsValidIndex(aRow)) return NS_MSG_INVALID_DBVIEW_INDEX; if (m_flags[aRow] & MSG_VIEW_FLAG_DUMMY && aColumnName[0] != 'u') { nsCOMPtr <nsIMsgDBHdr> msgHdr; nsresult rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr)); NS_ENSURE_SUCCESS(rv, rv); nsString hashKey; rv = HashHdr(msgHdr, hashKey); if (NS_FAILED(rv)) return NS_OK; nsCOMPtr<nsIMsgThread> msgThread; m_groupsTable.Get(hashKey, getter_AddRefs(msgThread)); nsMsgGroupThread * groupThread = static_cast<nsMsgGroupThread *>(msgThread.get()); if (aColumnName[0] == 's' && aColumnName[1] == 'u' ) { uint32_t flags; bool rcvDate = false; msgHdr->GetFlags(&flags); aValue.Truncate(); nsString tmp_str; switch (m_sortType) { case nsMsgViewSortType::byReceived: rcvDate = true; case nsMsgViewSortType::byDate: { uint32_t ageBucket = 0; GetAgeBucketValue(msgHdr, &ageBucket, rcvDate); switch (ageBucket) { case 1: if (m_kTodayString.IsEmpty()) m_kTodayString.Adopt(GetString(MOZ_UTF16("today"))); aValue.Assign(m_kTodayString); break; case 2: if (m_kYesterdayString.IsEmpty()) m_kYesterdayString.Adopt(GetString(MOZ_UTF16("yesterday"))); aValue.Assign(m_kYesterdayString); break; case 3: if (m_kLastWeekString.IsEmpty()) m_kLastWeekString.Adopt(GetString(MOZ_UTF16("lastWeek"))); aValue.Assign(m_kLastWeekString); break; case 4: if (m_kTwoWeeksAgoString.IsEmpty()) m_kTwoWeeksAgoString.Adopt(GetString(MOZ_UTF16("twoWeeksAgo"))); aValue.Assign(m_kTwoWeeksAgoString); break; case 5: if (m_kOldMailString.IsEmpty()) m_kOldMailString.Adopt(GetString(MOZ_UTF16("older"))); aValue.Assign(m_kOldMailString); break; default: NS_ASSERTION(false, "bad age thread"); break; } break; } case nsMsgViewSortType::bySubject: FetchSubject(msgHdr, m_flags[aRow], aValue); break; case nsMsgViewSortType::byAuthor: FetchAuthor(msgHdr, aValue); break; case nsMsgViewSortType::byStatus: rv = FetchStatus(m_flags[aRow], aValue); if (aValue.IsEmpty()) { tmp_str.Adopt(GetString(MOZ_UTF16("messagesWithNoStatus"))); aValue.Assign(tmp_str); } break; case nsMsgViewSortType::byTags: rv = FetchTags(msgHdr, aValue); if (aValue.IsEmpty()) { tmp_str.Adopt(GetString(MOZ_UTF16("untaggedMessages"))); aValue.Assign(tmp_str); } break; case nsMsgViewSortType::byPriority: FetchPriority(msgHdr, aValue); if (aValue.IsEmpty()) { tmp_str.Adopt(GetString(MOZ_UTF16("noPriority"))); aValue.Assign(tmp_str); } break; case nsMsgViewSortType::byAccount: FetchAccount(msgHdr, aValue); break; case nsMsgViewSortType::byRecipient: FetchRecipients(msgHdr, aValue); break; case nsMsgViewSortType::byAttachments: tmp_str.Adopt(GetString(flags & nsMsgMessageFlags::Attachment ? MOZ_UTF16("attachments") : MOZ_UTF16("noAttachments"))); aValue.Assign(tmp_str); break; case nsMsgViewSortType::byFlagged: tmp_str.Adopt(GetString(flags & nsMsgMessageFlags::Marked ? MOZ_UTF16("groupFlagged") : MOZ_UTF16("notFlagged"))); aValue.Assign(tmp_str); break; // byLocation is a special case; we don't want to have duplicate // all this logic in nsMsgSearchDBView, and its hash key is what we // want anyways, so just copy it across. case nsMsgViewSortType::byLocation: case nsMsgViewSortType::byCorrespondent: aValue = hashKey; break; case nsMsgViewSortType::byCustom: { nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandlerFromDBInfo(); if (colHandler) { rv = colHandler->GetSortStringForRow(msgHdr.get(), aValue); break; } } default: NS_ASSERTION(false, "we don't sort by group for this type"); break; } if (groupThread) { // Get number of messages in group nsAutoString formattedCountMsg; uint32_t numMsg = groupThread->NumRealChildren(); formattedCountMsg.AppendInt(numMsg); // Get number of unread messages nsAutoString formattedCountUnrMsg; uint32_t numUnrMsg = 0; groupThread->GetNumUnreadChildren(&numUnrMsg); formattedCountUnrMsg.AppendInt(numUnrMsg); // Add text to header aValue.Append(NS_LITERAL_STRING(" (")); if (numUnrMsg) { aValue.Append(formattedCountUnrMsg); aValue.Append(NS_LITERAL_STRING("/")); } aValue.Append(formattedCountMsg); aValue.Append(NS_LITERAL_STRING(")")); } } else if (aColumnName[0] == 't' && aColumnName[1] == 'o') { nsAutoString formattedCountString; uint32_t numChildren = (groupThread) ? groupThread->NumRealChildren() : 0; formattedCountString.AppendInt(numChildren); aValue.Assign(formattedCountString); } return NS_OK; } return nsMsgDBView::CellTextForColumn(aRow, aColumnName, aValue); }
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; }