mirror of
https://github.com/rn10950/RetroZilla.git
synced 2024-09-18 03:00:17 +02:00
777 lines
27 KiB
C++
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, ¤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 <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, ¬Used);
|
||
|
}
|
||
|
}
|
||
|
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);
|
||
|
}
|