/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "msgCore.h" #include "nsMsgSearchDBView.h" #include "nsIMsgHdr.h" #include "nsIMsgThread.h" #include "nsQuickSort.h" #include "nsIDBFolderInfo.h" #include "nsIMsgCopyService.h" #include "nsMsgUtils.h" #include "nsTreeColumns.h" #include "nsIMsgMessageService.h" #include "nsMsgGroupThread.h" #include "nsIPrefService.h" #include "nsIPrefBranch.h" #include "nsMsgMessageFlags.h" #include "nsIMsgSearchSession.h" #include "nsComponentManagerUtils.h" #include "nsServiceManagerUtils.h" static bool gReferenceOnlyThreading; nsMsgSearchDBView::nsMsgSearchDBView() { // Don't try to display messages for the search pane. mSuppressMsgDisplay = true; m_totalMessagesInView = 0; m_nextThreadId = 1; mCurIndex = 0; mTotalIndices = 0; mCommand = -1; } nsMsgSearchDBView::~nsMsgSearchDBView() {} NS_IMPL_ISUPPORTS_INHERITED(nsMsgSearchDBView, nsMsgDBView, nsIMsgDBView, nsIMsgCopyServiceListener, nsIMsgSearchNotify) NS_IMETHODIMP nsMsgSearchDBView::Open(nsIMsgFolder* folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, nsMsgViewFlagsTypeValue viewFlags, int32_t* pCount) { // DBViewWrapper.jsm likes to create search views with a sort order // of byNone, in order to have the order be the order the search results // are returned. But this doesn't work with threaded view, so make the // sort order be byDate if we're threaded. if (viewFlags & nsMsgViewFlagsType::kThreadedDisplay && sortType == nsMsgViewSortType::byNone) sortType = nsMsgViewSortType::byDate; nsresult rv = nsMsgDBView::Open(folder, sortType, sortOrder, viewFlags, pCount); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr prefBranch( do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); NS_ENSURE_SUCCESS(rv, rv); prefBranch->GetBoolPref("mail.strict_threading", &gReferenceOnlyThreading); // Our sort is automatically valid because we have no contents at this point! m_sortValid = true; if (pCount) *pCount = 0; m_folder = nullptr; return rv; } NS_IMETHODIMP nsMsgSearchDBView::CloneDBView(nsIMessenger* aMessengerInstance, nsIMsgWindow* aMsgWindow, nsIMsgDBViewCommandUpdater* aCmdUpdater, nsIMsgDBView** _retval) { nsMsgSearchDBView* newMsgDBView = new nsMsgSearchDBView(); nsresult rv = CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater); NS_ENSURE_SUCCESS(rv, rv); NS_IF_ADDREF(*_retval = newMsgDBView); return NS_OK; } NS_IMETHODIMP nsMsgSearchDBView::CopyDBView(nsMsgDBView* aNewMsgDBView, nsIMessenger* aMessengerInstance, nsIMsgWindow* aMsgWindow, nsIMsgDBViewCommandUpdater* aCmdUpdater) { nsMsgGroupView::CopyDBView(aNewMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater); nsMsgSearchDBView* newMsgDBView = (nsMsgSearchDBView*)aNewMsgDBView; // Now copy all of our private member data. newMsgDBView->mDestFolder = mDestFolder; newMsgDBView->mCommand = mCommand; newMsgDBView->mTotalIndices = mTotalIndices; newMsgDBView->mCurIndex = mCurIndex; newMsgDBView->m_folders.InsertObjectsAt(m_folders, 0); newMsgDBView->m_curCustomColumn = m_curCustomColumn; for (auto const& hdrs : m_hdrsForEachFolder) { newMsgDBView->m_hdrsForEachFolder.AppendElement(hdrs.Clone()); } newMsgDBView->m_uniqueFoldersSelected.InsertObjectsAt(m_uniqueFoldersSelected, 0); int32_t count = m_dbToUseList.Count(); for (int32_t i = 0; i < count; i++) { newMsgDBView->m_dbToUseList.AppendObject(m_dbToUseList[i]); // Register the new view with the database so it gets notifications. m_dbToUseList[i]->AddListener(newMsgDBView); } if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) { // We need to clone the thread and msg hdr hash tables. for (auto iter = m_threadsTable.Iter(); !iter.Done(); iter.Next()) { newMsgDBView->m_threadsTable.InsertOrUpdate(iter.Key(), iter.UserData()); } for (auto iter = m_hdrsTable.Iter(); !iter.Done(); iter.Next()) { newMsgDBView->m_hdrsTable.InsertOrUpdate(iter.Key(), iter.UserData()); } } return NS_OK; } NS_IMETHODIMP nsMsgSearchDBView::Close() { int32_t count = m_dbToUseList.Count(); for (int32_t i = 0; i < count; i++) m_dbToUseList[i]->RemoveListener(this); m_dbToUseList.Clear(); return nsMsgGroupView::Close(); } void nsMsgSearchDBView::InternalClose() { m_threadsTable.Clear(); m_hdrsTable.Clear(); nsMsgGroupView::InternalClose(); m_folders.Clear(); } NS_IMETHODIMP nsMsgSearchDBView::GetCellText(int32_t aRow, nsTreeColumn* aCol, nsAString& aValue) { NS_ENSURE_TRUE(IsValidIndex(aRow), NS_MSG_INVALID_DBVIEW_INDEX); NS_ENSURE_ARG_POINTER(aCol); const nsAString& colID = aCol->GetId(); // The only thing we contribute is location; dummy rows have no location, so // bail in that case. Otherwise, check if we are dealing with 'location'. // 'location', need to check for "lo" not just "l" to avoid "label" column. if (!(m_flags[aRow] & MSG_VIEW_FLAG_DUMMY) && colID.Length() >= 2 && colID.First() == 'l' && colID.CharAt(1) == 'o') return FetchLocation(aRow, aValue); else return nsMsgGroupView::GetCellText(aRow, aCol, aValue); } nsresult nsMsgSearchDBView::HashHdr(nsIMsgDBHdr* msgHdr, nsString& aHashKey) { if (m_sortType == nsMsgViewSortType::byLocation) { aHashKey.Truncate(); nsCOMPtr folder; msgHdr->GetFolder(getter_AddRefs(folder)); return folder->GetPrettyName(aHashKey); } return nsMsgGroupView::HashHdr(msgHdr, aHashKey); } nsresult nsMsgSearchDBView::FetchLocation(int32_t aRow, nsAString& aLocationString) { nsCOMPtr folder; nsresult rv = GetFolderForViewIndex(aRow, getter_AddRefs(folder)); NS_ENSURE_SUCCESS(rv, rv); return folder->GetPrettyName(aLocationString); } nsresult nsMsgSearchDBView::OnNewHeader(nsIMsgDBHdr* newHdr, nsMsgKey aParentKey, bool /*ensureListed*/) { 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) { // Check if this message is currently selected. If it is, tell the front // end to be prepared for a delete. nsCOMPtr commandUpdater( do_QueryReferent(mCommandUpdater)); bool isMsgSelected = false; if (mTreeSelection && commandUpdater) { mTreeSelection->IsSelected(deletedIndex, &isMsgSelected); if (isMsgSelected) commandUpdater->UpdateNextMessageAfterDelete(); } savedFlags = m_flags[deletedIndex]; RemoveByIndex(deletedIndex); if (isMsgSelected) { // Now tell the front end that the delete happened. commandUpdater->SelectedMessageRemoved(); } } nsCOMPtr thread; GetXFThreadFromMsgHdr(aHdrDeleted, getter_AddRefs(thread)); if (thread) { nsMsgXFViewThread* viewThread = static_cast(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 rootHdr; thread->GetRootHdr(getter_AddRefs(rootHdr)); if (rootHdr) { nsMsgViewIndex threadIndex = GetThreadRootIndex(rootHdr); if (IsValidIndex(threadIndex)) AndExtraFlag(threadIndex, ~(MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided | MSG_VIEW_FLAG_HASCHILDREN)); } } else if (savedFlags & MSG_VIEW_FLAG_HASCHILDREN) { if (savedFlags & nsMsgMessageFlags::Elided) { nsCOMPtr rootHdr; nsresult rv = thread->GetRootHdr(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; } NS_IMETHODIMP nsMsgSearchDBView::OnHdrFlagsChanged(nsIMsgDBHdr* aHdrChanged, uint32_t aOldFlags, uint32_t aNewFlags, nsIDBChangeListener* aInstigator) { // Defer to base class if we're grouped or not threaded at all. if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort || !(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) { return nsMsgGroupView::OnHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags, aInstigator); } nsCOMPtr thread; bool foundMessageId; // Check if the hdr that changed is in a xf thread, and if the read flag // changed, update the thread unread count. GetXFThreadFromMsgHdr returns // the thread the header does or would belong to, so we need to also // check that the header is actually in the thread. GetXFThreadFromMsgHdr(aHdrChanged, getter_AddRefs(thread), &foundMessageId); if (foundMessageId) { nsMsgXFViewThread* viewThread = static_cast(thread.get()); if (viewThread->HdrIndex(aHdrChanged) != -1) { uint32_t deltaFlags = (aOldFlags ^ aNewFlags); if (deltaFlags & nsMsgMessageFlags::Read) thread->MarkChildRead(aNewFlags & nsMsgMessageFlags::Read); } } return nsMsgDBView::OnHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags, aInstigator); } void nsMsgSearchDBView::InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr* hdr, nsMsgKey msgKey, uint32_t flags, uint32_t level) { if ((int32_t)index < 0) { NS_ERROR("invalid insert index"); index = 0; level = 0; } else if (index > m_keys.Length()) { NS_ERROR("inserting past end of array"); index = m_keys.Length(); } m_keys.InsertElementAt(index, msgKey); m_flags.InsertElementAt(index, flags); m_levels.InsertElementAt(index, level); nsCOMPtr folder; hdr->GetFolder(getter_AddRefs(folder)); m_folders.InsertObjectAt(folder, index); } void nsMsgSearchDBView::SetMsgHdrAt(nsIMsgDBHdr* hdr, nsMsgViewIndex index, nsMsgKey msgKey, uint32_t flags, uint32_t level) { m_keys[index] = msgKey; m_flags[index] = flags; m_levels[index] = level; nsCOMPtr folder; hdr->GetFolder(getter_AddRefs(folder)); m_folders.ReplaceObjectAt(folder, index); } void nsMsgSearchDBView::InsertEmptyRows(nsMsgViewIndex viewIndex, int32_t numRows) { for (int32_t i = 0; i < numRows; i++) { m_folders.InsertObjectAt(nullptr, viewIndex + i); } return nsMsgDBView::InsertEmptyRows(viewIndex, numRows); } void nsMsgSearchDBView::RemoveRows(nsMsgViewIndex viewIndex, int32_t numRows) { nsMsgDBView::RemoveRows(viewIndex, numRows); for (int32_t i = 0; i < numRows; i++) m_folders.RemoveObjectAt(viewIndex); } nsresult nsMsgSearchDBView::GetMsgHdrForViewIndex(nsMsgViewIndex index, nsIMsgDBHdr** msgHdr) { if (index == nsMsgViewIndex_None || index >= (uint32_t)m_folders.Count()) { return NS_MSG_INVALID_DBVIEW_INDEX; } nsIMsgFolder* folder = m_folders[index]; if (folder) { nsCOMPtr db; nsresult rv = folder->GetMsgDatabase(getter_AddRefs(db)); NS_ENSURE_SUCCESS(rv, rv); if (db) { return db->GetMsgHdrForKey(m_keys[index], msgHdr); } } return NS_ERROR_FAILURE; } NS_IMETHODIMP nsMsgSearchDBView::GetFolderForViewIndex(nsMsgViewIndex index, nsIMsgFolder** aFolder) { NS_ENSURE_ARG_POINTER(aFolder); if (index == nsMsgViewIndex_None || index >= (uint32_t)m_folders.Count()) return NS_MSG_INVALID_DBVIEW_INDEX; NS_IF_ADDREF(*aFolder = m_folders[index]); return *aFolder ? NS_OK : NS_ERROR_NULL_POINTER; } nsresult nsMsgSearchDBView::GetDBForViewIndex(nsMsgViewIndex index, nsIMsgDatabase** db) { nsCOMPtr aFolder; nsresult rv = GetFolderForViewIndex(index, getter_AddRefs(aFolder)); NS_ENSURE_SUCCESS(rv, rv); return aFolder->GetMsgDatabase(db); } 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 thread; nsCOMPtr 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 = viewThread; } else { viewThread = static_cast(thread.get()); thread->GetChildHdrAt(0, getter_AddRefs(threadRoot)); } AddMsgToHashTables(msgHdr, thread); nsCOMPtr 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() || (IsValidIndex(insertIndex) && !m_levels[insertIndex]), "inserting into middle of thread"); if (insertIndex == nsMsgViewIndex_None) return NS_MSG_INVALID_DBVIEW_INDEX; 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 (!IsValidIndex(threadIndex)) { NS_ERROR("couldn't find thread index for newly inserted header"); // Not really OK, but not failure exactly. return NS_OK; } 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 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; } // 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 threadHdr; GetMsgHdrForViewIndex(threadIndex, getter_AddRefs(threadHdr)); uint32_t saveFlags = m_flags[threadIndex]; bool threadIsExpanded = !(saveFlags & nsMsgMessageFlags::Elided); int32_t childCount = 0; nsMsgKey preservedKey; AutoTArray preservedSelection; int32_t selectionCount; int32_t currentIndex; bool hasSelection = 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 threadKeys; nsTArray threadFlags; nsTArray threadLevels; nsCOMArray 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() || (IsValidIndex(newIndex) && !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::GetMessageEnumerator( nsIMsgEnumerator** enumerator) { // We do not have an m_db, so the default behavior (in nsMsgDBView) is not // what we want (it will crash). We just want someone to enumerate the // headers that we already have. Conveniently, nsMsgDBView already knows // how to do this with its view enumerator, so we just use that. return nsMsgDBView::GetViewEnumerator(enumerator); } 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; } NS_IMETHODIMP nsMsgSearchDBView::OnSearchHit(nsIMsgDBHdr* aMsgHdr, nsIMsgFolder* folder) { NS_ENSURE_ARG(aMsgHdr); NS_ENSURE_ARG(folder); if (m_folders.IndexOf(folder) < 0) // Do this just for new folder. { nsCOMPtr dbToUse; nsCOMPtr folderInfo; folder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(dbToUse)); if (dbToUse) { dbToUse->AddListener(this); m_dbToUseList.AppendObject(dbToUse); } } m_totalMessagesInView++; if (m_sortValid) return InsertHdrFromFolder(aMsgHdr, folder); else return AddHdrFromFolder(aMsgHdr, folder); } NS_IMETHODIMP nsMsgSearchDBView::OnSearchDone(nsresult status) { // We want to set imap delete model once the search is over because setting // next message after deletion will happen before deleting the message and // search scope can change with every search. // Set to default in case it is non-imap folder. mDeleteModel = nsMsgImapDeleteModels::MoveToTrash; nsIMsgFolder* curFolder = m_folders.SafeObjectAt(0); if (curFolder) GetImapDeleteModel(curFolder); return NS_OK; } // For now also acts as a way of resetting the search datasource. NS_IMETHODIMP nsMsgSearchDBView::OnNewSearch() { int32_t oldSize = GetSize(); int32_t count = m_dbToUseList.Count(); for (int32_t j = 0; j < count; j++) m_dbToUseList[j]->RemoveListener(this); m_dbToUseList.Clear(); m_folders.Clear(); m_keys.Clear(); m_levels.Clear(); m_flags.Clear(); m_totalMessagesInView = 0; // Needs to happen after we remove the keys, since RowCountChanged() will // call our GetRowCount(). if (mTree) mTree->RowCountChanged(0, -oldSize); if (mJSTree) mJSTree->RowCountChanged(0, -oldSize); // mSearchResults->Clear(); return NS_OK; } NS_IMETHODIMP nsMsgSearchDBView::GetViewType(nsMsgViewTypeValue* aViewType) { NS_ENSURE_ARG_POINTER(aViewType); *aViewType = nsMsgViewType::eShowSearch; return NS_OK; } NS_IMETHODIMP nsMsgSearchDBView::SetSearchSession(nsIMsgSearchSession* aSession) { m_searchSession = do_GetWeakReference(aSession); return NS_OK; } NS_IMETHODIMP nsMsgSearchDBView::OnAnnouncerGoingAway( nsIDBChangeAnnouncer* instigator) { nsIMsgDatabase* db = static_cast(instigator); if (db) { db->RemoveListener(this); m_dbToUseList.RemoveObject(db); } return NS_OK; } nsCOMArray* nsMsgSearchDBView::GetFolders() { return &m_folders; } NS_IMETHODIMP nsMsgSearchDBView::GetCommandStatus( nsMsgViewCommandTypeValue command, bool* selectable_p, nsMsgViewCommandCheckStateValue* selected_p) { if (command != nsMsgViewCommandType::runJunkControls) return nsMsgDBView::GetCommandStatus(command, selectable_p, selected_p); *selectable_p = false; return NS_OK; } NS_IMETHODIMP nsMsgSearchDBView::DoCommandWithFolder(nsMsgViewCommandTypeValue command, nsIMsgFolder* destFolder) { mCommand = command; mDestFolder = destFolder; return nsMsgDBView::DoCommandWithFolder(command, destFolder); } NS_IMETHODIMP nsMsgSearchDBView::DoCommand(nsMsgViewCommandTypeValue command) { mCommand = command; if (command == nsMsgViewCommandType::deleteMsg || command == nsMsgViewCommandType::deleteNoTrash || command == nsMsgViewCommandType::selectAll || command == nsMsgViewCommandType::selectThread || command == nsMsgViewCommandType::selectFlagged || command == nsMsgViewCommandType::expandAll || command == nsMsgViewCommandType::collapseAll) return nsMsgDBView::DoCommand(command); nsresult rv = NS_OK; nsMsgViewIndexArray selection; GetIndicesForSelection(selection); // We need to break apart the selection by folders, and then call // ApplyCommandToIndices with the command and the indices in the // selection that are from that folder. mozilla::UniquePtr[]> indexArrays; int32_t numArrays; rv = PartitionSelectionByFolder(selection, indexArrays, &numArrays); NS_ENSURE_SUCCESS(rv, rv); for (int32_t folderIndex = 0; folderIndex < numArrays; folderIndex++) { rv = ApplyCommandToIndices(command, (indexArrays.get())[folderIndex]); NS_ENSURE_SUCCESS(rv, rv); } return rv; } // This method removes the specified line from the view, and adjusts the // various flags and levels of affected messages. nsresult nsMsgSearchDBView::RemoveByIndex(nsMsgViewIndex index) { if (!IsValidIndex(index)) return NS_MSG_INVALID_DBVIEW_INDEX; if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) { nsCOMPtr msgHdr; nsCOMPtr thread; nsresult rv = GetMsgHdrForViewIndex(index, getter_AddRefs(msgHdr)); NS_ENSURE_SUCCESS(rv, rv); GetXFThreadFromMsgHdr(msgHdr, getter_AddRefs(thread)); if (thread) { nsMsgXFViewThread* viewThread = static_cast(thread.get()); if (viewThread->MsgCount() == 2) { // If we removed the next to last message in the thread, // we need to adjust the flags on the first message in the thread. nsMsgViewIndex threadIndex = m_levels[index] ? index - 1 : index; if (threadIndex != nsMsgViewIndex_None) { AndExtraFlag(threadIndex, ~(MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided | MSG_VIEW_FLAG_HASCHILDREN)); m_levels[threadIndex] = 0; NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed); } } // Bump up the level of all the descendents of the message // that was removed, if the thread was expanded. uint8_t removedLevel = m_levels[index]; nsMsgViewIndex i = index + 1; if (i < m_levels.Length() && m_levels[i] > removedLevel) { // Promote the child of the removed message. uint8_t promotedLevel = m_levels[i]; m_levels[i] = promotedLevel - 1; i++; // Now promote all the children of the promoted message. for (; i < m_levels.Length() && m_levels[i] > promotedLevel; i++) m_levels[i] = m_levels[i] - 1; } } } m_folders.RemoveObjectAt(index); return nsMsgDBView::RemoveByIndex(index); } NS_IMETHODIMP nsMsgSearchDBView::ApplyCommandToIndices( nsMsgViewCommandTypeValue command, nsTArray const& selection) { mCommand = command; return nsMsgDBView::ApplyCommandToIndices(command, selection); } nsresult nsMsgSearchDBView::DeleteMessages( nsIMsgWindow* window, nsTArray const& selection, bool deleteStorage) { nsresult rv = GetFoldersAndHdrsForSelection(selection); NS_ENSURE_SUCCESS(rv, rv); if (mDeleteModel != nsMsgImapDeleteModels::MoveToTrash) deleteStorage = true; if (mDeleteModel != nsMsgImapDeleteModels::IMAPDelete) m_deletingRows = true; // Remember the deleted messages in case the user undoes the delete, // and we want to restore the hdr to the view, even if it no // longer matches the search criteria. for (nsMsgViewIndex viewIndex : selection) { nsCOMPtr msgHdr; (void)GetMsgHdrForViewIndex(viewIndex, getter_AddRefs(msgHdr)); if (msgHdr) { RememberDeletedMsgHdr(msgHdr); } // If we are deleting rows, save off the view indices. if (m_deletingRows) { mIndicesToNoteChange.AppendElement(viewIndex); } } rv = deleteStorage ? ProcessRequestsInAllFolders(window) : ProcessRequestsInOneFolder(window); if (NS_FAILED(rv)) m_deletingRows = false; return rv; } nsresult nsMsgSearchDBView::CopyMessages( nsIMsgWindow* window, nsTArray const& selection, bool isMove, nsIMsgFolder* destFolder) { GetFoldersAndHdrsForSelection(selection); return ProcessRequestsInOneFolder(window); } nsresult nsMsgSearchDBView::PartitionSelectionByFolder( nsTArray const& selection, mozilla::UniquePtr[]>& indexArrays, int32_t* numArrays) { nsCOMArray uniqueFoldersSelected; nsTArray numIndicesSelected; mCurIndex = 0; // Build unique folder list based on headers selected by the user. for (nsMsgViewIndex viewIndex : selection) { nsIMsgFolder* curFolder = m_folders[viewIndex]; int32_t folderIndex = uniqueFoldersSelected.IndexOf(curFolder); if (folderIndex < 0) { uniqueFoldersSelected.AppendObject(curFolder); numIndicesSelected.AppendElement(1); } else { numIndicesSelected[folderIndex]++; } } int32_t numFolders = uniqueFoldersSelected.Count(); indexArrays = mozilla::MakeUnique[]>(numFolders); *numArrays = numFolders; NS_ENSURE_TRUE(indexArrays, NS_ERROR_OUT_OF_MEMORY); for (int32_t folderIndex = 0; folderIndex < numFolders; folderIndex++) { (indexArrays.get())[folderIndex].SetCapacity( numIndicesSelected[folderIndex]); } for (nsMsgViewIndex viewIndex : selection) { nsIMsgFolder* curFolder = m_folders[viewIndex]; int32_t folderIndex = uniqueFoldersSelected.IndexOf(curFolder); (indexArrays.get())[folderIndex].AppendElement(viewIndex); } return NS_OK; } nsresult nsMsgSearchDBView::GetFoldersAndHdrsForSelection( nsTArray const& selection) { nsresult rv = NS_OK; mCurIndex = 0; m_uniqueFoldersSelected.Clear(); m_hdrsForEachFolder.Clear(); AutoTArray, 1> messages; rv = GetHeadersFromSelection(selection, messages); NS_ENSURE_SUCCESS(rv, rv); // Build unique folder list based on headers selected by the user. for (nsIMsgDBHdr* hdr : messages) { nsCOMPtr curFolder; hdr->GetFolder(getter_AddRefs(curFolder)); if (m_uniqueFoldersSelected.IndexOf(curFolder) < 0) { m_uniqueFoldersSelected.AppendObject(curFolder); } } // Group the headers selected by each folder. uint32_t numFolders = m_uniqueFoldersSelected.Count(); for (uint32_t folderIndex = 0; folderIndex < numFolders; folderIndex++) { nsIMsgFolder* curFolder = m_uniqueFoldersSelected[folderIndex]; nsTArray> msgHdrsForOneFolder; for (nsIMsgDBHdr* hdr : messages) { nsCOMPtr msgFolder; hdr->GetFolder(getter_AddRefs(msgFolder)); if (NS_SUCCEEDED(rv) && msgFolder && msgFolder == curFolder) { msgHdrsForOneFolder.AppendElement(hdr); } } m_hdrsForEachFolder.AppendElement(msgHdrsForOneFolder.Clone()); } return rv; } nsresult nsMsgSearchDBView::ApplyCommandToIndicesWithFolder( nsMsgViewCommandTypeValue command, nsTArray const& selection, nsIMsgFolder* destFolder) { mCommand = command; mDestFolder = destFolder; return nsMsgDBView::ApplyCommandToIndicesWithFolder(command, selection, destFolder); } // nsIMsgCopyServiceListener methods NS_IMETHODIMP nsMsgSearchDBView::OnStartCopy() { return NS_OK; } NS_IMETHODIMP nsMsgSearchDBView::OnProgress(uint32_t aProgress, uint32_t aProgressMax) { return NS_OK; } // Believe it or not, these next two are msgcopyservice listener methods! NS_IMETHODIMP nsMsgSearchDBView::SetMessageKey(nsMsgKey aMessageKey) { return NS_OK; } NS_IMETHODIMP nsMsgSearchDBView::GetMessageId(nsACString& messageId) { return NS_OK; } NS_IMETHODIMP nsMsgSearchDBView::OnStopCopy(nsresult aStatus) { if (NS_SUCCEEDED(aStatus)) { mCurIndex++; if ((int32_t)mCurIndex < m_uniqueFoldersSelected.Count()) { nsCOMPtr msgWindow(do_QueryReferent(mMsgWindowWeak)); ProcessRequestsInOneFolder(msgWindow); } } return NS_OK; } // End nsIMsgCopyServiceListener methods. nsresult nsMsgSearchDBView::ProcessRequestsInOneFolder(nsIMsgWindow* window) { nsresult rv = NS_OK; // Folder operations like copy/move are not implemented for .eml files. if (m_uniqueFoldersSelected.Count() == 0) return NS_ERROR_NOT_IMPLEMENTED; nsIMsgFolder* curFolder = m_uniqueFoldersSelected[mCurIndex]; NS_ASSERTION(curFolder, "curFolder is null"); nsTArray> const& msgs = m_hdrsForEachFolder[mCurIndex]; // called for delete with trash, copy and move if (mCommand == nsMsgViewCommandType::deleteMsg) curFolder->DeleteMessages(msgs, window, false /* delete storage */, false /* is move*/, this, true /*allowUndo*/); else { NS_ASSERTION(!(curFolder == mDestFolder), "The source folder and the destination folder are the same"); if (NS_SUCCEEDED(rv) && curFolder != mDestFolder) { nsCOMPtr copyService = do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv); if (NS_SUCCEEDED(rv)) { if (mCommand == nsMsgViewCommandType::moveMessages) copyService->CopyMessages(curFolder, msgs, mDestFolder, true /* isMove */, this, window, true /*allowUndo*/); else if (mCommand == nsMsgViewCommandType::copyMessages) copyService->CopyMessages(curFolder, msgs, mDestFolder, false /* isMove */, this, window, true /*allowUndo*/); } } } return rv; } nsresult nsMsgSearchDBView::ProcessRequestsInAllFolders(nsIMsgWindow* window) { uint32_t numFolders = m_uniqueFoldersSelected.Count(); for (uint32_t folderIndex = 0; folderIndex < numFolders; folderIndex++) { nsIMsgFolder* curFolder = m_uniqueFoldersSelected[folderIndex]; NS_ASSERTION(curFolder, "curFolder is null"); curFolder->DeleteMessages( m_hdrsForEachFolder[folderIndex], window, true /* delete storage */, false /* is move*/, nullptr /*copyServListener*/, false /*allowUndo*/); } return NS_OK; } NS_IMETHODIMP nsMsgSearchDBView::Sort(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder) { if (!m_checkedCustomColumns && CustomColumnsInSortAndNotRegistered()) return NS_OK; int32_t rowCountBeforeSort = GetSize(); if (!rowCountBeforeSort) return NS_OK; if (m_viewFlags & (nsMsgViewFlagsType::kThreadedDisplay | nsMsgViewFlagsType::kGroupBySort)) { // ### This forgets which threads were expanded, and is sub-optimal // since it rebuilds the thread objects. m_sortType = sortType; m_sortOrder = sortOrder; return RebuildView(m_viewFlags); } nsMsgKey preservedKey; AutoTArray preservedSelection; SaveAndClearSelection(&preservedKey, preservedSelection); nsresult rv = nsMsgDBView::Sort(sortType, sortOrder); // The sort may have changed the number of rows before we restore the // selection, tell the tree do this before we call restore selection. // This is safe when there is no selection. rv = AdjustRowCount(rowCountBeforeSort, GetSize()); RestoreSelection(preservedKey, preservedSelection); if (mTree) mTree->Invalidate(); if (mJSTree) mJSTree->Invalidate(); NS_ENSURE_SUCCESS(rv, rv); return rv; } // If nothing selected, return an NS_ERROR. NS_IMETHODIMP nsMsgSearchDBView::GetHdrForFirstSelectedMessage(nsIMsgDBHdr** hdr) { NS_ENSURE_ARG_POINTER(hdr); nsMsgViewIndex index; nsresult rv = GetViewIndexForFirstSelectedMsg(&index); NS_ENSURE_SUCCESS(rv, rv); return GetMsgHdrForViewIndex(index, hdr); } NS_IMETHODIMP nsMsgSearchDBView::OpenWithHdrs(nsIMsgEnumerator* aHeaders, nsMsgViewSortTypeValue aSortType, nsMsgViewSortOrderValue aSortOrder, nsMsgViewFlagsTypeValue aViewFlags, int32_t* aCount) { if (aViewFlags & nsMsgViewFlagsType::kGroupBySort) return nsMsgGroupView::OpenWithHdrs(aHeaders, aSortType, aSortOrder, aViewFlags, aCount); m_sortType = aSortType; m_sortOrder = aSortOrder; m_viewFlags = aViewFlags; SaveSortInfo(m_sortType, m_sortOrder); bool hasMore; nsresult rv = NS_OK; while (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv = aHeaders->HasMoreElements(&hasMore)) && hasMore) { nsCOMPtr msgHdr; nsCOMPtr folder; rv = aHeaders->GetNext(getter_AddRefs(msgHdr)); if (NS_SUCCEEDED(rv) && msgHdr) { msgHdr->GetFolder(getter_AddRefs(folder)); AddHdrFromFolder(msgHdr, folder); } } *aCount = m_keys.Length(); return rv; } nsresult nsMsgSearchDBView::GetFolderFromMsgURI(const nsACString& aMsgURI, nsIMsgFolder** aFolder) { nsCOMPtr msgMessageService; nsresult rv = GetMessageServiceFromURI(aMsgURI, getter_AddRefs(msgMessageService)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr msgHdr; rv = msgMessageService->MessageURIToMsgHdr(aMsgURI, getter_AddRefs(msgHdr)); NS_ENSURE_SUCCESS(rv, rv); return msgHdr->GetFolder(aFolder); } nsMsgViewIndex nsMsgSearchDBView::FindHdr(nsIMsgDBHdr* msgHdr, nsMsgViewIndex startIndex, bool allowDummy) { nsCOMPtr curHdr; uint32_t index; // It would be nice to take advantage of sorted views when possible. for (index = startIndex; index < GetSize(); index++) { GetMsgHdrForViewIndex(index, getter_AddRefs(curHdr)); if (curHdr == msgHdr && (allowDummy || !(m_flags[index] & MSG_VIEW_FLAG_DUMMY) || (m_flags[index] & nsMsgMessageFlags::Elided))) break; } return index < GetSize() ? index : nsMsgViewIndex_None; } // This method looks for the XF thread that corresponds to this message hdr, // first by looking up the message id, then references, and finally, if subject // threading is turned on, the subject. nsresult nsMsgSearchDBView::GetXFThreadFromMsgHdr(nsIMsgDBHdr* msgHdr, nsIMsgThread** pThread, bool* foundByMessageId) { NS_ENSURE_ARG_POINTER(msgHdr); NS_ENSURE_ARG_POINTER(pThread); nsAutoCString messageId; msgHdr->GetMessageId(getter_Copies(messageId)); *pThread = nullptr; m_threadsTable.Get(messageId, pThread); // The caller may want to know if we found the thread by the msgHdr's // messageId. if (foundByMessageId) *foundByMessageId = *pThread != nullptr; if (!*pThread) { uint16_t numReferences = 0; msgHdr->GetNumReferences(&numReferences); for (int32_t i = numReferences - 1; i >= 0 && !*pThread; i--) { nsAutoCString reference; msgHdr->GetStringReference(i, reference); if (reference.IsEmpty()) break; m_threadsTable.Get(reference, pThread); } } // If we're threading by subject, and we couldn't find the thread by ref, // just treat subject as an other ref. if (!*pThread && !gReferenceOnlyThreading) { nsCString subject; msgHdr->GetSubject(subject); // This is the raw rfc822 subject header, so this is OK. m_threadsTable.Get(subject, pThread); } return (*pThread) ? NS_OK : NS_ERROR_FAILURE; } bool nsMsgSearchDBView::GetMsgHdrFromHash(nsCString& reference, nsIMsgDBHdr** hdr) { return m_hdrsTable.Get(reference, hdr); } bool nsMsgSearchDBView::GetThreadFromHash(nsCString& reference, nsIMsgThread** thread) { return m_threadsTable.Get(reference, thread); } nsresult nsMsgSearchDBView::AddRefToHash(nsCString& reference, nsIMsgThread* thread) { // Check if this reference is already is associated with a thread; // If so, don't overwrite that association. nsCOMPtr oldThread; m_threadsTable.Get(reference, getter_AddRefs(oldThread)); if (oldThread) return NS_OK; m_threadsTable.InsertOrUpdate(reference, thread); return NS_OK; } nsresult nsMsgSearchDBView::AddMsgToHashTables(nsIMsgDBHdr* msgHdr, nsIMsgThread* thread) { NS_ENSURE_ARG_POINTER(msgHdr); uint16_t numReferences = 0; nsresult rv; msgHdr->GetNumReferences(&numReferences); for (int32_t i = 0; i < numReferences; i++) { nsAutoCString reference; msgHdr->GetStringReference(i, reference); if (reference.IsEmpty()) break; rv = AddRefToHash(reference, thread); NS_ENSURE_SUCCESS(rv, rv); } nsCString messageId; msgHdr->GetMessageId(getter_Copies(messageId)); m_hdrsTable.InsertOrUpdate(messageId, msgHdr); if (!gReferenceOnlyThreading) { nsCString subject; msgHdr->GetSubject(subject); // if we're threading by subject, just treat subject as an other ref. AddRefToHash(subject, thread); } return AddRefToHash(messageId, thread); } nsresult nsMsgSearchDBView::RemoveRefFromHash(nsCString& reference) { m_threadsTable.Remove(reference); return NS_OK; } nsresult nsMsgSearchDBView::RemoveMsgFromHashTables(nsIMsgDBHdr* msgHdr) { NS_ENSURE_ARG_POINTER(msgHdr); uint16_t numReferences = 0; nsresult rv = NS_OK; msgHdr->GetNumReferences(&numReferences); for (int32_t i = 0; i < numReferences; i++) { nsAutoCString reference; msgHdr->GetStringReference(i, reference); if (reference.IsEmpty()) break; rv = RemoveRefFromHash(reference); if (NS_FAILED(rv)) break; } nsCString messageId; msgHdr->GetMessageId(getter_Copies(messageId)); m_hdrsTable.Remove(messageId); RemoveRefFromHash(messageId); if (!gReferenceOnlyThreading) { nsCString subject; msgHdr->GetSubject(subject); // If we're threading by subject, just treat subject as an other ref. RemoveRefFromHash(subject); } return rv; } nsMsgGroupThread* nsMsgSearchDBView::CreateGroupThread( nsIMsgDatabase* /* db */) { return new nsMsgXFGroupThread(); } NS_IMETHODIMP nsMsgSearchDBView::GetThreadContainingMsgHdr(nsIMsgDBHdr* msgHdr, nsIMsgThread** pThread) { if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort) return nsMsgGroupView::GetThreadContainingMsgHdr(msgHdr, pThread); else if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) return GetXFThreadFromMsgHdr(msgHdr, pThread); // If not threaded, use the real thread. nsCOMPtr msgDB; nsresult rv = GetDBForHeader(msgHdr, getter_AddRefs(msgDB)); NS_ENSURE_SUCCESS(rv, rv); return msgDB->GetThreadContainingMsgHdr(msgHdr, pThread); } 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; // Account for the existing thread root. numChildren--; InsertEmptyRows(viewIndex, numChildren); bool threadedView = m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay && !(m_viewFlags & nsMsgViewFlagsType::kGroupBySort); nsMsgXFViewThread* viewThread; if (threadedView) viewThread = static_cast(threadHdr); for (i = 1; i <= numChildren; i++) { nsCOMPtr 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; } NS_IMETHODIMP nsMsgSearchDBView::GetNumMsgsInView(int32_t* aNumMsgs) { NS_ENSURE_ARG_POINTER(aNumMsgs); *aNumMsgs = m_totalMessagesInView; return NS_OK; }