Back to index

lightning-sunbird  0.9+nobinonly
nsMsgQuickSearchDBView.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 2001
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Navin Gupta <naving@netscape.com> (Original Author)
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either of the GNU General Public License Version 2 or later (the "GPL"),
00027  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include "msgCore.h"
00040 #include "nsMsgQuickSearchDBView.h"
00041 #include "nsMsgFolderFlags.h"
00042 #include "nsIMsgHdr.h"
00043 #include "nsMsgBaseCID.h"
00044 #include "nsIMsgImapMailFolder.h"
00045 #include "nsImapCore.h"
00046 #include "nsIMsgHdr.h"
00047 #include "nsIDBFolderInfo.h"
00048 
00049 nsMsgQuickSearchDBView::nsMsgQuickSearchDBView()
00050 {
00051   m_usingCachedHits = PR_FALSE;
00052   m_cacheEmpty = PR_TRUE;
00053 }
00054 
00055 nsMsgQuickSearchDBView::~nsMsgQuickSearchDBView()
00056 {      
00057  /* destructor code */
00058 }
00059 
00060 NS_IMPL_ISUPPORTS_INHERITED2(nsMsgQuickSearchDBView, nsMsgDBView, nsIMsgDBView, nsIMsgSearchNotify)
00061 
00062 NS_IMETHODIMP nsMsgQuickSearchDBView::Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, nsMsgViewFlagsTypeValue viewFlags, PRInt32 *pCount)
00063 {
00064   nsresult rv = nsMsgDBView::Open(folder, sortType, sortOrder, viewFlags, pCount);
00065   NS_ENSURE_SUCCESS(rv, rv);
00066 
00067   if (!m_db)
00068     return NS_ERROR_NULL_POINTER;
00069   if (pCount)
00070     *pCount = 0;
00071   m_viewFolder = nsnull;
00072   return InitThreadedView(pCount);
00073 }
00074 
00075 NS_IMETHODIMP nsMsgQuickSearchDBView::DoCommand(nsMsgViewCommandTypeValue aCommand)
00076 {
00077   if (aCommand == nsMsgViewCommandType::markAllRead)
00078   {
00079     nsresult rv = NS_OK;
00080     m_folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, PR_FALSE, PR_TRUE /*dbBatching*/);
00081 
00082     for (PRInt32 i=0;NS_SUCCEEDED(rv) && i < GetSize();i++)
00083     {
00084       nsCOMPtr<nsIMsgDBHdr> msgHdr;
00085       m_db->GetMsgHdrForKey(m_keys[i],getter_AddRefs(msgHdr)); 
00086       rv = m_db->MarkHdrRead(msgHdr, PR_TRUE, nsnull);
00087     }
00088 
00089     m_folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, PR_TRUE, PR_TRUE /*dbBatching*/);
00090 
00091     nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_folder);
00092     if (NS_SUCCEEDED(rv) && imapFolder)
00093       rv = imapFolder->StoreImapFlags(kImapMsgSeenFlag, PR_TRUE, m_keys.GetArray(), 
00094                                       m_keys.GetSize(), nsnull);
00095 
00096     m_db->SetSummaryValid(PR_TRUE);
00097     return rv;
00098   }
00099   else
00100     return nsMsgDBView::DoCommand(aCommand);
00101 }
00102 
00103 NS_IMETHODIMP nsMsgQuickSearchDBView::GetViewType(nsMsgViewTypeValue *aViewType)
00104 {
00105     NS_ENSURE_ARG_POINTER(aViewType);
00106     *aViewType = nsMsgViewType::eShowQuickSearchResults; 
00107     return NS_OK;
00108 }
00109 
00110 nsresult nsMsgQuickSearchDBView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, PRBool ensureListed)
00111 {
00112   if (newHdr)
00113   {
00114     PRBool match=PR_FALSE;
00115     nsCOMPtr <nsIMsgSearchSession> searchSession = do_QueryReferent(m_searchSession);
00116     if (searchSession)
00117       searchSession->MatchHdr(newHdr, m_db, &match);
00118     if (match)
00119     {
00120       // put the new header in m_origKeys, so that expanding a thread will
00121       // show the newly added header.
00122       nsMsgKey newKey;
00123       (void) newHdr->GetMessageKey(&newKey);
00124       nsMsgViewIndex insertIndex = GetInsertIndexHelper(newHdr, &m_origKeys, 
00125                       nsMsgViewSortOrder::ascending, nsMsgViewSortType::byId);
00126       m_origKeys.InsertAt(insertIndex, newKey);
00127       nsMsgThreadedDBView::OnNewHeader(newHdr, aParentKey, ensureListed); // do not add a new message if there isn't a match.
00128     }
00129   }
00130   return NS_OK;
00131 }
00132 
00133 NS_IMETHODIMP nsMsgQuickSearchDBView::OnHdrChange(nsIMsgDBHdr *aHdrChanged, PRUint32 aOldFlags, 
00134                                        PRUint32 aNewFlags, nsIDBChangeListener *aInstigator)
00135 {
00136   nsresult rv = nsMsgDBView::OnHdrChange(aHdrChanged, aOldFlags, aNewFlags, aInstigator);
00137   // flags haven't really changed - check if the message is newly classified as junk 
00138   if ((aOldFlags == aNewFlags) && (aOldFlags & MSG_FLAG_NEW)) 
00139   {
00140     if (aHdrChanged)
00141     {
00142       nsXPIDLCString junkScoreStr;
00143       (void) aHdrChanged->GetStringProperty("junkscore", getter_Copies(junkScoreStr));
00144       if (atoi(junkScoreStr.get()) > 50)
00145       {
00146         nsXPIDLCString originStr;
00147         (void) aHdrChanged->GetStringProperty("junkscoreorigin", 
00148                                        getter_Copies(originStr));
00149 
00150         // if this was classified by the plugin, see if we're supposed to
00151         // show junk mail
00152         if (originStr.get()[0] == 'p') 
00153         {
00154           PRBool match=PR_FALSE;
00155           nsCOMPtr <nsIMsgSearchSession> searchSession = do_QueryReferent(m_searchSession);
00156           if (searchSession)
00157             searchSession->MatchHdr(aHdrChanged, m_db, &match);
00158           if (!match)
00159           {
00160             // remove hdr from view
00161             nsMsgViewIndex deletedIndex = FindHdr(aHdrChanged);
00162             if (deletedIndex != nsMsgViewIndex_None)
00163               RemoveByIndex(deletedIndex);
00164           }
00165         }
00166       }
00167     }
00168   }
00169   else if (m_viewFolder && (aOldFlags & MSG_FLAG_READ) != (aNewFlags & MSG_FLAG_READ))
00170   {
00171     // if we're displaying a single folder virtual folder for an imap folder,
00172     // the search criteria might be on message body, and we might not have the
00173     // message body offline, in which case we can't tell if the message 
00174     // matched or not. But if the unread flag changed, we need to update the
00175     // unread counts. Normally, VirtualFolderChangeListener::OnHdrChange will
00176     // handle this, but it won't work for body criteria when we don't have the
00177     // body offline.
00178     nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_viewFolder);
00179     if (imapFolder)
00180     {
00181       nsMsgViewIndex hdrIndex = FindHdr(aHdrChanged);
00182       if (hdrIndex != nsMsgViewIndex_None)
00183       {
00184         nsCOMPtr <nsIMsgSearchSession> searchSession = do_QueryReferent(m_searchSession);
00185         if (searchSession)
00186         {
00187           PRBool oldMatch, newMatch;
00188           rv = searchSession->MatchHdr(aHdrChanged, m_db, &newMatch);
00189           aHdrChanged->SetFlags(aOldFlags);
00190           rv = searchSession->MatchHdr(aHdrChanged, m_db, &oldMatch);
00191           aHdrChanged->SetFlags(aNewFlags); 
00192           // if it doesn't match the criteria, VirtualFolderChangeListener::OnHdrChange
00193           // won't tweak the read/unread counts. So do it here:
00194           if (!oldMatch && !newMatch)
00195           {
00196             nsCOMPtr <nsIMsgDatabase> virtDatabase;
00197             nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
00198 
00199             rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase));
00200             NS_ENSURE_SUCCESS(rv, rv);
00201             dbFolderInfo->ChangeNumUnreadMessages((aOldFlags & MSG_FLAG_READ) ? 1 : -1);
00202             m_viewFolder->UpdateSummaryTotals(PR_TRUE); // force update from db.
00203             virtDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
00204           }
00205         }
00206       }
00207     }
00208   }
00209   return rv;
00210 }
00211 
00212 NS_IMETHODIMP
00213 nsMsgQuickSearchDBView::GetSearchSession(nsIMsgSearchSession* *aSession)
00214 {
00215   NS_ASSERTION(PR_FALSE, "GetSearchSession method is not implemented");
00216   return NS_ERROR_NOT_IMPLEMENTED;
00217 }
00218 
00219 
00220 NS_IMETHODIMP
00221 nsMsgQuickSearchDBView::SetSearchSession(nsIMsgSearchSession *aSession)
00222 {
00223   m_searchSession = do_GetWeakReference(aSession);
00224   return NS_OK;
00225 }
00226 
00227 NS_IMETHODIMP
00228 nsMsgQuickSearchDBView::OnSearchHit(nsIMsgDBHdr* aMsgHdr, nsIMsgFolder *folder)
00229 {
00230   NS_ENSURE_ARG(aMsgHdr);
00231   if (!m_db)
00232     return NS_ERROR_NULL_POINTER;
00233   // remember search hit and when search is done, reconcile cache
00234   // with new hits;
00235   m_hdrHits.AppendObject(aMsgHdr);
00236   nsMsgKey key;
00237   aMsgHdr->GetMessageKey(&key);
00238   // is FindKey going to be expensive here? A lot of hits could make
00239   // it a little bit slow to search through the view for every hit.
00240   if (m_cacheEmpty || FindKey(key, PR_FALSE) == nsMsgViewIndex_None)
00241   return AddHdr(aMsgHdr); 
00242   else
00243     return NS_OK;
00244 }
00245 
00246 NS_IMETHODIMP
00247 nsMsgQuickSearchDBView::OnSearchDone(nsresult status)
00248 {
00249   if (m_viewFolder)
00250   {
00251     nsMsgKeyArray keyArray;
00252     nsXPIDLCString searchUri;
00253     m_viewFolder->GetURI(getter_Copies(searchUri));
00254     PRUint32 count = m_hdrHits.Count();
00255     // build up message keys.
00256     PRUint32 i;
00257     for (i = 0; i < count; i++)
00258     {
00259       nsMsgKey key;
00260       m_hdrHits[i]->GetMessageKey(&key);
00261       keyArray.Add(key);
00262     }
00263     nsMsgKey *staleHits;
00264     PRUint32 numBadHits;
00265     if (m_db)
00266     {
00267       nsresult rv = m_db->RefreshCache(searchUri, m_hdrHits.Count(), keyArray.GetArray(), &numBadHits, &staleHits);
00268       NS_ENSURE_SUCCESS(rv, rv);
00269       for (i = 0; i < numBadHits; i++)
00270       {
00271         nsMsgViewIndex staleHitIndex = FindKey(staleHits[i], PR_TRUE);
00272         if (staleHitIndex != nsMsgViewIndex_None)
00273           RemoveByIndex(staleHitIndex);
00274       }
00275       delete [] staleHits;
00276     }
00277   }
00278   if (m_sortType != nsMsgViewSortType::byThread)//we do not find levels for the results.
00279   {
00280     m_sortValid = PR_FALSE;       //sort the results 
00281     Sort(m_sortType, m_sortOrder);
00282   }
00283   if (m_viewFolder)
00284     SetMRUTimeForFolder(m_viewFolder);
00285 
00286   m_hdrHits.Clear();
00287   return NS_OK;
00288 }
00289 
00290 
00291 NS_IMETHODIMP
00292 nsMsgQuickSearchDBView::OnNewSearch()
00293 {
00294   PRInt32 oldSize = GetSize();
00295 
00296   m_keys.RemoveAll();
00297   m_levels.RemoveAll();
00298   m_flags.RemoveAll();
00299   m_hdrHits.Clear();
00300   // this needs to happen after we remove all the keys, since RowCountChanged() will call our GetRowCount()
00301   if (mTree)
00302     mTree->RowCountChanged(0, -oldSize);
00303   PRUint32 folderFlags = 0;
00304   if (m_viewFolder)
00305     m_viewFolder->GetFlags(&folderFlags);
00306   // check if it's a virtual folder - if so, we should get the cached hits 
00307   // from the db, and set a flag saying that we're using cached values.
00308   if (folderFlags & MSG_FOLDER_FLAG_VIRTUAL)
00309   {
00310     nsCOMPtr<nsISimpleEnumerator> cachedHits;
00311     nsXPIDLCString searchUri;
00312     m_viewFolder->GetURI(getter_Copies(searchUri));
00313     m_db->GetCachedHits(searchUri, getter_AddRefs(cachedHits));
00314     if (cachedHits)
00315     {
00316       PRBool hasMore;
00317 
00318       m_usingCachedHits = PR_TRUE;
00319       cachedHits->HasMoreElements(&hasMore);
00320       m_cacheEmpty = !hasMore;
00321       while (hasMore)
00322       {
00323         nsCOMPtr <nsIMsgDBHdr> pHeader;
00324         nsresult rv = cachedHits->GetNext(getter_AddRefs(pHeader));
00325         NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
00326         if (pHeader && NS_SUCCEEDED(rv))
00327           AddHdr(pHeader);
00328         else
00329           break;
00330         cachedHits->HasMoreElements(&hasMore);
00331       }
00332     }
00333   }
00334   return NS_OK;
00335 }
00336 
00337 nsresult nsMsgQuickSearchDBView::GetFirstMessageHdrToDisplayInThread(nsIMsgThread *threadHdr, nsIMsgDBHdr **result)
00338 {
00339   PRUint32 numChildren;
00340   nsresult rv = NS_OK;
00341   PRUint8 minLevel = 0xff;
00342   nsMsgKey threadRootKey;
00343 
00344   threadHdr->GetNumChildren(&numChildren);
00345   threadHdr->GetThreadKey(&threadRootKey);
00346   if ((PRInt32) numChildren < 0)
00347     numChildren = 0;
00348 
00349   nsCOMPtr <nsIMsgDBHdr> retHdr;
00350 
00351   // iterate over thread, finding mgsHdr in view with the lowest level.
00352   for (PRUint32 childIndex = 0; childIndex < numChildren; childIndex++)
00353   {
00354     nsCOMPtr <nsIMsgDBHdr> child;
00355     rv = threadHdr->GetChildHdrAt(childIndex, getter_AddRefs(child));
00356     if (NS_SUCCEEDED(rv) && child)
00357     {
00358       nsMsgKey msgKey;
00359       child->GetMessageKey(&msgKey);
00360 
00361       // this works because we've already sorted m_keys by id.
00362       nsMsgViewIndex keyIndex = m_origKeys.IndexOfSorted(msgKey);
00363       if (keyIndex != kNotFound)
00364       {
00365         // this is the root, so it's the best we're going to do.
00366         if (msgKey == threadRootKey)
00367         {
00368           retHdr = child;
00369           break;
00370         }
00371         PRUint8 level = 0;
00372         nsMsgKey parentId;
00373         child->GetThreadParent(&parentId);
00374         nsCOMPtr <nsIMsgDBHdr> parent;
00375         // count number of ancestors - that's our level
00376         while (parentId != nsMsgKey_None)
00377         {
00378           rv = m_db->GetMsgHdrForKey(parentId, getter_AddRefs(parent));
00379           if (parent)
00380           {
00381             nsMsgKey saveParentId = parentId;
00382             parent->GetThreadParent(&parentId);
00383             // message is it's own parent - bad, let's break out of here.
00384             if (parentId == saveParentId)
00385               break;
00386             level++;
00387           }
00388           else // if we can't find the parent, don't loop forever.
00389             break;
00390         }
00391         if (level < minLevel)
00392         {
00393           minLevel = level;
00394           retHdr = child;
00395         }
00396       }
00397     }
00398   }
00399   NS_IF_ADDREF(*result = retHdr);
00400   return NS_OK; 
00401 }
00402 
00403 nsresult nsMsgQuickSearchDBView::SortThreads(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder)
00404 {
00405   if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
00406     return NS_OK;
00407 
00408   // iterate over the messages in the view, getting the thread id's
00409   // sort m_keys so we can quickly find if a key is in the view. 
00410   m_keys.QuickSort();
00411   // array of the threads' root hdr keys.
00412   nsMsgKeyArray threadRootIds;
00413   nsCOMPtr <nsIMsgDBHdr> rootHdr;
00414   nsCOMPtr <nsIMsgDBHdr> msgHdr;
00415   nsCOMPtr <nsIMsgThread> threadHdr;
00416   for (PRUint32 i = 0; i < m_keys.GetSize(); i++)
00417   {
00418     GetMsgHdrForViewIndex(i, getter_AddRefs(msgHdr));
00419     m_db->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(threadHdr));
00420     if (threadHdr)
00421     {
00422       nsMsgKey rootKey;
00423       threadHdr->GetChildKeyAt(0, &rootKey);
00424       nsMsgViewIndex threadRootIndex = threadRootIds.IndexOfSorted(rootKey);
00425       // if we already have that id in top level threads, ignore this msg.
00426       if (threadRootIndex != kNotFound)
00427         continue;
00428       // it would be nice if GetInsertIndexHelper always found the hdr, but it doesn't.
00429       threadHdr->GetChildHdrAt(0, getter_AddRefs(rootHdr));
00430       threadRootIndex = GetInsertIndexHelper(rootHdr, &threadRootIds, nsMsgViewSortOrder::ascending, nsMsgViewSortType::byId);
00431       threadRootIds.InsertAt(threadRootIndex, rootKey);
00432     }
00433   }
00434   m_origKeys.CopyArray(m_keys);
00435   // need to sort the top level threads now by sort order, if it's not by id.
00436   if (sortType != nsMsgViewSortType::byId)
00437   {
00438     m_keys.CopyArray(threadRootIds);
00439     nsMsgDBView::Sort(sortType, sortOrder);
00440     threadRootIds.CopyArray(m_keys);
00441   }
00442   m_keys.RemoveAll();
00443   m_levels.RemoveAll();
00444   m_flags.RemoveAll();
00445   // now we've build up the list of thread ids - need to build the view
00446   // from that. So for each thread id, we need to list the messages in the thread.
00447   PRUint32 numThreads = threadRootIds.GetSize();
00448   for (PRUint32 threadIndex = 0; threadIndex < numThreads; threadIndex++)
00449   {
00450     m_db->GetMsgHdrForKey(threadRootIds[threadIndex], getter_AddRefs(rootHdr));
00451     if (rootHdr)
00452     {
00453       nsCOMPtr <nsIMsgDBHdr> displayRootHdr;
00454       m_db->GetThreadContainingMsgHdr(rootHdr, getter_AddRefs(threadHdr));
00455       if (threadHdr)
00456       {
00457         nsMsgKey rootKey;
00458         PRUint32 rootFlags;
00459         GetFirstMessageHdrToDisplayInThread(threadHdr, getter_AddRefs(displayRootHdr));
00460         if (!displayRootHdr)
00461           continue;
00462         displayRootHdr->GetMessageKey(&rootKey);
00463         displayRootHdr->GetFlags(&rootFlags);
00464         rootFlags |= MSG_VIEW_FLAG_ISTHREAD;
00465         m_keys.Add(rootKey);
00466         m_flags.Add(rootFlags);
00467         m_levels.Add(0);
00468 
00469         nsMsgViewIndex startOfThreadViewIndex = m_keys.GetSize() - 1;
00470         PRUint32 numListed;
00471         ListIdsInThread(threadHdr, startOfThreadViewIndex, &numListed);
00472       }
00473     }
00474   }
00475   NS_ASSERTION(m_origKeys.GetSize() == m_keys.GetSize(), "problem threading quick search");
00476   return NS_OK;
00477 }
00478 
00479 nsresult  nsMsgQuickSearchDBView::ListIdsInThread(nsIMsgThread *threadHdr, nsMsgViewIndex startOfThreadViewIndex, PRUint32 *pNumListed)
00480 {
00481   PRUint32 numChildren;
00482   threadHdr->GetNumChildren(&numChildren);
00483   PRUint32 i;
00484   PRUint32 viewIndex = startOfThreadViewIndex + 1;
00485   nsCOMPtr <nsIMsgDBHdr> rootHdr;
00486   nsMsgKey rootKey;
00487   PRUint32 rootFlags = m_flags[startOfThreadViewIndex];
00488   *pNumListed = 0;
00489   GetMsgHdrForViewIndex(startOfThreadViewIndex, getter_AddRefs(rootHdr));
00490   rootHdr->GetMessageKey(&rootKey);
00491   for (i = 0; i < numChildren; i++)
00492   {
00493     nsCOMPtr <nsIMsgDBHdr> msgHdr;
00494     threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr));
00495     if (msgHdr != nsnull)
00496     {
00497       nsMsgKey msgKey;
00498       msgHdr->GetMessageKey(&msgKey);
00499       if (msgKey != rootKey)
00500       {
00501         nsMsgViewIndex threadRootIndex = m_origKeys.IndexOfSorted(msgKey);
00502         // if this hdr is in the original view, add it to new view.
00503         if (threadRootIndex != kNotFound)
00504         {
00505           PRUint32 childFlags;
00506           msgHdr->GetFlags(&childFlags);
00507           PRUint8 levelToAdd;
00508           m_keys.InsertAt(viewIndex, msgKey);
00509           m_flags.InsertAt(viewIndex, childFlags);
00510           if (! (rootFlags & MSG_VIEW_FLAG_HASCHILDREN))
00511           {
00512             rootFlags |= MSG_VIEW_FLAG_HASCHILDREN;
00513             m_flags.SetAt(startOfThreadViewIndex, rootFlags);
00514           }
00515           levelToAdd = FindLevelInThread(msgHdr, startOfThreadViewIndex, viewIndex);
00516           m_levels.InsertAt(viewIndex, levelToAdd);
00517           viewIndex++;
00518           (*pNumListed)++;
00519         }
00520       }
00521     }
00522   }
00523   return NS_OK;
00524 }
00525 
00526 nsresult nsMsgQuickSearchDBView::ExpansionDelta(nsMsgViewIndex index, PRInt32 *expansionDelta)
00527 {
00528   *expansionDelta = 0;
00529   if ( index > ((nsMsgViewIndex) m_keys.GetSize()))
00530     return NS_MSG_MESSAGE_NOT_FOUND;
00531 
00532   char flags = m_flags[index];
00533 
00534   if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
00535     return NS_OK;
00536 
00537   // The client can pass in the key of any message
00538   // in a thread and get the expansion delta for the thread.
00539 
00540   PRInt32 numChildren = CountExpandedThread(index);
00541 
00542   *expansionDelta = (flags & MSG_FLAG_ELIDED) ? 
00543                     numChildren - 1 : - (PRInt32) (numChildren - 1);
00544   return NS_OK;
00545 }