RetroZilla/mailnews/base/src/nsMsgGroupView.cpp
2015-10-20 23:03:22 -04:00

777 lines
27 KiB
C++

/* -*- 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 <nsIDBFolderInfo> dbFolderInfo;
PersistFolderInfo(getter_AddRefs(dbFolderInfo));
nsCOMPtr <nsISimpleEnumerator> 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 <nsIDBFolderInfo> 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 <nsIMsgDBHdr> 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 <nsIMsgDatabase> 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, &currentExplodedTime);
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 <nsISupports> supports;
nsCOMPtr <nsIMsgDBHdr> 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, &notUsed);
}
}
PRUint32 expandFlags = 0;
PRUint32 viewFlag = (m_sortType == nsMsgViewSortType::byDate) ? MSG_VIEW_FLAG_DUMMY : 0;
if (viewFlag)
{
nsCOMPtr <nsIDBFolderInfo> 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 <nsIMsgThread> 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 <nsISimpleEnumerator> 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 <nsIMsgThread> 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 <nsIMsgThread> 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 <nsIMsgDBHdr> 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 <nsIMsgMessagePaneController> 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);
}