mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-06 17:16:12 +00:00
4aacbb417c
#72264 sort by thread doesn't restore selection #74068 if we switch sort mode, scroll to find the message we are displaying sr=bienvenu
634 lines
21 KiB
C++
634 lines
21 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape 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/NPL/
|
|
*
|
|
* 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 Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 2001 Netscape Communications Corporation. All
|
|
* Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*/
|
|
|
|
#include "msgCore.h"
|
|
#include "nsMsgThreadedDBView.h"
|
|
#include "nsIMsgHdr.h"
|
|
#include "nsIMsgThread.h"
|
|
|
|
nsMsgThreadedDBView::nsMsgThreadedDBView()
|
|
{
|
|
/* member initializers and constructor code */
|
|
m_havePrevView = PR_FALSE;
|
|
}
|
|
|
|
nsMsgThreadedDBView::~nsMsgThreadedDBView()
|
|
{
|
|
/* destructor code */
|
|
}
|
|
|
|
NS_IMETHODIMP nsMsgThreadedDBView::Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, nsMsgViewFlagsTypeValue viewFlags, PRInt32 *pCount)
|
|
{
|
|
nsresult rv;
|
|
rv = nsMsgDBView::Open(folder, sortType, sortOrder, viewFlags, pCount);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (pCount)
|
|
*pCount = 0;
|
|
return InitThreadedView(pCount);
|
|
}
|
|
|
|
NS_IMETHODIMP nsMsgThreadedDBView::Close()
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
nsresult nsMsgThreadedDBView::InitThreadedView(PRInt32 *pCount)
|
|
{
|
|
nsresult rv;
|
|
|
|
m_keys.RemoveAll();
|
|
m_flags.RemoveAll();
|
|
m_levels.RemoveAll();
|
|
m_prevKeys.RemoveAll();
|
|
m_prevFlags.RemoveAll();
|
|
m_prevLevels.RemoveAll();
|
|
m_havePrevView = PR_FALSE;
|
|
nsresult getSortrv = NS_OK; // ### TODO m_db->GetSortInfo(&sortType, &sortOrder);
|
|
|
|
// list all the ids into m_keys.
|
|
nsMsgKey startMsg = 0;
|
|
do
|
|
{
|
|
const PRInt32 kIdChunkSize = 200;
|
|
PRInt32 numListed = 0;
|
|
nsMsgKey idArray[kIdChunkSize];
|
|
PRInt32 flagArray[kIdChunkSize];
|
|
char levelArray[kIdChunkSize];
|
|
|
|
rv = ListThreadIds(&startMsg, (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly) != 0, idArray, flagArray,
|
|
levelArray, kIdChunkSize, &numListed, nsnull);
|
|
if (NS_SUCCEEDED(rv))
|
|
{
|
|
PRInt32 numAdded = AddKeys(idArray, flagArray, levelArray, m_sortType, numListed);
|
|
if (pCount)
|
|
*pCount += numAdded;
|
|
}
|
|
|
|
} while (NS_SUCCEEDED(rv) && startMsg != nsMsgKey_None);
|
|
|
|
if (NS_SUCCEEDED(getSortrv))
|
|
{
|
|
rv = InitSort(m_sortType, m_sortOrder);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
nsresult nsMsgThreadedDBView::AddKeys(nsMsgKey *pKeys, PRInt32 *pFlags, const char *pLevels, nsMsgViewSortTypeValue sortType, PRInt32 numKeysToAdd)
|
|
|
|
{
|
|
PRInt32 numAdded = 0;
|
|
for (PRInt32 i = 0; i < numKeysToAdd; i++)
|
|
{
|
|
PRInt32 threadFlag = pFlags[i];
|
|
PRInt32 flag = threadFlag;
|
|
|
|
// skip ignored threads.
|
|
if ((threadFlag & MSG_FLAG_IGNORED) && !(m_viewFlags & nsMsgViewFlagsType::kShowIgnored))
|
|
continue;
|
|
// by default, make threads collapsed, unless we're in only viewing new msgs
|
|
|
|
if (flag & MSG_VIEW_FLAG_HASCHILDREN)
|
|
flag |= MSG_FLAG_ELIDED;
|
|
// should this be persistent? Doesn't seem to need to be.
|
|
flag |= MSG_VIEW_FLAG_ISTHREAD;
|
|
m_keys.Add(pKeys[i]);
|
|
m_flags.Add(flag);
|
|
m_levels.Add(pLevels[i]);
|
|
numAdded++;
|
|
if ((/*m_viewFlags & nsMsgViewFlagsType::kUnreadOnly || */(sortType != nsMsgViewSortType::byThread)) && flag & MSG_FLAG_ELIDED)
|
|
{
|
|
ExpandByIndex(m_keys.GetSize() - 1, NULL);
|
|
}
|
|
}
|
|
return numAdded;
|
|
}
|
|
|
|
NS_IMETHODIMP nsMsgThreadedDBView::Sort(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder)
|
|
{
|
|
nsresult rv;
|
|
|
|
nsMsgKeyArray preservedSelection;
|
|
SaveSelection(&preservedSelection);
|
|
|
|
PRInt32 rowCountBeforeSort = GetSize();
|
|
|
|
// if the client wants us to forget our cached id arrays, they
|
|
// should build a new view. If this isn't good enough, we
|
|
// need a method to do that.
|
|
if (sortType != m_sortType || !m_sortValid)
|
|
{
|
|
if (sortType == nsMsgViewSortType::byThread)
|
|
{
|
|
m_sortType = sortType;
|
|
m_viewFlags |= nsMsgViewFlagsType::kThreadedDisplay;
|
|
if ( m_havePrevView)
|
|
{
|
|
// restore saved id array and flags array
|
|
m_keys.RemoveAll();
|
|
m_keys.InsertAt(0, &m_prevKeys);
|
|
m_flags.RemoveAll();
|
|
m_flags.InsertAt(0, &m_prevFlags);
|
|
m_levels.RemoveAll();
|
|
m_levels.InsertAt(0, &m_prevLevels);
|
|
// m_messageDB->SetSortInfo(sortType, sortOrder);
|
|
m_sortValid = PR_TRUE;
|
|
|
|
// the sort may have changed the number of rows
|
|
// before we restore the selection, tell the outliner
|
|
PRInt32 rowCountAfterSort = GetSize();
|
|
if (rowCountBeforeSort != rowCountAfterSort) {
|
|
mOutliner->RowCountChanged(rowCountBeforeSort, rowCountBeforeSort - rowCountAfterSort);
|
|
}
|
|
|
|
RestoreSelection(&preservedSelection);
|
|
if (mOutliner) mOutliner->Invalidate();
|
|
return NS_OK;
|
|
}
|
|
else
|
|
{
|
|
// set sort info in anticipation of what Init will do.
|
|
InitThreadedView(nsnull); // build up thread list.
|
|
if (sortOrder != nsMsgViewSortOrder::ascending)
|
|
Sort(sortType, sortOrder);
|
|
|
|
// the sort may have changed the number of rows
|
|
// before we update the selection, tell the outliner
|
|
PRInt32 rowCountAfterSort = GetSize();
|
|
if (rowCountBeforeSort != rowCountAfterSort) {
|
|
mOutliner->RowCountChanged(rowCountBeforeSort, rowCountBeforeSort - rowCountAfterSort);
|
|
}
|
|
|
|
RestoreSelection(&preservedSelection);
|
|
if (mOutliner) mOutliner->Invalidate();
|
|
return NS_OK;
|
|
}
|
|
}
|
|
else if (sortType != nsMsgViewSortType::byThread && m_sortType == nsMsgViewSortType::byThread /* && !m_havePrevView*/)
|
|
{
|
|
// going from SortByThread to non-thread sort - must build new key, level,and flags arrays
|
|
m_prevKeys.RemoveAll();
|
|
m_prevKeys.InsertAt(0, &m_keys);
|
|
m_prevFlags.RemoveAll();
|
|
m_prevFlags.InsertAt(0, &m_flags);
|
|
m_prevLevels.RemoveAll();
|
|
m_prevLevels.InsertAt(0, &m_levels);
|
|
ExpandAll();
|
|
// m_idArray.RemoveAll();
|
|
// m_flags.RemoveAll();
|
|
m_havePrevView = PR_TRUE;
|
|
m_viewFlags &= ~nsMsgViewFlagsType::kThreadedDisplay;
|
|
}
|
|
}
|
|
// call the base class in case we're not sorting by thread
|
|
rv = nsMsgDBView::Sort(sortType, sortOrder);
|
|
|
|
// the sort may have changed the number of rows
|
|
// before we restore the selection, tell the outliner
|
|
PRInt32 rowCountAfterSort = GetSize();
|
|
if (rowCountBeforeSort != rowCountAfterSort) {
|
|
mOutliner->RowCountChanged(rowCountBeforeSort, rowCountBeforeSort - rowCountAfterSort);
|
|
}
|
|
|
|
RestoreSelection(&preservedSelection);
|
|
if (mOutliner) mOutliner->Invalidate();
|
|
NS_ENSURE_SUCCESS(rv,rv);
|
|
return NS_OK;
|
|
}
|
|
|
|
// list the ids of the top-level thread ids starting at id == startMsg. This actually returns
|
|
// the ids of the first message in each thread.
|
|
nsresult nsMsgThreadedDBView::ListThreadIds(nsMsgKey *startMsg, PRBool unreadOnly, nsMsgKey *pOutput, PRInt32 *pFlags, char *pLevels,
|
|
PRInt32 numToList, PRInt32 *pNumListed, PRInt32 *pTotalHeaders)
|
|
{
|
|
nsresult rv = NS_OK;
|
|
// N.B..don't ret before assigning numListed to *pNumListed
|
|
PRInt32 numListed = 0;
|
|
|
|
if (*startMsg > 0)
|
|
{
|
|
NS_ASSERTION(m_threadEnumerator != nsnull, "where's our iterator?"); // for now, we'll just have to rely on the caller leaving
|
|
// the iterator in the right place.
|
|
}
|
|
else
|
|
{
|
|
NS_ASSERTION(m_db, "no db");
|
|
if (!m_db) return NS_ERROR_UNEXPECTED;
|
|
rv = m_db->EnumerateThreads(getter_AddRefs(m_threadEnumerator));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
PRBool hasMore = PR_FALSE;
|
|
|
|
nsCOMPtr <nsIMsgThread> threadHdr ;
|
|
PRInt32 threadsRemoved = 0;
|
|
for (numListed = 0; numListed < numToList
|
|
&& NS_SUCCEEDED(rv = m_threadEnumerator->HasMoreElements(&hasMore)) && (hasMore == PR_TRUE);)
|
|
{
|
|
nsCOMPtr <nsISupports> supports;
|
|
rv = m_threadEnumerator->GetNext(getter_AddRefs(supports));
|
|
if (!NS_SUCCEEDED(rv))
|
|
{
|
|
threadHdr = nsnull;
|
|
break;
|
|
}
|
|
threadHdr = do_QueryInterface(supports);
|
|
if (!threadHdr)
|
|
break;
|
|
nsCOMPtr <nsIMsgDBHdr> msgHdr;
|
|
PRUint32 numChildren;
|
|
if (unreadOnly)
|
|
threadHdr->GetNumUnreadChildren(&numChildren);
|
|
else
|
|
threadHdr->GetNumChildren(&numChildren);
|
|
PRUint32 threadFlags;
|
|
threadHdr->GetFlags(&threadFlags);
|
|
if (numChildren != 0) // not empty thread
|
|
{
|
|
if (pTotalHeaders)
|
|
*pTotalHeaders += numChildren;
|
|
if (unreadOnly)
|
|
rv = threadHdr->GetFirstUnreadChild(getter_AddRefs(msgHdr));
|
|
else
|
|
rv = threadHdr->GetChildAt(0, getter_AddRefs(msgHdr));
|
|
if (NS_SUCCEEDED(rv) && msgHdr != nsnull && WantsThisThread(threadHdr))
|
|
{
|
|
PRUint32 msgFlags;
|
|
PRUint32 newMsgFlags;
|
|
nsMsgKey msgKey;
|
|
msgHdr->GetMessageKey(&msgKey);
|
|
msgHdr->GetFlags(&msgFlags);
|
|
// turn off high byte of msg flags - used for view flags.
|
|
msgFlags &= ~MSG_VIEW_FLAGS;
|
|
pOutput[numListed] = msgKey;
|
|
pLevels[numListed] = 0;
|
|
// DMB TODO - This will do for now...Until we decide how to
|
|
// handle thread flags vs. message flags, if we do decide
|
|
// to make them different.
|
|
msgHdr->OrFlags(threadFlags & (MSG_FLAG_WATCHED | MSG_FLAG_IGNORED), &newMsgFlags);
|
|
PRBool isRead = PR_FALSE;
|
|
|
|
// make sure DB agrees with newsrc, if we're news.
|
|
m_db->IsRead(msgKey, &isRead);
|
|
m_db->MarkHdrRead(msgHdr, isRead, nsnull);
|
|
// try adding in MSG_VIEW_FLAG_ISTHREAD flag for unreadonly view.
|
|
pFlags[numListed] = msgFlags | MSG_VIEW_FLAG_ISTHREAD | threadFlags;
|
|
if (numChildren > 1)
|
|
pFlags[numListed] |= MSG_VIEW_FLAG_HASCHILDREN;
|
|
|
|
numListed++;
|
|
}
|
|
else
|
|
NS_ASSERTION(NS_SUCCEEDED(rv) && msgHdr, "couldn't get header for some reason");
|
|
}
|
|
else if (threadsRemoved < 10 && !(threadFlags & (MSG_FLAG_WATCHED | MSG_FLAG_IGNORED)))
|
|
{
|
|
// ### remove thread.
|
|
threadsRemoved++; // don't want to remove all empty threads first time
|
|
// around as it will choke preformance for upgrade.
|
|
#ifdef DEBUG_bienvenu
|
|
printf("removing empty non-ignored non-watched thread\n");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if (hasMore && threadHdr)
|
|
{
|
|
threadHdr->GetThreadKey(startMsg);
|
|
}
|
|
else
|
|
{
|
|
*startMsg = nsMsgKey_None;
|
|
m_threadEnumerator = nsnull;
|
|
}
|
|
*pNumListed = numListed;
|
|
return rv;
|
|
}
|
|
|
|
nsresult nsMsgThreadedDBView::ExpandAll()
|
|
{
|
|
nsresult rv = NS_OK;
|
|
// go through expanding in place
|
|
for (PRUint32 i = 0; i < m_keys.GetSize(); i++)
|
|
{
|
|
PRUint32 numExpanded;
|
|
PRUint32 flags = m_flags[i];
|
|
if (flags & MSG_VIEW_FLAG_HASCHILDREN && (flags & MSG_FLAG_ELIDED))
|
|
{
|
|
rv = ExpandByIndex(i, &numExpanded);
|
|
i += numExpanded;
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
void nsMsgThreadedDBView::OnExtraFlagChanged(nsMsgViewIndex index, PRUint32 extraFlag)
|
|
{
|
|
if (IsValidIndex(index) && m_havePrevView)
|
|
{
|
|
nsMsgKey keyChanged = m_keys[index];
|
|
nsMsgViewIndex prevViewIndex = m_prevKeys.FindIndex(keyChanged);
|
|
if (prevViewIndex != nsMsgViewIndex_None)
|
|
{
|
|
PRUint32 prevFlag = m_prevFlags.GetAt(prevViewIndex);
|
|
// don't want to change the elided bit, or has children or is thread
|
|
if (prevFlag & MSG_FLAG_ELIDED)
|
|
extraFlag |= MSG_FLAG_ELIDED;
|
|
else
|
|
extraFlag &= ~MSG_FLAG_ELIDED;
|
|
if (prevFlag & MSG_VIEW_FLAG_ISTHREAD)
|
|
extraFlag |= MSG_VIEW_FLAG_ISTHREAD;
|
|
else
|
|
extraFlag &= ~MSG_VIEW_FLAG_ISTHREAD;
|
|
if (prevFlag & MSG_VIEW_FLAG_HASCHILDREN)
|
|
extraFlag |= MSG_VIEW_FLAG_HASCHILDREN;
|
|
else
|
|
extraFlag &= ~MSG_VIEW_FLAG_HASCHILDREN;
|
|
m_prevFlags.SetAt(prevViewIndex, extraFlag); // will this be right?
|
|
}
|
|
}
|
|
// we don't really know what's changed, but to be on the safe side, set the sort invalid
|
|
// so that reverse sort will pick it up.
|
|
if (m_sortType == nsMsgViewSortType::byStatus || m_sortType == nsMsgViewSortType::byFlagged ||
|
|
m_sortType == nsMsgViewSortType::byUnread || m_sortType == nsMsgViewSortType::byPriority)
|
|
m_sortValid = PR_FALSE;
|
|
}
|
|
|
|
void nsMsgThreadedDBView::OnHeaderAddedOrDeleted()
|
|
{
|
|
ClearPrevIdArray();
|
|
}
|
|
|
|
void nsMsgThreadedDBView::ClearPrevIdArray()
|
|
{
|
|
m_prevKeys.RemoveAll();
|
|
m_prevFlags.RemoveAll();
|
|
m_havePrevView = PR_FALSE;
|
|
}
|
|
|
|
nsresult nsMsgThreadedDBView::InitSort(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder)
|
|
{
|
|
if (sortType == nsMsgViewSortType::byThread)
|
|
{
|
|
nsMsgDBView::Sort(nsMsgViewSortType::byId, sortOrder); // sort top level threads by id.
|
|
m_sortType = nsMsgViewSortType::byThread;
|
|
m_viewFlags |= nsMsgViewFlagsType::kThreadedDisplay;
|
|
// m_db->SetSortInfo(m_sortType, sortOrder);
|
|
}
|
|
else
|
|
m_viewFlags &= ~nsMsgViewFlagsType::kThreadedDisplay;
|
|
|
|
// by default, the unread only view should have all threads expanded.
|
|
if ((m_viewFlags & nsMsgViewFlagsType::kUnreadOnly) && m_sortType == nsMsgViewSortType::byThread)
|
|
ExpandAll();
|
|
if (sortType != nsMsgViewSortType::byThread)
|
|
ExpandAll(); // for now, expand all and do a flat sort.
|
|
|
|
Sort(sortType, sortOrder);
|
|
if (sortType != nsMsgViewSortType::byThread) // forget prev view, since it has everything expanded.
|
|
ClearPrevIdArray();
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsMsgThreadedDBView::OnNewHeader(nsMsgKey newKey, nsMsgKey aParentKey, PRBool ensureListed)
|
|
{
|
|
nsresult rv = NS_OK;
|
|
// views can override this behaviour, which is to append to view.
|
|
// This is the mail behaviour, but threaded views want
|
|
// to insert in order...
|
|
nsCOMPtr <nsIMsgDBHdr> msgHdr;
|
|
rv = m_db->GetMsgHdrForKey(newKey, getter_AddRefs(msgHdr));
|
|
if (NS_SUCCEEDED(rv) && msgHdr != nsnull)
|
|
{
|
|
PRUint32 msgFlags;
|
|
msgHdr->GetFlags(&msgFlags);
|
|
if ((m_viewFlags & nsMsgViewFlagsType::kUnreadOnly) && !ensureListed && (msgFlags & MSG_FLAG_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 (! (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))// || msgHdr->GetMessageKey() == m_messageDB->GetKeyOfFirstMsgInThread(msgHdr->GetMessageKey()))
|
|
rv = AddHdr(msgHdr);
|
|
else // 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;
|
|
nsMsgViewIndex threadIndex = ThreadIndexOfMsg(newKey, nsMsgViewIndex_None, &threadCount, &threadFlags);
|
|
if (threadIndex != nsMsgViewIndex_None)
|
|
{
|
|
// check if this is now the new thread hdr
|
|
PRUint32 flags = m_flags[threadIndex];
|
|
// if we have a collapsed thread which just got a new
|
|
// top of thread, change the keys array.
|
|
char level = 0; // ### TODO
|
|
if ((flags & MSG_FLAG_ELIDED) && level == 0
|
|
&& (!(m_viewFlags & nsMsgViewFlagsType::kUnreadOnly) || !(msgFlags & MSG_FLAG_READ)))
|
|
{
|
|
nsMsgKey msgKey;
|
|
msgHdr->GetMessageKey(&msgKey);
|
|
m_keys.SetAt(threadIndex, msgKey);
|
|
NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
|
|
}
|
|
if (! (flags & MSG_VIEW_FLAG_HASCHILDREN))
|
|
{
|
|
flags |= MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_ISTHREAD;
|
|
if (!(m_viewFlags & nsMsgViewFlagsType::kUnreadOnly))
|
|
flags |= MSG_FLAG_ELIDED;
|
|
m_flags[threadIndex] = flags;
|
|
NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
|
|
}
|
|
if (! (flags & MSG_FLAG_ELIDED)) // thread is expanded
|
|
{ // insert child into thread
|
|
PRUint8 level = 0; // levels of other hdrs may have changed!
|
|
PRUint32 newFlags = msgFlags;
|
|
nsMsgViewIndex insertIndex = 0;
|
|
#if 0
|
|
insertIndex = GetInsertInfoForNewHdr(newKey, threadIndex, &level);
|
|
#endif
|
|
// this header is the new king! try collapsing the existing thread,
|
|
// removing it, installing this header as king, and expanding it.
|
|
if (level == 0)
|
|
{
|
|
CollapseByIndex(threadIndex, nsnull);
|
|
// call base class, so child won't get promoted.
|
|
nsMsgDBView::RemoveByIndex(threadIndex);
|
|
newFlags |= MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN | MSG_FLAG_ELIDED;
|
|
}
|
|
m_keys.InsertAt(insertIndex, newKey);
|
|
m_flags.InsertAt(insertIndex, newFlags, 1);
|
|
m_levels.InsertAt(insertIndex, level);
|
|
NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
|
|
NoteChange(insertIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
|
|
if (level == 0)
|
|
ExpandByIndex(threadIndex, nsnull);
|
|
}
|
|
}
|
|
else // adding msg to thread that's not in view.
|
|
{
|
|
nsCOMPtr <nsIMsgThread> threadHdr;
|
|
m_db->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(threadHdr));
|
|
if (threadHdr)
|
|
{
|
|
AddMsgToThreadNotInView(threadHdr, msgHdr, ensureListed);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
rv = NS_MSG_MESSAGE_NOT_FOUND;
|
|
return rv;
|
|
}
|
|
|
|
nsresult nsMsgThreadedDBView::AddMsgToThreadNotInView(nsIMsgThread *threadHdr, nsIMsgDBHdr *msgHdr, PRBool ensureListed)
|
|
{
|
|
nsresult rv = NS_OK;
|
|
PRUint32 threadFlags;
|
|
threadHdr->GetFlags(&threadFlags);
|
|
if (!(threadFlags & MSG_FLAG_IGNORED))
|
|
rv = AddHdr(msgHdr);
|
|
return rv;
|
|
}
|
|
|
|
// This method just removes the specified line from the view. It does
|
|
// NOT delete it from the database.
|
|
nsresult nsMsgThreadedDBView::RemoveByIndex(nsMsgViewIndex index)
|
|
{
|
|
nsresult rv = NS_OK;
|
|
PRInt32 flags;
|
|
|
|
if (!IsValidIndex(index))
|
|
return NS_MSG_INVALID_DBVIEW_INDEX;
|
|
|
|
flags = m_flags[index];
|
|
|
|
if (! (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
|
|
return nsMsgDBView::RemoveByIndex(index);
|
|
|
|
nsCOMPtr <nsIMsgThread> threadHdr;
|
|
GetThreadContainingIndex(index, getter_AddRefs(threadHdr));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
PRUint32 numThreadChildren;
|
|
threadHdr->GetNumChildren(&numThreadChildren);
|
|
// check if we're the top level msg in the thread, and we're not collapsed.
|
|
if ((flags & MSG_VIEW_FLAG_ISTHREAD) && !(flags & MSG_FLAG_ELIDED) && (flags & MSG_VIEW_FLAG_HASCHILDREN))
|
|
{
|
|
// fix flags on thread header...Newly promoted message
|
|
// should have flags set correctly
|
|
if (threadHdr)
|
|
{
|
|
nsMsgDBView::RemoveByIndex(index);
|
|
nsCOMPtr <nsIMsgThread> nextThreadHdr;
|
|
if (numThreadChildren > 0)
|
|
{
|
|
// unreadOnly
|
|
nsCOMPtr <nsIMsgDBHdr> msgHdr;
|
|
rv = threadHdr->GetChildHdrAt(0, getter_AddRefs(msgHdr));
|
|
if (msgHdr != nsnull)
|
|
{
|
|
PRUint32 flag = 0;
|
|
msgHdr->GetFlags(&flag);
|
|
if (numThreadChildren > 1)
|
|
flag |= MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN;
|
|
m_flags.SetAtGrow(index, flag);
|
|
m_levels.SetAtGrow(index, 0);
|
|
}
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
else if (!(flags & MSG_VIEW_FLAG_ISTHREAD))
|
|
{
|
|
// we're not deleting the top level msg, but top level msg might be only msg in thread now
|
|
if (threadHdr && numThreadChildren == 1)
|
|
{
|
|
nsMsgKey msgKey;
|
|
rv = threadHdr->GetChildKeyAt(0, &msgKey);
|
|
if (NS_SUCCEEDED(rv))
|
|
{
|
|
nsMsgViewIndex threadIndex = FindViewIndex(msgKey);
|
|
if (threadIndex != nsMsgViewIndex_None)
|
|
{
|
|
PRUint32 flags = m_flags[threadIndex];
|
|
flags &= ~(MSG_VIEW_FLAG_ISTHREAD | MSG_FLAG_ELIDED | MSG_VIEW_FLAG_HASCHILDREN);
|
|
m_flags[threadIndex] = flags;
|
|
NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return nsMsgDBView::RemoveByIndex(index);
|
|
}
|
|
// deleting collapsed thread header is special case. Child will be promoted,
|
|
// so just tell FE that line changed, not that it was deleted
|
|
if (threadHdr && numThreadChildren > 0) // header has aleady been deleted from thread
|
|
{
|
|
// change the id array and flags array to reflect the child header.
|
|
// If we're not deleting the header, we want the second header,
|
|
// Otherwise, the first one (which just got promoted).
|
|
nsCOMPtr <nsIMsgDBHdr> msgHdr;
|
|
rv = threadHdr->GetChildHdrAt(0, getter_AddRefs(msgHdr));
|
|
if (msgHdr != nsnull)
|
|
{
|
|
nsMsgKey msgKey;
|
|
msgHdr->GetMessageKey(&msgKey);
|
|
m_keys.SetAt(index, msgKey);
|
|
|
|
PRUint32 flag = 0;
|
|
msgHdr->GetFlags(&flag);
|
|
// CopyDBFlagsToExtraFlags(msgHdr->GetFlags(), &flag);
|
|
// if (msgHdr->GetArticleNum() == msgHdr->GetThreadId())
|
|
flag |= MSG_VIEW_FLAG_ISTHREAD;
|
|
|
|
if (numThreadChildren == 1) // if only hdr in thread (with one about to be deleted)
|
|
// adjust flags.
|
|
{
|
|
flag &= ~MSG_VIEW_FLAG_HASCHILDREN;
|
|
flag &= ~MSG_FLAG_ELIDED;
|
|
// tell FE that thread header needs to be repainted.
|
|
NoteChange(index, 1, nsMsgViewNotificationCode::changed);
|
|
}
|
|
else
|
|
{
|
|
flag |= MSG_VIEW_FLAG_HASCHILDREN;
|
|
flag |= MSG_FLAG_ELIDED;
|
|
}
|
|
m_flags[index] = flag;
|
|
}
|
|
else
|
|
NS_ASSERTION(PR_FALSE, "couldn't find thread child");
|
|
NoteChange(index, 1, nsMsgViewNotificationCode::changed);
|
|
}
|
|
else
|
|
rv = nsMsgDBView::RemoveByIndex(index);
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP nsMsgThreadedDBView::GetViewType(nsMsgViewTypeValue *aViewType)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aViewType);
|
|
*aViewType = nsMsgViewType::eShowAllThreads;
|
|
return NS_OK;
|
|
}
|