nsresult nsMsgThreadedDBView::AddKeys(nsMsgKey *pKeys, PRInt32 *pFlags, const char *pLevels, nsMsgViewSortTypeValue sortType, PRInt32 numKeysToAdd) { PRInt32 numAdded = 0; // Allocate enough space first to avoid memory allocation/deallocation. m_keys.SetCapacity(m_keys.Length() + numKeysToAdd); m_flags.SetCapacity(m_flags.Length() + numKeysToAdd); m_levels.SetCapacity(m_levels.Length() + numKeysToAdd); for (PRInt32 i = 0; i < numKeysToAdd; i++) { PRInt32 threadFlag = pFlags[i]; PRInt32 flag = threadFlag; // skip ignored threads. if ((threadFlag & nsMsgMessageFlags::Ignored) && !(m_viewFlags & nsMsgViewFlagsType::kShowIgnored)) continue; // skip ignored subthreads nsCOMPtr <nsIMsgDBHdr> msgHdr; m_db->GetMsgHdrForKey(pKeys[i], getter_AddRefs(msgHdr)); if (!(m_viewFlags & nsMsgViewFlagsType::kShowIgnored)) { bool killed; msgHdr->GetIsKilled(&killed); if (killed) continue; } // by default, make threads collapsed, unless we're in only viewing new msgs if (flag & MSG_VIEW_FLAG_HASCHILDREN) flag |= nsMsgMessageFlags::Elided; // should this be persistent? Doesn't seem to need to be. flag |= MSG_VIEW_FLAG_ISTHREAD; m_keys.AppendElement(pKeys[i]); m_flags.AppendElement(flag); m_levels.AppendElement(pLevels[i]); numAdded++; // we expand as we build the view, which allows us to insert at the end of the key array, // instead of the middle, and is much faster. if ((!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) || m_viewFlags & nsMsgViewFlagsType::kExpandAll) && flag & nsMsgMessageFlags::Elided) ExpandByIndex(m_keys.Length() - 1, NULL); } return numAdded; }
NS_IMETHODIMP nsMsgGroupView::OpenWithHdrs(nsISimpleEnumerator *aHeaders, nsMsgViewSortTypeValue aSortType, nsMsgViewSortOrderValue aSortOrder, nsMsgViewFlagsTypeValue aViewFlags, int32_t *aCount) { nsresult rv = NS_OK; m_groupsTable.Clear(); if (aSortType == nsMsgViewSortType::byThread || aSortType == nsMsgViewSortType::byId || aSortType == nsMsgViewSortType::byNone || aSortType == nsMsgViewSortType::bySize) return NS_ERROR_INVALID_ARG; m_sortType = aSortType; m_sortOrder = aSortOrder; m_viewFlags = aViewFlags | nsMsgViewFlagsType::kThreadedDisplay | nsMsgViewFlagsType::kGroupBySort; SaveSortInfo(m_sortType, m_sortOrder); bool hasMore; nsCOMPtr <nsISupports> supports; nsCOMPtr <nsIMsgDBHdr> msgHdr; while (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv = aHeaders->HasMoreElements(&hasMore)) && hasMore) { rv = aHeaders->GetNext(getter_AddRefs(supports)); if (NS_SUCCEEDED(rv) && supports) { bool notUsed; msgHdr = do_QueryInterface(supports); AddHdrToThread(msgHdr, ¬Used); } } uint32_t expandFlags = 0; bool expandAll = m_viewFlags & nsMsgViewFlagsType::kExpandAll; uint32_t viewFlag = (m_sortType == nsMsgViewSortType::byDate) ? MSG_VIEW_FLAG_DUMMY : 0; if (viewFlag && m_db) { nsCOMPtr <nsIDBFolderInfo> dbFolderInfo; nsresult rv = m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); NS_ENSURE_SUCCESS(rv, rv); if (dbFolderInfo) dbFolderInfo->GetUint32Property("dateGroupFlags", 0, &expandFlags); } // go through the view updating the flags for threads with more than one message... // and if grouped by date, expanding threads that were expanded before. for (uint32_t viewIndex = 0; viewIndex < m_keys.Length(); viewIndex++) { nsCOMPtr <nsIMsgThread> thread; GetThreadContainingIndex(viewIndex, getter_AddRefs(thread)); if (thread) { uint32_t numChildren; thread->GetNumChildren(&numChildren); if (numChildren > 1 || viewFlag) OrExtraFlag(viewIndex, viewFlag | MSG_VIEW_FLAG_HASCHILDREN); if (expandAll || expandFlags) { nsMsgGroupThread *groupThread = static_cast<nsMsgGroupThread *>((nsIMsgThread *) thread); if (expandAll || expandFlags & (1 << groupThread->m_threadKey)) { uint32_t numExpanded; ExpandByIndex(viewIndex, &numExpanded); viewIndex += numExpanded; } } } } *aCount = m_keys.Length(); return rv; }
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; }
nsresult nsMsgThreadedDBView::SortThreads(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder) { NS_PRECONDITION(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay, "trying to sort unthreaded threads"); PRUint32 numThreads = 0; // the idea here is that copy the current view, then build up an m_keys and m_flags array of just the top level // messages in the view, and then call nsMsgDBView::Sort(sortType, sortOrder). // Then, we expand the threads in the result array that were expanded in the original view (perhaps by copying // from the original view, but more likely just be calling expand). for (PRUint32 i = 0; i < m_keys.Length(); i++) { if (m_flags[i] & MSG_VIEW_FLAG_ISTHREAD) { if (numThreads < i) { m_keys[numThreads] = m_keys[i]; m_flags[numThreads] = m_flags[i]; } m_levels[numThreads] = 0; numThreads++; } } m_keys.SetLength(numThreads); m_flags.SetLength(numThreads); m_levels.SetLength(numThreads); //m_viewFlags &= ~nsMsgViewFlagsType::kThreadedDisplay; m_sortType = nsMsgViewSortType::byNone; // sort from scratch nsMsgDBView::Sort(sortType, sortOrder); m_viewFlags |= nsMsgViewFlagsType::kThreadedDisplay; SetSuppressChangeNotifications(true); // Loop through the original array, for each thread that's expanded, find it in the new array // and expand the thread. We have to update MSG_VIEW_FLAG_HAS_CHILDREN because // we may be going from a flat sort, which doesn't maintain that flag, // to a threaded sort, which requires that flag. for (PRUint32 j = 0; j < m_keys.Length(); j++) { PRUint32 flags = m_flags[j]; if ((flags & (MSG_VIEW_FLAG_HASCHILDREN | nsMsgMessageFlags::Elided)) == MSG_VIEW_FLAG_HASCHILDREN) { PRUint32 numExpanded; m_flags[j] = flags | nsMsgMessageFlags::Elided; ExpandByIndex(j, &numExpanded); j += numExpanded; if (numExpanded > 0) m_flags[j - numExpanded] = flags | MSG_VIEW_FLAG_HASCHILDREN; } else if (flags & MSG_VIEW_FLAG_ISTHREAD && ! (flags & MSG_VIEW_FLAG_HASCHILDREN)) { nsCOMPtr <nsIMsgDBHdr> msgHdr; nsCOMPtr <nsIMsgThread> pThread; m_db->GetMsgHdrForKey(m_keys[j], getter_AddRefs(msgHdr)); if (msgHdr) { m_db->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(pThread)); if (pThread) { PRUint32 numChildren; pThread->GetNumChildren(&numChildren); if (numChildren > 1) m_flags[j] = flags | MSG_VIEW_FLAG_HASCHILDREN | nsMsgMessageFlags::Elided; } } } } SetSuppressChangeNotifications(false); return NS_OK; }