/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * David Bienvenu. * Portions created by the Initial Developer are Copyright (C) 2004 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "msgCore.h" #include "nsMsgGroupView.h" #include "nsIMsgHdr.h" #include "nsIMsgThread.h" #include "nsIDBFolderInfo.h" #include "nsIMsgSearchSession.h" #include "nsMsgGroupThread.h" #include "nsITreeColumns.h" #define MSGHDR_CACHE_LOOK_AHEAD_SIZE 25 // Allocate this more to avoid reallocation on new mail. #define MSGHDR_CACHE_MAX_SIZE 8192 // Max msghdr cache entries. #define MSGHDR_CACHE_DEFAULT_SIZE 100 nsMsgGroupView::nsMsgGroupView() { m_dayChanged = PR_FALSE; m_lastCurExplodedTime.tm_mday = 0; } nsMsgGroupView::~nsMsgGroupView() { } NS_IMETHODIMP nsMsgGroupView::Open(nsIMsgFolder *aFolder, nsMsgViewSortTypeValue aSortType, nsMsgViewSortOrderValue aSortOrder, nsMsgViewFlagsTypeValue aViewFlags, PRInt32 *aCount) { nsresult rv = nsMsgDBView::Open(aFolder, aSortType, aSortOrder, aViewFlags, aCount); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr dbFolderInfo; PersistFolderInfo(getter_AddRefs(dbFolderInfo)); nsCOMPtr headers; rv = m_db->EnumerateMessages(getter_AddRefs(headers)); NS_ENSURE_SUCCESS(rv, rv); return OpenWithHdrs(headers, aSortType, aSortOrder, aViewFlags, aCount); } /* static */PRIntn PR_CALLBACK ReleaseThread (nsHashKey *aKey, void *thread, void *closure) { nsMsgGroupThread *groupThread = (nsMsgGroupThread *) thread; groupThread->Release(); return kHashEnumerateNext; } void nsMsgGroupView::InternalClose() { if (m_db && m_sortType == nsMsgViewSortType::byDate) { nsCOMPtr dbFolderInfo; nsresult rv = m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); if (dbFolderInfo) { PRUint32 expandFlags = 0; PRUint32 num = GetSize(); for (PRUint32 i = 0; i < num; i++) { if (m_flags[i] & MSG_VIEW_FLAG_ISTHREAD && ! (m_flags[i] & MSG_FLAG_ELIDED)) { nsCOMPtr msgHdr; nsresult rv = GetMsgHdrForViewIndex(i, getter_AddRefs(msgHdr)); if (msgHdr) { nsHashKey *hashKey = AllocHashKeyForHdr(msgHdr); if (hashKey) expandFlags |= 1 << ((nsPRUint32Key *)hashKey)->GetValue(); } } } dbFolderInfo->SetUint32Property("dateGroupFlags", expandFlags); } } // enumerate m_groupsTable releasing the thread objects. m_groupsTable.Reset(ReleaseThread); } NS_IMETHODIMP nsMsgGroupView::Close() { InternalClose(); return nsMsgThreadedDBView::Close(); } nsHashKey *nsMsgGroupView::AllocHashKeyForHdr(nsIMsgDBHdr *msgHdr) { static nsXPIDLCString cStringKey; static nsXPIDLString stringKey; switch (m_sortType) { case nsMsgViewSortType::bySubject: (void) msgHdr->GetSubject(getter_Copies(cStringKey)); return new nsCStringKey(cStringKey.get()); break; case nsMsgViewSortType::byAuthor: (void) nsMsgDBView::FetchAuthor(msgHdr, getter_Copies(stringKey)); return new nsStringKey(stringKey.get()); case nsMsgViewSortType::byRecipient: (void) msgHdr->GetRecipients(getter_Copies(cStringKey)); return new nsCStringKey(cStringKey.get()); case nsMsgViewSortType::byAccount: case nsMsgViewSortType::byTags: { nsCOMPtr dbToUse = m_db; if (!dbToUse) // probably search view GetDBForViewIndex(0, getter_AddRefs(dbToUse)); nsresult rv = (m_sortType == nsMsgViewSortType::byAccount) ? FetchAccount(msgHdr, getter_Copies(stringKey)) : FetchTags(msgHdr, getter_Copies(stringKey)); return NS_SUCCEEDED(rv) ? new nsStringKey(stringKey.get()) : nsnull; } break; case nsMsgViewSortType::byAttachments: { PRUint32 flags; msgHdr->GetFlags(&flags); return new nsPRUint32Key(flags & MSG_FLAG_ATTACHMENT ? 1 : 0); } case nsMsgViewSortType::byFlagged: { PRUint32 flags; msgHdr->GetFlags(&flags); return new nsPRUint32Key(flags & MSG_FLAG_MARKED ? 1 : 0); } case nsMsgViewSortType::byPriority: { nsMsgPriorityValue priority; msgHdr->GetPriority(&priority); return new nsPRUint32Key(priority); } break; case nsMsgViewSortType::byStatus: { PRUint32 status = 0; GetStatusSortValue(msgHdr, &status); return new nsPRUint32Key(status); } case nsMsgViewSortType::byDate: { PRUint32 ageBucket = 1; PRTime dateOfMsg; nsresult rv = msgHdr->GetDate(&dateOfMsg); PRTime currentTime = PR_Now(); PRExplodedTime currentExplodedTime; PR_ExplodeTime(currentTime, PR_LocalTimeParameters, ¤tExplodedTime); PRExplodedTime explodedMsgTime; PR_ExplodeTime(dateOfMsg, PR_LocalTimeParameters, &explodedMsgTime); if (m_lastCurExplodedTime.tm_mday && m_lastCurExplodedTime.tm_mday != currentExplodedTime.tm_mday) m_dayChanged = PR_TRUE; // this will cause us to rebuild the view. m_lastCurExplodedTime = currentExplodedTime; if (currentExplodedTime.tm_year == explodedMsgTime.tm_year && currentExplodedTime.tm_month == explodedMsgTime.tm_month && currentExplodedTime.tm_mday == explodedMsgTime.tm_mday) { // same day... ageBucket = 1; } // figure out how many days ago this msg arrived else if (LL_CMP(currentTime, >, dateOfMsg)) { // some constants for calculation static PRInt64 microSecondsPerSecond; static PRInt64 microSecondsPerDay; static PRInt64 secondsPerDay; static PRInt64 microSecondsPer6Days; static PRInt64 microSecondsPer13Days; static PRBool bGotConstants = PR_FALSE; if ( !bGotConstants ) { // seeds PRInt64 secondsPerDay; LL_I2L ( microSecondsPerSecond, PR_USEC_PER_SEC ); LL_UI2L ( secondsPerDay, 60 * 60 * 24 ); // derivees LL_MUL( microSecondsPerDay, secondsPerDay, microSecondsPerSecond ); LL_MUL( microSecondsPer6Days, microSecondsPerDay, 6 ); LL_MUL( microSecondsPer13Days, microSecondsPerDay, 13 ); bGotConstants = PR_TRUE; } // setting the time variables to local time PRInt64 GMTLocalTimeShift; LL_ADD( GMTLocalTimeShift, currentExplodedTime.tm_params.tp_gmt_offset, currentExplodedTime.tm_params.tp_dst_offset ); LL_MUL( GMTLocalTimeShift, GMTLocalTimeShift, microSecondsPerSecond ); LL_ADD( currentTime, currentTime, GMTLocalTimeShift ); LL_ADD( dateOfMsg, dateOfMsg, GMTLocalTimeShift ); // the most recent midnight, counting from current time PRInt64 todaysMicroSeconds, mostRecentMidnight; LL_MOD( todaysMicroSeconds, currentTime, microSecondsPerDay ); LL_SUB( mostRecentMidnight, currentTime, todaysMicroSeconds ); PRInt64 yesterday; LL_SUB( yesterday, mostRecentMidnight, microSecondsPerDay ); // most recent midnight minus 6 days PRInt64 mostRecentWeek; LL_SUB( mostRecentWeek, mostRecentMidnight, microSecondsPer6Days ); // was the message sent yesterday? if ( LL_CMP( dateOfMsg, >=, yesterday ) ) { // yes .... ageBucket = 2; } else if ( LL_CMP(dateOfMsg, >=, mostRecentWeek) ) { ageBucket = 3; } else { PRInt64 lastTwoWeeks; LL_SUB( lastTwoWeeks, mostRecentMidnight, microSecondsPer13Days); ageBucket = LL_CMP(dateOfMsg, >=, lastTwoWeeks) ? 4 : 5; } } return new nsPRUint32Key(ageBucket); } default: NS_ASSERTION(PR_FALSE, "no hash key for this type"); } return nsnull; } nsMsgGroupThread *nsMsgGroupView::AddHdrToThread(nsIMsgDBHdr *msgHdr, PRBool *pNewThread) { nsMsgKey msgKey; PRUint32 msgFlags; msgHdr->GetMessageKey(&msgKey); msgHdr->GetFlags(&msgFlags); nsHashKey *hashKey = AllocHashKeyForHdr(msgHdr); // if (m_sortType == nsMsgViewSortType::byDate) // msgKey = ((nsPRUint32Key *) hashKey)->GetValue(); nsMsgGroupThread *foundThread = nsnull; if (hashKey) foundThread = (nsMsgGroupThread *) m_groupsTable.Get(hashKey); PRBool newThread = !foundThread; *pNewThread = newThread; nsMsgViewIndex viewIndexOfThread; if (!foundThread) { foundThread = new nsMsgGroupThread(m_db); m_groupsTable.Put(hashKey, foundThread); foundThread->AddRef(); if (GroupViewUsesDummyRow()) { foundThread->m_dummy = PR_TRUE; msgFlags |= MSG_VIEW_FLAG_DUMMY | MSG_VIEW_FLAG_HASCHILDREN; } nsMsgViewIndex insertIndex = GetInsertIndex(msgHdr); if (insertIndex == nsMsgViewIndex_None) insertIndex = m_keys.GetSize(); m_keys.InsertAt(insertIndex, msgKey); m_flags.InsertAt(insertIndex, msgFlags | MSG_VIEW_FLAG_ISTHREAD | MSG_FLAG_ELIDED); m_levels.InsertAt(insertIndex, 0, 1); // if grouped by date, insert dummy header for "age" if (GroupViewUsesDummyRow()) { foundThread->m_keys.InsertAt(0, msgKey/* nsMsgKey_None */); foundThread->m_threadKey = ((nsPRUint32Key *) hashKey)->GetValue(); } } else { viewIndexOfThread = GetIndexOfFirstDisplayedKeyInThread(foundThread); } delete hashKey; if (foundThread) foundThread->AddChildFromGroupView(msgHdr, this); // check if new hdr became thread root if (!newThread && foundThread->m_keys[0] == msgKey) { if (viewIndexOfThread != nsMsgKey_None) m_keys.SetAt(viewIndexOfThread, msgKey); if (GroupViewUsesDummyRow()) foundThread->m_keys.SetAt(1, msgKey); // replace the old duplicate dummy header. } return foundThread; } NS_IMETHODIMP nsMsgGroupView::OpenWithHdrs(nsISimpleEnumerator *aHeaders, nsMsgViewSortTypeValue aSortType, nsMsgViewSortOrderValue aSortOrder, nsMsgViewFlagsTypeValue aViewFlags, PRInt32 *aCount) { nsresult rv = NS_OK; 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; PRBool hasMore; nsCOMPtr supports; nsCOMPtr msgHdr; while (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv = aHeaders->HasMoreElements(&hasMore)) && hasMore) { nsXPIDLCString cStringKey; nsXPIDLString stringKey; rv = aHeaders->GetNext(getter_AddRefs(supports)); if (NS_SUCCEEDED(rv) && supports) { PRBool notUsed; msgHdr = do_QueryInterface(supports); AddHdrToThread(msgHdr, ¬Used); } } PRUint32 expandFlags = 0; PRUint32 viewFlag = (m_sortType == nsMsgViewSortType::byDate) ? MSG_VIEW_FLAG_DUMMY : 0; if (viewFlag) { nsCOMPtr dbFolderInfo; nsresult rv = m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); 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 (PRUint32 viewIndex = 0; viewIndex < m_keys.GetSize(); viewIndex++) { nsCOMPtr thread; GetThreadContainingIndex(viewIndex, getter_AddRefs(thread)); if (thread) { PRUint32 numChildren; thread->GetNumChildren(&numChildren); if (numChildren > 1 || viewFlag) OrExtraFlag(viewIndex, viewFlag | MSG_VIEW_FLAG_HASCHILDREN); if (expandFlags) { nsMsgGroupThread *groupThread = NS_STATIC_CAST(nsMsgGroupThread *, (nsIMsgThread *) thread); if (expandFlags & (1 << groupThread->m_threadKey)) { PRUint32 numExpanded; ExpandByIndex(viewIndex, &numExpanded); viewIndex += numExpanded; } } } } *aCount = m_keys.GetSize(); return rv; } // if the day has changed, we need to close and re-open the view. nsresult nsMsgGroupView::HandleDayChange() { nsCOMPtr headers; if (NS_SUCCEEDED(m_db->EnumerateMessages(getter_AddRefs(headers)))) { PRInt32 count; m_dayChanged = PR_FALSE; nsMsgKeyArray preservedSelection; nsMsgKey curSelectedKey; SaveAndClearSelection(&curSelectedKey, &preservedSelection); InternalClose(); PRInt32 oldSize = GetSize(); // this is important, because the tree will ask us for our // row count, which get determine from the number of keys. m_keys.RemoveAll(); // be consistent m_flags.RemoveAll(); m_levels.RemoveAll(); // this needs to happen after we remove all the keys, since RowCountChanged() will call our GetRowCount() if (mTree) mTree->RowCountChanged(0, -oldSize); DisableChangeUpdates(); nsresult rv = OpenWithHdrs(headers, m_sortType, m_sortOrder, m_viewFlags, &count); EnableChangeUpdates(); if (mTree) mTree->RowCountChanged(0, GetSize()); NS_ENSURE_SUCCESS(rv,rv); // now, restore our desired selection nsMsgKeyArray keyArray; keyArray.Add(curSelectedKey); return RestoreSelection(curSelectedKey, &keyArray); } return NS_OK; } nsresult nsMsgGroupView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, PRBool /*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 HandleDayChange(); PRBool newThread; nsMsgGroupThread *thread = AddHdrToThread(newHdr, &newThread); if (thread) { nsMsgKey msgKey; PRUint32 msgFlags; newHdr->GetMessageKey(&msgKey); newHdr->GetFlags(&msgFlags); nsMsgViewIndex threadIndex = ThreadIndexOfMsg(msgKey); PRInt32 numRowsInserted = 1; if (newThread && GroupViewUsesDummyRow()) numRowsInserted++; // may need to fix thread counts if (threadIndex != nsMsgViewIndex_None) { if (newThread) m_flags[threadIndex] &= ~MSG_FLAG_ELIDED; else m_flags[threadIndex] |= MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_ISTHREAD; PRInt32 numRowsToInvalidate = 1; if (! (m_flags[threadIndex] & MSG_FLAG_ELIDED)) { PRUint32 msgIndexInThread = thread->m_keys.IndexOf(msgKey); PRBool insertedAtThreadRoot = !msgIndexInThread; if (!msgIndexInThread && GroupViewUsesDummyRow()) msgIndexInThread++; if (!newThread || GroupViewUsesDummyRow()) { // this msg is the new parent of an expanded thread. AddHdrToThread already // updated m_keys[threadIndex], so we need to insert the old parent as a child // and update m_flags accordingly. if (!newThread && (!msgIndexInThread || (msgIndexInThread == 1 && GroupViewUsesDummyRow()))) { PRUint32 saveOldFlags = m_flags[threadIndex + msgIndexInThread] & ~(MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_ISTHREAD); if (!msgIndexInThread) msgFlags |= MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_ISTHREAD; m_flags[threadIndex + msgIndexInThread] = msgFlags; // this will cause us to insert the old header as the first child, with // the right key and flags. msgFlags = saveOldFlags; msgIndexInThread++; msgKey = thread->m_keys[msgIndexInThread]; } m_keys.InsertAt(threadIndex + msgIndexInThread, msgKey); m_flags.InsertAt(threadIndex + msgIndexInThread, msgFlags); if (msgIndexInThread > 0) { m_levels.InsertAt(threadIndex + msgIndexInThread, 1); } else // insert new header at level 0, and bump old level 0 to 1 { m_levels.InsertAt(threadIndex, 0, 1); m_levels.SetAt(threadIndex + 1, 1); } } // the call to NoteChange() has to happen after we add the key // as NoteChange() will call RowCountChanged() which will call our GetRowCount() NoteChange((insertedAtThreadRoot && GroupViewUsesDummyRow()) ? threadIndex + msgIndexInThread - 1 : threadIndex + msgIndexInThread, numRowsInserted, nsMsgViewNotificationCode::insertOrDelete); numRowsToInvalidate = msgIndexInThread; } NoteChange(threadIndex, numRowsToInvalidate, nsMsgViewNotificationCode::changed); } } // if thread is expanded, we need to add hdr to view... return NS_OK; } NS_IMETHODIMP nsMsgGroupView::OnHdrChange(nsIMsgDBHdr *aHdrChanged, PRUint32 aOldFlags, PRUint32 aNewFlags, nsIDBChangeListener *aInstigator) { nsCOMPtr thread; // 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 HandleDayChange(); nsresult rv = GetThreadContainingMsgHdr(aHdrChanged, getter_AddRefs(thread)); NS_ENSURE_SUCCESS(rv, rv); PRUint32 deltaFlags = (aOldFlags ^ aNewFlags); if (deltaFlags & MSG_FLAG_READ) thread->MarkChildRead(aNewFlags & MSG_FLAG_READ); return nsMsgDBView::OnHdrChange(aHdrChanged, aOldFlags, aNewFlags, aInstigator); } NS_IMETHODIMP nsMsgGroupView::OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted, nsMsgKey aParentKey, PRInt32 aFlags, nsIDBChangeListener *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 HandleDayChange(); nsCOMPtr thread; nsMsgKey keyDeleted; aHdrDeleted->GetMessageKey(&keyDeleted); nsresult rv = GetThreadContainingMsgHdr(aHdrDeleted, getter_AddRefs(thread)); NS_ENSURE_SUCCESS(rv, rv); nsMsgViewIndex viewIndexOfThread = GetIndexOfFirstDisplayedKeyInThread(thread); thread->RemoveChildHdr(aHdrDeleted, nsnull); nsMsgGroupThread *groupThread = NS_STATIC_CAST(nsMsgGroupThread *, (nsIMsgThread *) thread); PRBool rootDeleted = viewIndexOfThread != nsMsgKey_None && m_keys.GetAt(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) { nsMsgDBView::RemoveByIndex(viewIndexOfThread - 1); if (m_deletingRows) mIndicesToNoteChange.Add(viewIndexOfThread - 1); } } else if (rootDeleted && viewIndexOfThread > 0) { m_keys.SetAt(viewIndexOfThread - 1, m_keys.GetAt(viewIndexOfThread)); OrExtraFlag(viewIndexOfThread - 1, MSG_VIEW_FLAG_DUMMY | MSG_VIEW_FLAG_ISTHREAD); } } if (!groupThread->m_keys.GetSize()) { nsHashKey *hashKey = AllocHashKeyForHdr(aHdrDeleted); if (hashKey) m_groupsTable.Remove(hashKey); delete hashKey; } return rv; } NS_IMETHODIMP nsMsgGroupView::GetRowProperties(PRInt32 aRow, nsISupportsArray *aProperties) { if (!IsValidIndex(aRow)) return NS_MSG_INVALID_DBVIEW_INDEX; if (m_flags[aRow] & MSG_VIEW_FLAG_DUMMY) return aProperties->AppendElement(kDummyMsgAtom); return nsMsgDBView::GetRowProperties(aRow, aProperties); } NS_IMETHODIMP nsMsgGroupView::GetCellProperties(PRInt32 aRow, nsITreeColumn *aCol, nsISupportsArray *aProperties) { if (m_flags[aRow] & MSG_VIEW_FLAG_DUMMY) return aProperties->AppendElement(kDummyMsgAtom); return nsMsgDBView::GetCellProperties(aRow, aCol, aProperties); } NS_IMETHODIMP nsMsgGroupView::GetCellText(PRInt32 aRow, nsITreeColumn* aCol, nsAString& aValue) { const PRUnichar* colID; aCol->GetIdConst(&colID); if (m_flags[aRow] & MSG_VIEW_FLAG_DUMMY && colID[0] != 'u') { nsCOMPtr msgHdr; nsresult rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr)); NS_ENSURE_SUCCESS(rv, rv); nsHashKey *hashKey = AllocHashKeyForHdr(msgHdr); if (!hashKey) return NS_OK; nsMsgGroupThread *groupThread = (nsMsgGroupThread *) m_groupsTable.Get(hashKey); if (colID[0] == 's' && colID[1] == 'u' ) { aValue.SetCapacity(0); nsXPIDLString valueText; switch (m_sortType) { case nsMsgViewSortType::byDate: { switch (((nsPRUint32Key *)hashKey)->GetValue()) { case 1: if (!m_kTodayString.get()) m_kTodayString.Adopt(GetString(NS_LITERAL_STRING("today").get())); aValue.Assign(m_kTodayString); break; case 2: if (!m_kYesterdayString.get()) m_kYesterdayString.Adopt(GetString(NS_LITERAL_STRING("yesterday").get())); aValue.Assign(m_kYesterdayString); break; case 3: if (!m_kLastWeekString.get()) m_kLastWeekString.Adopt(GetString(NS_LITERAL_STRING("lastWeek").get())); aValue.Assign(m_kLastWeekString); break; case 4: if (!m_kTwoWeeksAgoString.get()) m_kTwoWeeksAgoString.Adopt(GetString(NS_LITERAL_STRING("twoWeeksAgo").get())); aValue.Assign(m_kTwoWeeksAgoString); break; case 5: if (!m_kOldMailString.get()) m_kOldMailString.Adopt(GetString(NS_LITERAL_STRING("older").get())); aValue.Assign(m_kOldMailString); break; default: NS_ASSERTION(PR_FALSE, "bad age thread"); break; } break; } case nsMsgViewSortType::byAuthor: FetchAuthor(msgHdr, getter_Copies(valueText)); aValue.Assign(valueText.get()); break; case nsMsgViewSortType::byStatus: rv = FetchStatus(m_flags[aRow], getter_Copies(valueText)); if (!valueText) valueText.Adopt(GetString(NS_LITERAL_STRING("messagesWithNoStatus").get())); aValue.Assign(valueText); break; case nsMsgViewSortType::byTags: rv = FetchTags(msgHdr, getter_Copies(valueText)); if (valueText.IsEmpty()) valueText.Adopt(GetString(NS_LITERAL_STRING("untaggedMessages").get())); aValue.Assign(valueText); break; case nsMsgViewSortType::byPriority: FetchPriority(msgHdr, getter_Copies(valueText)); if (!valueText) valueText.Adopt(GetString(NS_LITERAL_STRING("noPriority").get())); aValue.Assign(valueText); break; case nsMsgViewSortType::byAccount: FetchAccount(msgHdr, getter_Copies(valueText)); aValue.Assign(valueText); break; case nsMsgViewSortType::byRecipient: FetchRecipients(msgHdr, getter_Copies(valueText)); aValue.Assign(valueText); break; case nsMsgViewSortType::byAttachments: valueText.Adopt(GetString(((nsPRUint32Key *)hashKey)->GetValue() ? NS_LITERAL_STRING("attachments").get() : NS_LITERAL_STRING("noAttachments").get())); aValue.Assign(valueText); break; case nsMsgViewSortType::byFlagged: valueText.Adopt(GetString(((nsPRUint32Key *)hashKey)->GetValue() ? NS_LITERAL_STRING("groupFlagged").get() : NS_LITERAL_STRING("notFlagged").get())); aValue.Assign(valueText); break; default: NS_ASSERTION(PR_FALSE, "we don't sort by group for this type"); break; } } else if (colID[0] == 't') { nsAutoString formattedCountString; PRUint32 numChildren = (groupThread) ? groupThread->NumRealChildren() : 0; formattedCountString.AppendInt(numChildren); aValue.Assign(formattedCountString); } delete hashKey; return NS_OK; } return nsMsgDBView::GetCellText(aRow, aCol, aValue); } NS_IMETHODIMP nsMsgGroupView::LoadMessageByViewIndex(nsMsgViewIndex aViewIndex) { if (m_flags[aViewIndex] & MSG_VIEW_FLAG_DUMMY) { // if we used to have one item selected, and now we have more than one, we should clear the message pane. nsCOMPtr controller; if (mMsgWindow && NS_SUCCEEDED(mMsgWindow->GetMessagePaneController(getter_AddRefs(controller))) && controller) controller->ClearMsgPane(); // since we are selecting a dummy row, we should also clear out m_currentlyDisplayedMsgUri m_currentlyDisplayedMsgUri.Truncate(); return NS_OK; } else return nsMsgDBView::LoadMessageByViewIndex(aViewIndex); } nsresult nsMsgGroupView::GetThreadContainingMsgHdr(nsIMsgDBHdr *msgHdr, nsIMsgThread **pThread) { nsHashKey *hashKey = AllocHashKeyForHdr(msgHdr); if (hashKey) { nsMsgGroupThread *groupThread = (nsMsgGroupThread *) m_groupsTable.Get(hashKey); if (groupThread) groupThread->QueryInterface(NS_GET_IID(nsIMsgThread), (void **) pThread); delete hashKey; } else *pThread = nsnull; return (*pThread) ? NS_OK : NS_ERROR_FAILURE; } PRInt32 nsMsgGroupView::FindLevelInThread(nsIMsgDBHdr *msgHdr, nsMsgViewIndex startOfThread, nsMsgViewIndex viewIndex) { return (startOfThread == viewIndex) ? 0 : 1; } nsMsgViewIndex nsMsgGroupView::ThreadIndexOfMsg(nsMsgKey msgKey, nsMsgViewIndex msgIndex /* = nsMsgViewIndex_None */, PRInt32 *pThreadCount /* = NULL */, PRUint32 *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); } PRBool nsMsgGroupView::GroupViewUsesDummyRow() { return (m_sortType != nsMsgViewSortType::bySubject); }