Back to index

lightning-sunbird  0.9+nobinonly
nsMsgThreadedDBView.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  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either of the GNU General Public License Version 2 or later (the "GPL"),
00026  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 #include "msgCore.h"
00039 #include "nsMsgThreadedDBView.h"
00040 #include "nsIMsgHdr.h"
00041 #include "nsIMsgThread.h"
00042 #include "nsIDBFolderInfo.h"
00043 #include "nsIMsgSearchSession.h"
00044 
00045 #define MSGHDR_CACHE_LOOK_AHEAD_SIZE  25    // Allocate this more to avoid reallocation on new mail.
00046 #define MSGHDR_CACHE_MAX_SIZE         8192  // Max msghdr cache entries.
00047 #define MSGHDR_CACHE_DEFAULT_SIZE     100
00048 
00049 nsMsgThreadedDBView::nsMsgThreadedDBView()
00050 {
00051   /* member initializers and constructor code */
00052   m_havePrevView = PR_FALSE;
00053 }
00054 
00055 nsMsgThreadedDBView::~nsMsgThreadedDBView()
00056 {
00057   /* destructor code */
00058 }
00059 
00060 NS_IMETHODIMP nsMsgThreadedDBView::Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, nsMsgViewFlagsTypeValue viewFlags, PRInt32 *pCount)
00061 {
00062   nsresult rv = nsMsgDBView::Open(folder, sortType, sortOrder, viewFlags, pCount);
00063   NS_ENSURE_SUCCESS(rv, rv);
00064 
00065   if (!m_db)
00066     return NS_ERROR_NULL_POINTER;
00067   // Preset msg hdr cache size for performance reason.
00068   PRInt32 totalMessages, unreadMessages;
00069   nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
00070   PersistFolderInfo(getter_AddRefs(dbFolderInfo));
00071   NS_ENSURE_SUCCESS(rv, rv);
00072   // save off sort type and order, view type and flags
00073   dbFolderInfo->GetNumUnreadMessages(&unreadMessages);
00074   dbFolderInfo->GetNumMessages(&totalMessages);
00075   if (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly)
00076   { 
00077     // Set unread msg size + extra entries to avoid reallocation on new mail.
00078     totalMessages = (PRUint32)unreadMessages+MSGHDR_CACHE_LOOK_AHEAD_SIZE;  
00079   }
00080   else
00081   {
00082     if (totalMessages > MSGHDR_CACHE_MAX_SIZE) 
00083       totalMessages = MSGHDR_CACHE_MAX_SIZE;        // use max default
00084     else if (totalMessages > 0)
00085       totalMessages += MSGHDR_CACHE_LOOK_AHEAD_SIZE;// allocate extra entries to avoid reallocation on new mail.
00086   }
00087   // if total messages is 0, then we probably don't have any idea how many headers are in the db
00088   // so we have no business setting the cache size.
00089   if (totalMessages > 0)
00090     m_db->SetMsgHdrCacheSize((PRUint32)totalMessages);
00091   
00092   if (pCount)
00093     *pCount = 0;
00094   rv = InitThreadedView(pCount);
00095 
00096   // this is a hack, but we're trying to find a way to correct
00097   // incorrect total and unread msg counts w/o paying a big
00098   // performance penalty. So, if we're not threaded, just add
00099   // up the total and unread messages in the view and see if that
00100   // matches what the db totals say. Except ignored threads are
00101   // going to throw us off...hmm. Unless we just look at the
00102   // unread counts which is what mostly tweaks people anyway...
00103   PRInt32 unreadMsgsInView = 0;
00104   if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
00105   {
00106     for (PRInt32 i = 0; i < m_flags.GetSize(); i++)
00107     {
00108       if (! (m_flags.GetAt(i) & MSG_FLAG_READ))
00109         unreadMsgsInView++;
00110     }
00111     if (unreadMessages != unreadMsgsInView)
00112       m_db->SyncCounts();
00113   }
00114   m_db->SetMsgHdrCacheSize(MSGHDR_CACHE_DEFAULT_SIZE);
00115 
00116   return rv;
00117 }
00118 
00119 NS_IMETHODIMP nsMsgThreadedDBView::Close()
00120 {
00121   return nsMsgDBView::Close();
00122 }
00123 
00124 nsresult nsMsgThreadedDBView::InitThreadedView(PRInt32 *pCount)
00125 {
00126   nsresult rv;
00127   
00128   m_keys.RemoveAll();
00129   m_flags.RemoveAll();
00130   m_levels.RemoveAll(); 
00131   m_prevKeys.RemoveAll();
00132   m_prevFlags.RemoveAll();
00133   m_prevLevels.RemoveAll();
00134   m_havePrevView = PR_FALSE;
00135   nsresult getSortrv = NS_OK; // ### TODO m_db->GetSortInfo(&sortType, &sortOrder);
00136   
00137   // list all the ids into m_keys.
00138   nsMsgKey startMsg = 0; 
00139   do
00140   {
00141     const PRInt32 kIdChunkSize = 400;
00142     PRInt32                 numListed = 0;
00143     nsMsgKey  idArray[kIdChunkSize];
00144     PRInt32          flagArray[kIdChunkSize];
00145     char             levelArray[kIdChunkSize];
00146     
00147     rv = ListThreadIds(&startMsg, (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly) != 0, idArray, flagArray, 
00148       levelArray, kIdChunkSize, &numListed, nsnull);
00149     if (NS_SUCCEEDED(rv))
00150     {
00151       PRInt32 numAdded = AddKeys(idArray, flagArray, levelArray, m_sortType, numListed);
00152       if (pCount)
00153         *pCount += numAdded;
00154     }
00155     
00156   } while (NS_SUCCEEDED(rv) && startMsg != nsMsgKey_None);
00157   
00158   if (NS_SUCCEEDED(getSortrv))
00159   {
00160     rv = InitSort(m_sortType, m_sortOrder);
00161     SaveSortInfo(m_sortType, m_sortOrder);
00162 
00163   }
00164   return rv;
00165 }
00166 
00167 nsresult nsMsgThreadedDBView::SortThreads(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder)
00168 {
00169   NS_PRECONDITION(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay, "trying to sort unthreaded threads");
00170 
00171   PRUint32 numThreads = 0;
00172   // the idea here is that copy the current view,  then build up an m_keys and m_flags array of just the top level
00173   // messages in the view, and then call nsMsgDBView::Sort(sortType, sortOrder).
00174   // Then, we expand the threads in the result array that were expanded in the original view (perhaps by copying
00175   // from the original view, but more likely just be calling expand).
00176   for (PRUint32 i = 0; i < m_keys.GetSize(); i++)
00177   {
00178     if (m_flags[i] & MSG_VIEW_FLAG_ISTHREAD)
00179     {
00180       if (numThreads < i)
00181       {
00182         m_keys.SetAt(numThreads, m_keys[i]);
00183         m_flags[numThreads] = m_flags[i];
00184       }
00185       m_levels[numThreads] = 0;
00186       numThreads++;
00187     }
00188   }
00189   m_keys.SetSize(numThreads);
00190   m_flags.SetSize(numThreads);
00191   m_levels.SetSize(numThreads);
00192   //m_viewFlags &= ~nsMsgViewFlagsType::kThreadedDisplay;
00193   m_sortType = nsMsgViewSortType::byNone; // sort from scratch
00194   nsMsgDBView::Sort(sortType, sortOrder);
00195   m_viewFlags |= nsMsgViewFlagsType::kThreadedDisplay;
00196   DisableChangeUpdates();
00197   // Loop through the original array, for each thread that's expanded, find it in the new array
00198   // and expand the thread. We have to update MSG_VIEW_FLAG_HAS_CHILDREN because
00199   // we may be going from a flat sort, which doesn't maintain that flag,
00200   // to a threaded sort, which requires that flag.
00201   for (PRUint32 j = 0; j < m_keys.GetSize(); j++)
00202   {
00203     PRUint32 flags = m_flags[j];
00204     if ((flags & (MSG_VIEW_FLAG_HASCHILDREN | MSG_FLAG_ELIDED)) == MSG_VIEW_FLAG_HASCHILDREN)
00205     {
00206       PRUint32 numExpanded;
00207       m_flags[j] = flags | MSG_FLAG_ELIDED;
00208       ExpandByIndex(j, &numExpanded);
00209       j += numExpanded;
00210       if (numExpanded > 0)
00211         m_flags[j - numExpanded] = flags | MSG_VIEW_FLAG_HASCHILDREN;
00212     }
00213     else if (flags & MSG_VIEW_FLAG_ISTHREAD && ! (flags & MSG_VIEW_FLAG_HASCHILDREN))
00214     {
00215       nsCOMPtr <nsIMsgDBHdr> msgHdr;
00216       nsCOMPtr <nsIMsgThread> pThread;
00217       m_db->GetMsgHdrForKey(m_keys[j], getter_AddRefs(msgHdr));
00218       if (msgHdr)
00219       {
00220         m_db->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(pThread));
00221         if (pThread)
00222         {
00223           PRUint32 numChildren;
00224           pThread->GetNumChildren(&numChildren);
00225           if (numChildren > 1)
00226             m_flags[j] = flags | MSG_VIEW_FLAG_HASCHILDREN | MSG_FLAG_ELIDED;
00227         }
00228       }
00229     }
00230   }
00231   EnableChangeUpdates();
00232 
00233   return NS_OK;
00234 }
00235 
00236 nsresult nsMsgThreadedDBView::AddKeys(nsMsgKey *pKeys, PRInt32 *pFlags, const char *pLevels, nsMsgViewSortTypeValue sortType, PRInt32 numKeysToAdd)
00237 
00238 {
00239   PRInt32     numAdded = 0;
00240   // Allocate enough space first to avoid memory allocation/deallocation.
00241   m_keys.AllocateSpace(numKeysToAdd+m_keys.GetSize());
00242   m_flags.AllocateSpace(numKeysToAdd+m_flags.GetSize());
00243   m_levels.AllocateSpace(numKeysToAdd+m_levels.GetSize());
00244   for (PRInt32 i = 0; i < numKeysToAdd; i++)
00245   {
00246     PRInt32 threadFlag = pFlags[i];
00247     PRInt32 flag = threadFlag;
00248     
00249     // skip ignored threads.
00250     if ((threadFlag & MSG_FLAG_IGNORED) && !(m_viewFlags & nsMsgViewFlagsType::kShowIgnored))
00251       continue;
00252     // by default, make threads collapsed, unless we're in only viewing new msgs
00253     
00254     if (flag & MSG_VIEW_FLAG_HASCHILDREN)
00255       flag |= MSG_FLAG_ELIDED;
00256     // should this be persistent? Doesn't seem to need to be.
00257     flag |= MSG_VIEW_FLAG_ISTHREAD;
00258     m_keys.Add(pKeys[i]);
00259     m_flags.Add(flag);
00260     m_levels.Add(pLevels[i]);
00261     numAdded++;
00262     // we expand as we build the view, which allows us to insert at the end of the key array,
00263     // instead of the middle, and is much faster.
00264     if ((!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) || m_viewFlags & nsMsgViewFlagsType::kExpandAll) && flag & MSG_FLAG_ELIDED)
00265        ExpandByIndex(m_keys.GetSize() - 1, NULL);
00266   }
00267   return numAdded;
00268 }
00269 
00270 NS_IMETHODIMP nsMsgThreadedDBView::Sort(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder)
00271 {
00272   nsresult rv;
00273 
00274   PRInt32 rowCountBeforeSort = GetSize();
00275 
00276   if (!rowCountBeforeSort) 
00277   {
00278     // still need to setup our flags even when no articles - bug 98183.
00279     m_sortType = sortType;
00280     if (sortType == nsMsgViewSortType::byThread && ! (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
00281       SetViewFlags(m_viewFlags | nsMsgViewFlagsType::kThreadedDisplay);
00282     SaveSortInfo(sortType, sortOrder);
00283     return NS_OK;
00284   }
00285 
00286   // sort threads by sort order
00287   PRBool sortThreads = m_viewFlags & (nsMsgViewFlagsType::kThreadedDisplay | nsMsgViewFlagsType::kGroupBySort);
00288   
00289   // if sort type is by thread, and we're already threaded, change sort type to byId
00290   if (sortType == nsMsgViewSortType::byThread && (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) != 0)
00291     sortType = nsMsgViewSortType::byId;
00292 
00293   nsMsgKey preservedKey;
00294   nsMsgKeyArray preservedSelection;
00295   SaveAndClearSelection(&preservedKey, &preservedSelection);
00296   // if the client wants us to forget our cached id arrays, they
00297   // should build a new view. If this isn't good enough, we
00298   // need a method to do that.
00299   if (sortType != m_sortType || !m_sortValid || sortThreads)
00300   {
00301     SaveSortInfo(sortType, sortOrder);
00302     if (sortType == nsMsgViewSortType::byThread)  
00303     {
00304       m_sortType = sortType;
00305       m_viewFlags |= nsMsgViewFlagsType::kThreadedDisplay;
00306       m_viewFlags &= nsMsgViewFlagsType::kGroupBySort;
00307       if ( m_havePrevView)
00308       {
00309         // restore saved id array and flags array
00310         m_keys.RemoveAll();
00311         m_keys.InsertAt(0, &m_prevKeys);
00312         m_flags.RemoveAll();
00313         m_flags.InsertAt(0, &m_prevFlags);
00314         m_levels.RemoveAll();
00315         m_levels.InsertAt(0, &m_prevLevels);
00316         m_sortValid = PR_TRUE;
00317         
00318         // the sort may have changed the number of rows
00319         // before we restore the selection, tell the tree
00320         // do this before we call restore selection
00321         // this is safe when there is no selection.
00322         rv = AdjustRowCount(rowCountBeforeSort, GetSize());
00323         
00324         RestoreSelection(preservedKey, &preservedSelection);
00325         if (mTree) mTree->Invalidate();
00326         return NS_OK;
00327       }
00328       else
00329       {
00330         // set sort info in anticipation of what Init will do.
00331         InitThreadedView(nsnull);  // build up thread list.
00332         if (sortOrder != nsMsgViewSortOrder::ascending)
00333           Sort(sortType, sortOrder);
00334         
00335         // the sort may have changed the number of rows
00336         // before we update the selection, tell the tree
00337         // do this before we call restore selection
00338         // this is safe when there is no selection.
00339         rv = AdjustRowCount(rowCountBeforeSort, GetSize());
00340         
00341         RestoreSelection(preservedKey, &preservedSelection);
00342         if (mTree) mTree->Invalidate();
00343         return NS_OK;
00344       }
00345     }
00346     else if (sortType  != nsMsgViewSortType::byThread && (m_sortType == nsMsgViewSortType::byThread  || sortThreads)/* && !m_havePrevView*/)
00347     {
00348       if (sortThreads)
00349       {
00350         SortThreads(sortType, sortOrder);
00351         sortType = nsMsgViewSortType::byThread; // hack so base class won't do anything
00352       }
00353       else
00354       {
00355         // going from SortByThread to non-thread sort - must build new key, level,and flags arrays 
00356         m_prevKeys.RemoveAll();
00357         m_prevKeys.InsertAt(0, &m_keys);
00358         m_prevFlags.RemoveAll();
00359         m_prevFlags.InsertAt(0, &m_flags);
00360         m_prevLevels.RemoveAll();
00361         m_prevLevels.InsertAt(0, &m_levels);
00362         // do this before we sort, so that we'll use the cheap method
00363         // of expanding.
00364         m_viewFlags &= ~(nsMsgViewFlagsType::kThreadedDisplay | nsMsgViewFlagsType::kGroupBySort);
00365         ExpandAll();
00366         //                  m_idArray.RemoveAll();
00367         //                  m_flags.RemoveAll();
00368         m_havePrevView = PR_TRUE;
00369       }
00370     }
00371   }
00372   else if (m_sortOrder != sortOrder)// check for toggling the sort
00373   {
00374     nsMsgDBView::Sort(sortType, sortOrder);
00375   }
00376   if (!sortThreads)
00377   {
00378     // call the base class in case we're not sorting by thread
00379     rv = nsMsgDBView::Sort(sortType, sortOrder);
00380     SaveSortInfo(sortType, sortOrder);
00381   }
00382   // the sort may have changed the number of rows
00383   // before we restore the selection, tell the tree
00384   // do this before we call restore selection
00385   // this is safe when there is no selection.
00386   rv = AdjustRowCount(rowCountBeforeSort, GetSize());
00387 
00388   RestoreSelection(preservedKey, &preservedSelection);
00389   if (mTree) mTree->Invalidate();
00390   NS_ENSURE_SUCCESS(rv,rv);
00391   return NS_OK;
00392 }
00393 
00394 // list the ids of the top-level thread ids starting at id == startMsg. This actually returns
00395 // the ids of the first message in each thread.
00396 nsresult nsMsgThreadedDBView::ListThreadIds(nsMsgKey *startMsg, PRBool unreadOnly, nsMsgKey *pOutput, PRInt32 *pFlags, char *pLevels, 
00397                                                                 PRInt32 numToList, PRInt32 *pNumListed, PRInt32 *pTotalHeaders)
00398 {
00399   nsresult rv = NS_OK;
00400   // N.B..don't ret before assigning numListed to *pNumListed
00401   PRInt32     numListed = 0;
00402   
00403   if (*startMsg > 0)
00404   {
00405     NS_ASSERTION(m_threadEnumerator != nsnull, "where's our iterator?");     // for now, we'll just have to rely on the caller leaving
00406     // the iterator in the right place.
00407   }
00408   else
00409   {
00410     NS_ASSERTION(m_db, "no db");
00411     if (!m_db) return NS_ERROR_UNEXPECTED;
00412     rv = m_db->EnumerateThreads(getter_AddRefs(m_threadEnumerator));
00413     NS_ENSURE_SUCCESS(rv, rv);
00414   }
00415   
00416   PRBool hasMore = PR_FALSE;
00417   
00418   nsCOMPtr <nsIMsgThread> threadHdr ;
00419   PRInt32     threadsRemoved = 0;
00420   for (numListed = 0; numListed < numToList
00421     && NS_SUCCEEDED(rv = m_threadEnumerator->HasMoreElements(&hasMore)) && (hasMore == PR_TRUE);)
00422   {
00423     nsCOMPtr <nsISupports> supports;
00424     rv = m_threadEnumerator->GetNext(getter_AddRefs(supports));
00425     if (NS_FAILED(rv))
00426     {
00427       threadHdr = nsnull;
00428       break;
00429     }
00430     threadHdr = do_QueryInterface(supports);
00431     if (!threadHdr)
00432       break;
00433     nsCOMPtr <nsIMsgDBHdr> msgHdr;
00434     PRUint32 numChildren;
00435     if (unreadOnly)
00436       threadHdr->GetNumUnreadChildren(&numChildren);
00437     else
00438       threadHdr->GetNumChildren(&numChildren);
00439     PRUint32 threadFlags;
00440     threadHdr->GetFlags(&threadFlags);
00441     if (numChildren != 0)   // not empty thread
00442     {
00443       PRInt32 unusedRootIndex;
00444       if (pTotalHeaders)
00445         *pTotalHeaders += numChildren;
00446       if (unreadOnly)
00447         rv = threadHdr->GetFirstUnreadChild(getter_AddRefs(msgHdr));
00448       else
00449         rv = threadHdr->GetRootHdr(&unusedRootIndex, getter_AddRefs(msgHdr));
00450       if (NS_SUCCEEDED(rv) && msgHdr != nsnull && WantsThisThread(threadHdr))
00451       {
00452         PRUint32 msgFlags;
00453         PRUint32 newMsgFlags;
00454         nsMsgKey msgKey;
00455         msgHdr->GetMessageKey(&msgKey);
00456         msgHdr->GetFlags(&msgFlags);
00457         // turn off high byte of msg flags - used for view flags.
00458         msgFlags &= ~MSG_VIEW_FLAGS;
00459         pOutput[numListed] = msgKey;
00460         pLevels[numListed] = 0;
00461         // turn off these flags on msg hdr - they belong in thread
00462         msgHdr->AndFlags(~(MSG_FLAG_WATCHED | MSG_FLAG_IGNORED), &newMsgFlags);
00463         AdjustReadFlag(msgHdr, &msgFlags);
00464         // try adding in MSG_VIEW_FLAG_ISTHREAD flag for unreadonly view.
00465         pFlags[numListed] = msgFlags | MSG_VIEW_FLAG_ISTHREAD | threadFlags;
00466         if (numChildren > 1)
00467           pFlags[numListed] |= MSG_VIEW_FLAG_HASCHILDREN;
00468         
00469         numListed++;
00470       }
00471       else
00472         NS_ASSERTION(NS_SUCCEEDED(rv) && msgHdr, "couldn't get header for some reason");
00473     }
00474     else if (threadsRemoved < 10 && !(threadFlags & (MSG_FLAG_WATCHED | MSG_FLAG_IGNORED)))
00475     {
00476       // ### remove thread.
00477       threadsRemoved++;     // don't want to remove all empty threads first time
00478       // around as it will choke preformance for upgrade.
00479 #ifdef DEBUG_bienvenu
00480       printf("removing empty non-ignored non-watched thread\n");
00481 #endif
00482     }
00483   }
00484   
00485   if (hasMore && threadHdr)
00486   {
00487     threadHdr->GetThreadKey(startMsg);
00488   }
00489   else
00490   {
00491     *startMsg = nsMsgKey_None;
00492     nsCOMPtr <nsIDBChangeListener> dbListener = do_QueryInterface(m_threadEnumerator);
00493     // this is needed to make the thread enumerator release its reference to the db.
00494     if (dbListener)
00495       dbListener->OnAnnouncerGoingAway(nsnull);
00496     m_threadEnumerator = nsnull;
00497   }
00498   *pNumListed = numListed;
00499   return rv;
00500 }
00501 
00502 void   nsMsgThreadedDBView::OnExtraFlagChanged(nsMsgViewIndex index, PRUint32 extraFlag)
00503 {
00504   if (IsValidIndex(index))
00505   {
00506     if (m_havePrevView)
00507     {
00508       nsMsgKey keyChanged = m_keys[index];
00509       nsMsgViewIndex prevViewIndex = m_prevKeys.FindIndex(keyChanged);
00510       if (prevViewIndex != nsMsgViewIndex_None)
00511       {
00512         PRUint32 prevFlag = m_prevFlags.GetAt(prevViewIndex);
00513         // don't want to change the elided bit, or has children or is thread
00514         if (prevFlag & MSG_FLAG_ELIDED)
00515           extraFlag |= MSG_FLAG_ELIDED;
00516         else
00517           extraFlag &= ~MSG_FLAG_ELIDED;
00518         if (prevFlag & MSG_VIEW_FLAG_ISTHREAD)
00519           extraFlag |= MSG_VIEW_FLAG_ISTHREAD;
00520         else
00521           extraFlag &= ~MSG_VIEW_FLAG_ISTHREAD;
00522         if (prevFlag & MSG_VIEW_FLAG_HASCHILDREN)
00523           extraFlag |= MSG_VIEW_FLAG_HASCHILDREN;
00524         else
00525           extraFlag &= ~MSG_VIEW_FLAG_HASCHILDREN;
00526         m_prevFlags.SetAt(prevViewIndex, extraFlag);    // will this be right?
00527       }
00528     }
00529   }
00530   // we don't really know what's changed, but to be on the safe side, set the sort invalid
00531   // so that reverse sort will pick it up.
00532   if (m_sortType == nsMsgViewSortType::byStatus || m_sortType == nsMsgViewSortType::byFlagged || 
00533     m_sortType == nsMsgViewSortType::byUnread || m_sortType == nsMsgViewSortType::byPriority)
00534     m_sortValid = PR_FALSE;
00535 }
00536 
00537 void nsMsgThreadedDBView::OnHeaderAddedOrDeleted()
00538 {
00539   ClearPrevIdArray();
00540 }
00541 
00542 void nsMsgThreadedDBView::ClearPrevIdArray()
00543 {
00544   m_prevKeys.RemoveAll();
00545   m_prevLevels.RemoveAll();
00546   m_prevFlags.RemoveAll();
00547   m_havePrevView = PR_FALSE;
00548 }
00549 
00550 nsresult nsMsgThreadedDBView::InitSort(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder)
00551 {
00552   if (sortType == nsMsgViewSortType::byThread)
00553   {
00554     nsMsgDBView::Sort(nsMsgViewSortType::byId, sortOrder); // sort top level threads by id.
00555     m_sortType = nsMsgViewSortType::byThread;
00556     m_viewFlags |= nsMsgViewFlagsType::kThreadedDisplay;
00557     m_viewFlags &= ~nsMsgViewFlagsType::kGroupBySort;
00558     SetViewFlags(m_viewFlags); // persist the view flags.
00559     //        m_db->SetSortInfo(m_sortType, sortOrder);
00560   }
00561 //  else
00562 //    m_viewFlags &= ~nsMsgViewFlagsType::kThreadedDisplay;
00563   
00564   // by default, the unread only view should have all threads expanded.
00565   if ((m_viewFlags & (nsMsgViewFlagsType::kUnreadOnly|nsMsgViewFlagsType::kExpandAll)) 
00566       && (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
00567     ExpandAll();
00568   if (! (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
00569     ExpandAll(); // for now, expand all and do a flat sort.
00570   
00571   Sort(sortType, sortOrder);
00572   if (sortType != nsMsgViewSortType::byThread)   // forget prev view, since it has everything expanded.
00573     ClearPrevIdArray();
00574   return NS_OK;
00575 }
00576 
00577 nsresult nsMsgThreadedDBView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, PRBool ensureListed)
00578 {
00579   nsresult rv = NS_OK;
00580   nsMsgKey newKey;
00581   newHdr->GetMessageKey(&newKey);
00582 
00583   // views can override this behaviour, which is to append to view.
00584   // This is the mail behaviour, but threaded views want
00585   // to insert in order...
00586   if (newHdr)
00587   {
00588     PRUint32 msgFlags;
00589     newHdr->GetFlags(&msgFlags);
00590     if ((m_viewFlags & nsMsgViewFlagsType::kUnreadOnly) && !ensureListed && (msgFlags & MSG_FLAG_READ))
00591       return NS_OK;
00592     // Currently, we only add the header in a threaded view if it's a thread.
00593     // We used to check if this was the first header in the thread, but that's
00594     // a bit harder in the unreadOnly view. But we'll catch it below.
00595 
00596     // for search view we don't support threaded display so just add it to the view.   
00597     if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) // || msgHdr->GetMessageKey() == m_messageDB->GetKeyOfFirstMsgInThread(msgHdr->GetMessageKey()))
00598       rv = AddHdr(newHdr);
00599     else      // need to find the thread we added this to so we can change the hasnew flag
00600       // added message to existing thread, but not to view
00601     {  // Fix flags on thread header.
00602       PRInt32 threadCount;
00603       PRUint32 threadFlags;
00604       nsMsgViewIndex threadIndex = ThreadIndexOfMsg(newKey, nsMsgViewIndex_None, &threadCount, &threadFlags);
00605       if (threadIndex != nsMsgViewIndex_None)
00606       {
00607         PRUint32     flags = m_flags[threadIndex];
00608         if (!(flags & MSG_VIEW_FLAG_HASCHILDREN))
00609         {
00610           flags |= MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_ISTHREAD;
00611           if (!(m_viewFlags & nsMsgViewFlagsType::kUnreadOnly))
00612             flags |= MSG_FLAG_ELIDED;
00613           m_flags[threadIndex] = flags;
00614         }
00615         if (!(flags & MSG_FLAG_ELIDED))   // thread is expanded
00616         {                                                      // insert child into thread
00617           // levels of other hdrs may have changed!
00618           PRUint32   newFlags = msgFlags;
00619           PRInt32 level = 0;
00620           nsMsgViewIndex insertIndex = threadIndex;
00621           if (aParentKey == nsMsgKey_None)
00622           {
00623             newFlags |= MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN;
00624           }
00625           else
00626           {
00627             nsMsgViewIndex parentIndex = FindParentInThread(aParentKey, threadIndex);
00628             level = m_levels[parentIndex] + 1;
00629             insertIndex = GetInsertInfoForNewHdr(newHdr, parentIndex, level);
00630           }
00631           m_keys.InsertAt(insertIndex, newKey);
00632           m_flags.InsertAt(insertIndex, newFlags, 1);
00633           m_levels.InsertAt(insertIndex, level);
00634 
00635           // the call to NoteChange() has to happen after we add the key
00636           // as NoteChange() will call RowCountChanged() which will call our GetRowCount()
00637           NoteChange(insertIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
00638 
00639           if (aParentKey == nsMsgKey_None)
00640           {
00641             // this header is the new king! try collapsing the existing thread,
00642             // removing it, installing this header as king, and expanding it.
00643             CollapseByIndex(threadIndex, nsnull);
00644             // call base class, so child won't get promoted.
00645             // nsMsgDBView::RemoveByIndex(threadIndex); 
00646             ExpandByIndex(threadIndex, nsnull);
00647           }
00648         }
00649         else if (aParentKey == nsMsgKey_None)
00650         {
00651           // if we have a collapsed thread which just got a new
00652           // top of thread, change the keys array.
00653           m_keys.SetAt(threadIndex, newKey);
00654         }
00655         // note change, to update the parent thread's unread and total counts
00656         NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
00657       }
00658       else // adding msg to thread that's not in view.
00659       {
00660         nsCOMPtr <nsIMsgThread> threadHdr;
00661         m_db->GetThreadContainingMsgHdr(newHdr, getter_AddRefs(threadHdr));
00662         if (threadHdr)
00663         {
00664           AddMsgToThreadNotInView(threadHdr, newHdr, ensureListed);
00665         }
00666       }
00667     }
00668   }
00669   else
00670     rv = NS_MSG_MESSAGE_NOT_FOUND;
00671   return rv;
00672 }
00673 
00674 
00675 NS_IMETHODIMP nsMsgThreadedDBView::OnParentChanged (nsMsgKey aKeyChanged, nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeListener *aInstigator)
00676 {
00677   // we need to adjust the level of the hdr whose parent changed, and invalidate that row,
00678   // iff we're in threaded mode.
00679   if (PR_FALSE && m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
00680   {
00681     nsMsgViewIndex childIndex = FindViewIndex(aKeyChanged);
00682     if (childIndex != nsMsgViewIndex_None)
00683     {
00684       nsMsgViewIndex parentIndex = FindViewIndex(newParent);
00685       PRInt32 newParentLevel = (parentIndex == nsMsgViewIndex_None) ? -1 : m_levels[parentIndex];
00686       nsMsgViewIndex oldParentIndex = FindViewIndex(oldParent);
00687       PRInt32 oldParentLevel = (oldParentIndex != nsMsgViewIndex_None || newParent == nsMsgKey_None) 
00688         ? m_levels[oldParentIndex] : -1 ;
00689       PRInt32 levelChanged = m_levels[childIndex];
00690       PRInt32 parentDelta = oldParentLevel - newParentLevel;
00691       m_levels[childIndex] = (newParent == nsMsgKey_None) ? 0 : newParentLevel + 1;
00692       if (parentDelta > 0)
00693       {
00694         for (nsMsgViewIndex viewIndex = childIndex + 1; viewIndex < GetSize() && m_levels[viewIndex] > levelChanged;  viewIndex++)
00695         {
00696           m_levels[viewIndex] = m_levels[viewIndex] - parentDelta;
00697           NoteChange(viewIndex, 1, nsMsgViewNotificationCode::changed);
00698         }
00699       }
00700       NoteChange(childIndex, 1, nsMsgViewNotificationCode::changed);
00701     }
00702   }
00703   return NS_OK;
00704 }
00705 
00706 
00707 nsMsgViewIndex nsMsgThreadedDBView::GetInsertInfoForNewHdr(nsIMsgDBHdr *newHdr, nsMsgViewIndex parentIndex, PRInt32 targetLevel)
00708 {
00709   PRUint32 viewSize = GetSize();
00710   while (++parentIndex < viewSize)
00711   {
00712     // loop until we find a message at a level less than or equal to the parent level
00713     if (m_levels[parentIndex] < targetLevel)
00714       break;
00715   }
00716   return parentIndex;
00717 }
00718 
00719 nsresult nsMsgThreadedDBView::AddMsgToThreadNotInView(nsIMsgThread *threadHdr, nsIMsgDBHdr *msgHdr, PRBool ensureListed)
00720 {
00721   nsresult rv = NS_OK;
00722   PRUint32 threadFlags;
00723   threadHdr->GetFlags(&threadFlags);
00724   if (!(threadFlags & MSG_FLAG_IGNORED))
00725     rv = AddHdr(msgHdr);
00726   return rv;
00727 }
00728 
00729 // This method just removes the specified line from the view. It does
00730 // NOT delete it from the database.
00731 nsresult nsMsgThreadedDBView::RemoveByIndex(nsMsgViewIndex index)
00732 {
00733   nsresult rv = NS_OK;
00734   PRInt32     flags;
00735   
00736   if (!IsValidIndex(index))
00737     return NS_MSG_INVALID_DBVIEW_INDEX;
00738   
00739   OnHeaderAddedOrDeleted();
00740   
00741   flags = m_flags[index];
00742   
00743   if (! (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) 
00744     return nsMsgDBView::RemoveByIndex(index);
00745   
00746   nsCOMPtr <nsIMsgThread> threadHdr; 
00747   GetThreadContainingIndex(index, getter_AddRefs(threadHdr));
00748   NS_ENSURE_SUCCESS(rv, rv);
00749   PRUint32 numThreadChildren = 0; // if we can't get thread, it's already deleted and thus has 0 children
00750   if (threadHdr)
00751     threadHdr->GetNumChildren(&numThreadChildren);
00752   // check if we're the top level msg in the thread, and we're not collapsed.
00753   if ((flags & MSG_VIEW_FLAG_ISTHREAD) && !(flags & MSG_FLAG_ELIDED) && (flags & MSG_VIEW_FLAG_HASCHILDREN))
00754   {
00755     // fix flags on thread header...Newly promoted message 
00756     // should have flags set correctly
00757     if (threadHdr)
00758     {
00759       nsMsgDBView::RemoveByIndex(index);
00760       nsCOMPtr <nsIMsgThread> nextThreadHdr;
00761       if (numThreadChildren > 0)
00762       {
00763         // unreadOnly
00764         nsCOMPtr <nsIMsgDBHdr> msgHdr;
00765         rv = threadHdr->GetChildHdrAt(0, getter_AddRefs(msgHdr));
00766         if (msgHdr != nsnull)
00767         {
00768           PRUint32 flag = 0;
00769           msgHdr->GetFlags(&flag);
00770           if (numThreadChildren > 1)
00771             flag |= MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN;
00772           m_flags.SetAtGrow(index, flag);
00773           m_levels.SetAtGrow(index, 0);
00774         }
00775       }
00776     }
00777     return rv;
00778   }
00779   else if (!(flags & MSG_VIEW_FLAG_ISTHREAD))
00780   {
00781     // we're not deleting the top level msg, but top level msg might be only msg in thread now
00782     if (threadHdr && numThreadChildren == 1) 
00783     {
00784       nsMsgKey msgKey;
00785       rv = threadHdr->GetChildKeyAt(0, &msgKey);
00786       if (NS_SUCCEEDED(rv))
00787       {
00788         nsMsgViewIndex threadIndex = FindViewIndex(msgKey);
00789         if (threadIndex != nsMsgViewIndex_None)
00790         {
00791           PRUint32 flags = m_flags[threadIndex];
00792           flags &= ~(MSG_VIEW_FLAG_ISTHREAD | MSG_FLAG_ELIDED | MSG_VIEW_FLAG_HASCHILDREN);
00793           m_flags[threadIndex] = flags;
00794           NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
00795         }
00796       }
00797       
00798     }
00799     
00800     return nsMsgDBView::RemoveByIndex(index);
00801   }
00802   // deleting collapsed thread header is special case. Child will be promoted,
00803   // so just tell FE that line changed, not that it was deleted
00804   if (threadHdr && numThreadChildren > 0) // header has aleady been deleted from thread
00805   {
00806     // change the id array and flags array to reflect the child header.
00807     // If we're not deleting the header, we want the second header,
00808     // Otherwise, the first one (which just got promoted).
00809     nsCOMPtr <nsIMsgDBHdr> msgHdr;
00810     rv = threadHdr->GetChildHdrAt(0, getter_AddRefs(msgHdr));
00811     if (msgHdr != nsnull)
00812     {
00813       nsMsgKey msgKey;
00814       msgHdr->GetMessageKey(&msgKey);
00815       m_keys.SetAt(index, msgKey);
00816       
00817       PRUint32 flag = 0;
00818       msgHdr->GetFlags(&flag);
00819       //                    CopyDBFlagsToExtraFlags(msgHdr->GetFlags(), &flag);
00820       //                    if (msgHdr->GetArticleNum() == msgHdr->GetThreadId())
00821       flag |= MSG_VIEW_FLAG_ISTHREAD;
00822       
00823       if (numThreadChildren == 1)  // if only hdr in thread (with one about to be deleted)
00824         // adjust flags.
00825       {
00826         flag &=  ~MSG_VIEW_FLAG_HASCHILDREN;
00827         flag &= ~MSG_FLAG_ELIDED;
00828         // tell FE that thread header needs to be repainted.
00829         NoteChange(index, 1, nsMsgViewNotificationCode::changed);     
00830       }
00831       else
00832       {
00833         flag |= MSG_VIEW_FLAG_HASCHILDREN;
00834         flag |= MSG_FLAG_ELIDED;
00835       }
00836       m_flags[index] = flag;
00837       mIndicesToNoteChange.RemoveElement(index);
00838     }
00839     else
00840       NS_ASSERTION(PR_FALSE, "couldn't find thread child");    
00841     NoteChange(index, 1, nsMsgViewNotificationCode::changed);  
00842   }
00843   else
00844     rv = nsMsgDBView::RemoveByIndex(index);
00845   return rv;
00846 }
00847 
00848 NS_IMETHODIMP nsMsgThreadedDBView::GetViewType(nsMsgViewTypeValue *aViewType)
00849 {
00850     NS_ENSURE_ARG_POINTER(aViewType);
00851     *aViewType = nsMsgViewType::eShowAllThreads; 
00852     return NS_OK;
00853 }
00854 
00855 NS_IMETHODIMP
00856 nsMsgThreadedDBView::CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater, nsIMsgDBView **_retval)
00857 {
00858   nsMsgThreadedDBView* newMsgDBView;
00859   NS_NEWXPCOM(newMsgDBView, nsMsgThreadedDBView);
00860 
00861   if (!newMsgDBView)
00862     return NS_ERROR_OUT_OF_MEMORY;
00863 
00864   nsresult rv = CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
00865   NS_ENSURE_SUCCESS(rv,rv);
00866 
00867   NS_IF_ADDREF(*_retval = newMsgDBView);
00868   return NS_OK;
00869 }
00870 
00871 NS_IMETHODIMP
00872 nsMsgThreadedDBView::GetSupportsThreading(PRBool *aResult)
00873 {
00874   NS_ENSURE_ARG_POINTER(aResult);
00875   *aResult = PR_TRUE;
00876   return NS_OK;
00877 }