Back to index

lightning-sunbird  0.9+nobinonly
nsMsgGroupView.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  * David Bienvenu.
00019  * Portions created by the Initial Developer are Copyright (C) 2004
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 "nsMsgGroupView.h"
00040 #include "nsIMsgHdr.h"
00041 #include "nsIMsgThread.h"
00042 #include "nsIDBFolderInfo.h"
00043 #include "nsIMsgSearchSession.h"
00044 #include "nsMsgGroupThread.h"
00045 #include "nsITreeColumns.h"
00046 
00047 #define MSGHDR_CACHE_LOOK_AHEAD_SIZE  25    // Allocate this more to avoid reallocation on new mail.
00048 #define MSGHDR_CACHE_MAX_SIZE         8192  // Max msghdr cache entries.
00049 #define MSGHDR_CACHE_DEFAULT_SIZE     100
00050 
00051 nsMsgGroupView::nsMsgGroupView()
00052 {
00053   m_dayChanged = PR_FALSE;
00054   m_lastCurExplodedTime.tm_mday = 0;
00055 }
00056 
00057 nsMsgGroupView::~nsMsgGroupView()
00058 {
00059 }
00060 
00061 NS_IMETHODIMP nsMsgGroupView::Open(nsIMsgFolder *aFolder, nsMsgViewSortTypeValue aSortType, nsMsgViewSortOrderValue aSortOrder, nsMsgViewFlagsTypeValue aViewFlags, PRInt32 *aCount)
00062 {
00063   nsresult rv = nsMsgDBView::Open(aFolder, aSortType, aSortOrder, aViewFlags, aCount);
00064   NS_ENSURE_SUCCESS(rv, rv);
00065 
00066   nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
00067   PersistFolderInfo(getter_AddRefs(dbFolderInfo));
00068 
00069   nsCOMPtr <nsISimpleEnumerator> headers;
00070   rv = m_db->EnumerateMessages(getter_AddRefs(headers));
00071   NS_ENSURE_SUCCESS(rv, rv);
00072 
00073   return OpenWithHdrs(headers, aSortType, aSortOrder, aViewFlags, aCount);
00074 }
00075 
00076 /* static */PRIntn PR_CALLBACK ReleaseThread (nsHashKey *aKey, void *thread, void *closure)
00077 {
00078   nsMsgGroupThread *groupThread = (nsMsgGroupThread *) thread;
00079   groupThread->Release();
00080   return kHashEnumerateNext;
00081 }
00082 
00083 void nsMsgGroupView::InternalClose()
00084 {
00085   if (m_db && m_sortType == nsMsgViewSortType::byDate)
00086   {
00087     nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
00088     nsresult rv = m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
00089     if (dbFolderInfo)
00090     {
00091       PRUint32 expandFlags = 0;
00092       PRUint32 num = GetSize();
00093          
00094       for (PRUint32 i = 0; i < num; i++) 
00095       {
00096         if (m_flags[i] & MSG_VIEW_FLAG_ISTHREAD && ! (m_flags[i] & MSG_FLAG_ELIDED))
00097         {
00098           nsCOMPtr <nsIMsgDBHdr> msgHdr;
00099           nsresult rv = GetMsgHdrForViewIndex(i, getter_AddRefs(msgHdr));
00100           if (msgHdr)
00101           {
00102              nsHashKey *hashKey = AllocHashKeyForHdr(msgHdr);
00103              if (hashKey)
00104                expandFlags |=  1 << ((nsPRUint32Key *)hashKey)->GetValue();
00105           }
00106         }
00107       }
00108       dbFolderInfo->SetUint32Property("dateGroupFlags", expandFlags);
00109     }
00110   }
00111   // enumerate m_groupsTable releasing the thread objects.
00112   m_groupsTable.Reset(ReleaseThread);
00113 }
00114 
00115 NS_IMETHODIMP nsMsgGroupView::Close()
00116 {
00117   InternalClose();
00118   return nsMsgThreadedDBView::Close();
00119 }
00120 
00121 nsHashKey *nsMsgGroupView::AllocHashKeyForHdr(nsIMsgDBHdr *msgHdr)
00122 {
00123   static nsXPIDLCString cStringKey;
00124   static nsXPIDLString stringKey;
00125   switch (m_sortType)
00126   {
00127     case nsMsgViewSortType::bySubject:
00128       (void) msgHdr->GetSubject(getter_Copies(cStringKey));
00129       return new nsCStringKey(cStringKey.get());
00130       break;
00131     case nsMsgViewSortType::byAuthor:
00132       (void) nsMsgDBView::FetchAuthor(msgHdr, getter_Copies(stringKey));
00133       return new nsStringKey(stringKey.get());
00134     case nsMsgViewSortType::byRecipient:
00135       (void) msgHdr->GetRecipients(getter_Copies(cStringKey));
00136       return new nsCStringKey(cStringKey.get());
00137     case nsMsgViewSortType::byAccount:
00138     case nsMsgViewSortType::byTags:
00139       {
00140         nsCOMPtr <nsIMsgDatabase> dbToUse = m_db;
00141   
00142         if (!dbToUse) // probably search view
00143           GetDBForViewIndex(0, getter_AddRefs(dbToUse));
00144 
00145         nsresult rv = (m_sortType == nsMsgViewSortType::byAccount) 
00146           ? FetchAccount(msgHdr, getter_Copies(stringKey))
00147           : FetchTags(msgHdr, getter_Copies(stringKey));
00148         return NS_SUCCEEDED(rv) ? new nsStringKey(stringKey.get()) : nsnull;
00149 
00150       }
00151       break;
00152     case nsMsgViewSortType::byAttachments:
00153       {
00154         PRUint32 flags;
00155         msgHdr->GetFlags(&flags);
00156         return new nsPRUint32Key(flags & MSG_FLAG_ATTACHMENT ? 1 : 0);
00157       }
00158     case nsMsgViewSortType::byFlagged:
00159       {
00160         PRUint32 flags;
00161         msgHdr->GetFlags(&flags);
00162         return new nsPRUint32Key(flags & MSG_FLAG_MARKED ? 1 : 0);
00163       }
00164     case nsMsgViewSortType::byPriority:
00165       {
00166         nsMsgPriorityValue priority;
00167         msgHdr->GetPriority(&priority);
00168         return new nsPRUint32Key(priority);
00169       }
00170       break;
00171     case nsMsgViewSortType::byStatus:
00172       {
00173         PRUint32 status = 0;
00174 
00175         GetStatusSortValue(msgHdr, &status);
00176         return new nsPRUint32Key(status);
00177       }
00178     case nsMsgViewSortType::byDate:
00179     {
00180       PRUint32 ageBucket = 1;
00181       PRTime dateOfMsg;
00182            
00183       nsresult rv = msgHdr->GetDate(&dateOfMsg);
00184 
00185       PRTime currentTime = PR_Now();
00186       PRExplodedTime currentExplodedTime;
00187       PR_ExplodeTime(currentTime, PR_LocalTimeParameters, &currentExplodedTime);
00188       PRExplodedTime explodedMsgTime;
00189       PR_ExplodeTime(dateOfMsg, PR_LocalTimeParameters, &explodedMsgTime);
00190 
00191       if (m_lastCurExplodedTime.tm_mday &&
00192          m_lastCurExplodedTime.tm_mday != currentExplodedTime.tm_mday)
00193         m_dayChanged = PR_TRUE; // this will cause us to rebuild the view.
00194 
00195       m_lastCurExplodedTime = currentExplodedTime;
00196       if (currentExplodedTime.tm_year == explodedMsgTime.tm_year &&
00197           currentExplodedTime.tm_month == explodedMsgTime.tm_month &&
00198           currentExplodedTime.tm_mday == explodedMsgTime.tm_mday)
00199       {
00200         // same day...
00201         ageBucket = 1;
00202       } 
00203       // figure out how many days ago this msg arrived
00204       else if (LL_CMP(currentTime, >, dateOfMsg))
00205       {
00206         // some constants for calculation
00207         static PRInt64 microSecondsPerSecond;
00208         static PRInt64 microSecondsPerDay;
00209        static PRInt64 secondsPerDay;
00210        static PRInt64 microSecondsPer6Days;
00211        static PRInt64 microSecondsPer13Days;
00212                      
00213         static PRBool bGotConstants = PR_FALSE;
00214         if ( !bGotConstants )
00215         {
00216           // seeds
00217           PRInt64 secondsPerDay;
00218 
00219           LL_I2L  ( microSecondsPerSecond,  PR_USEC_PER_SEC );
00220           LL_UI2L ( secondsPerDay,          60 * 60 * 24 );
00221     
00222           // derivees
00223           LL_MUL( microSecondsPerDay,   secondsPerDay,      microSecondsPerSecond );
00224           LL_MUL( microSecondsPer6Days, microSecondsPerDay, 6 );
00225          LL_MUL( microSecondsPer13Days, microSecondsPerDay, 13 );
00226          
00227          bGotConstants = PR_TRUE;
00228         }
00229 
00230        // setting the time variables to local time
00231         PRInt64 GMTLocalTimeShift;
00232         LL_ADD( GMTLocalTimeShift, currentExplodedTime.tm_params.tp_gmt_offset, currentExplodedTime.tm_params.tp_dst_offset );
00233         LL_MUL( GMTLocalTimeShift, GMTLocalTimeShift, microSecondsPerSecond );
00234         LL_ADD( currentTime, currentTime, GMTLocalTimeShift );
00235         LL_ADD( dateOfMsg, dateOfMsg, GMTLocalTimeShift );
00236        
00237        // the most recent midnight, counting from current time
00238        PRInt64 todaysMicroSeconds, mostRecentMidnight;
00239        LL_MOD( todaysMicroSeconds, currentTime, microSecondsPerDay );
00240        LL_SUB( mostRecentMidnight, currentTime, todaysMicroSeconds );
00241        PRInt64 yesterday;
00242        LL_SUB( yesterday, mostRecentMidnight, microSecondsPerDay );
00243        // most recent midnight minus 6 days
00244        PRInt64 mostRecentWeek;
00245        LL_SUB( mostRecentWeek, mostRecentMidnight, microSecondsPer6Days );
00246        
00247         // was the message sent yesterday?
00248         if ( LL_CMP( dateOfMsg, >=, yesterday ) )
00249         { // yes ....
00250           ageBucket = 2;
00251         }
00252         else if ( LL_CMP(dateOfMsg, >=, mostRecentWeek) )
00253         {
00254           ageBucket = 3;
00255         }
00256         else
00257         {
00258           PRInt64 lastTwoWeeks;
00259          LL_SUB( lastTwoWeeks, mostRecentMidnight, microSecondsPer13Days);
00260          ageBucket = LL_CMP(dateOfMsg, >=, lastTwoWeeks) ? 4 : 5;
00261         }
00262       }
00263       return new nsPRUint32Key(ageBucket);
00264     }
00265     default:
00266       NS_ASSERTION(PR_FALSE, "no hash key for this type");
00267     }
00268   return nsnull;
00269 }
00270 
00271 nsMsgGroupThread *nsMsgGroupView::AddHdrToThread(nsIMsgDBHdr *msgHdr, PRBool *pNewThread)
00272 {
00273   nsMsgKey msgKey;
00274   PRUint32 msgFlags;
00275   msgHdr->GetMessageKey(&msgKey);
00276   msgHdr->GetFlags(&msgFlags);
00277   nsHashKey *hashKey = AllocHashKeyForHdr(msgHdr);
00278 //  if (m_sortType == nsMsgViewSortType::byDate)
00279 //    msgKey = ((nsPRUint32Key *) hashKey)->GetValue();
00280   nsMsgGroupThread *foundThread = nsnull;
00281   if (hashKey)
00282     foundThread = (nsMsgGroupThread *) m_groupsTable.Get(hashKey);
00283   PRBool newThread = !foundThread;
00284   *pNewThread = newThread;
00285   nsMsgViewIndex viewIndexOfThread;
00286   if (!foundThread)
00287   {
00288     foundThread = new nsMsgGroupThread(m_db);
00289     m_groupsTable.Put(hashKey, foundThread);
00290     foundThread->AddRef();
00291     if (GroupViewUsesDummyRow())
00292     {
00293       foundThread->m_dummy = PR_TRUE;
00294       msgFlags |=  MSG_VIEW_FLAG_DUMMY | MSG_VIEW_FLAG_HASCHILDREN;
00295     }
00296 
00297     nsMsgViewIndex insertIndex = GetInsertIndex(msgHdr);
00298     if (insertIndex == nsMsgViewIndex_None)
00299       insertIndex = m_keys.GetSize();
00300     m_keys.InsertAt(insertIndex, msgKey);
00301     m_flags.InsertAt(insertIndex, msgFlags | MSG_VIEW_FLAG_ISTHREAD | MSG_FLAG_ELIDED);
00302     m_levels.InsertAt(insertIndex, 0, 1);
00303     // if grouped by date, insert dummy header for "age"
00304     if (GroupViewUsesDummyRow())
00305     {
00306       foundThread->m_keys.InsertAt(0, msgKey/* nsMsgKey_None */);
00307       foundThread->m_threadKey = ((nsPRUint32Key *) hashKey)->GetValue();
00308     }
00309   }
00310   else
00311   {
00312     viewIndexOfThread = GetIndexOfFirstDisplayedKeyInThread(foundThread);
00313   }
00314   delete hashKey;
00315   if (foundThread)
00316     foundThread->AddChildFromGroupView(msgHdr, this);
00317   // check if new hdr became thread root
00318   if (!newThread && foundThread->m_keys[0] == msgKey)
00319   {
00320     if (viewIndexOfThread != nsMsgKey_None)
00321       m_keys.SetAt(viewIndexOfThread, msgKey);
00322     if (GroupViewUsesDummyRow())
00323       foundThread->m_keys.SetAt(1, msgKey); // replace the old duplicate dummy header.
00324   }
00325 
00326   return foundThread;
00327 }
00328 
00329 NS_IMETHODIMP nsMsgGroupView::OpenWithHdrs(nsISimpleEnumerator *aHeaders, nsMsgViewSortTypeValue aSortType, 
00330                                         nsMsgViewSortOrderValue aSortOrder, nsMsgViewFlagsTypeValue aViewFlags, 
00331                                         PRInt32 *aCount)
00332 {
00333   nsresult rv = NS_OK;
00334 
00335   if (aSortType == nsMsgViewSortType::byThread || aSortType == nsMsgViewSortType::byId
00336     || aSortType == nsMsgViewSortType::byNone || aSortType == nsMsgViewSortType::bySize)
00337     return NS_ERROR_INVALID_ARG;
00338 
00339   m_sortType = aSortType;
00340   m_sortOrder = aSortOrder;
00341   m_viewFlags = aViewFlags | nsMsgViewFlagsType::kThreadedDisplay | nsMsgViewFlagsType::kGroupBySort;
00342 
00343   PRBool hasMore;
00344   nsCOMPtr <nsISupports> supports;
00345   nsCOMPtr <nsIMsgDBHdr> msgHdr;
00346   while (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv = aHeaders->HasMoreElements(&hasMore)) && hasMore)
00347   {
00348     nsXPIDLCString cStringKey;
00349     nsXPIDLString stringKey;
00350     rv = aHeaders->GetNext(getter_AddRefs(supports));
00351     if (NS_SUCCEEDED(rv) && supports)
00352     {
00353       PRBool notUsed;
00354       msgHdr = do_QueryInterface(supports);
00355       AddHdrToThread(msgHdr, &notUsed);
00356     }
00357   }
00358   PRUint32 expandFlags = 0;
00359   PRUint32 viewFlag = (m_sortType == nsMsgViewSortType::byDate) ? MSG_VIEW_FLAG_DUMMY : 0;
00360   if (viewFlag)
00361   {
00362     nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
00363     nsresult rv = m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
00364     if (dbFolderInfo)
00365       dbFolderInfo->GetUint32Property("dateGroupFlags",  0, &expandFlags);
00366 
00367   }
00368   // go through the view updating the flags for threads with more than one message...
00369   // and if grouped by date, expanding threads that were expanded before.
00370   for (PRUint32 viewIndex = 0; viewIndex < m_keys.GetSize(); viewIndex++)
00371   {
00372     nsCOMPtr <nsIMsgThread> thread;
00373     GetThreadContainingIndex(viewIndex, getter_AddRefs(thread));
00374     if (thread)
00375     {
00376       PRUint32 numChildren;
00377       thread->GetNumChildren(&numChildren);
00378       if (numChildren > 1 || viewFlag)
00379         OrExtraFlag(viewIndex, viewFlag | MSG_VIEW_FLAG_HASCHILDREN);
00380       if (expandFlags)
00381       {
00382         nsMsgGroupThread *groupThread = NS_STATIC_CAST(nsMsgGroupThread *, (nsIMsgThread *) thread);
00383         if (expandFlags & (1 << groupThread->m_threadKey))
00384         {
00385           PRUint32 numExpanded;
00386           ExpandByIndex(viewIndex, &numExpanded);
00387           viewIndex += numExpanded;
00388         }
00389       }
00390     }
00391   }
00392   *aCount = m_keys.GetSize();
00393   return rv;
00394 }
00395 
00396 // if the day has changed, we need to close and re-open the view.
00397 nsresult nsMsgGroupView::HandleDayChange()
00398 {
00399   nsCOMPtr <nsISimpleEnumerator> headers;
00400   if (NS_SUCCEEDED(m_db->EnumerateMessages(getter_AddRefs(headers))))
00401   {
00402     PRInt32 count;
00403     m_dayChanged = PR_FALSE;
00404     nsMsgKeyArray preservedSelection;
00405     nsMsgKey curSelectedKey;
00406     SaveAndClearSelection(&curSelectedKey, &preservedSelection);
00407     InternalClose();
00408     PRInt32 oldSize = GetSize();
00409     // this is important, because the tree will ask us for our
00410     // row count, which get determine from the number of keys.
00411     m_keys.RemoveAll();
00412     // be consistent
00413     m_flags.RemoveAll();
00414     m_levels.RemoveAll();
00415 
00416     // this needs to happen after we remove all the keys, since RowCountChanged() will call our GetRowCount()
00417     if (mTree) 
00418       mTree->RowCountChanged(0, -oldSize);
00419     DisableChangeUpdates();
00420     nsresult rv = OpenWithHdrs(headers, m_sortType, m_sortOrder, m_viewFlags, &count);
00421     EnableChangeUpdates();
00422     if (mTree) 
00423       mTree->RowCountChanged(0, GetSize());
00424 
00425     NS_ENSURE_SUCCESS(rv,rv);
00426 
00427     // now, restore our desired selection
00428     nsMsgKeyArray keyArray;
00429     keyArray.Add(curSelectedKey);
00430 
00431     return RestoreSelection(curSelectedKey, &keyArray);
00432   }
00433   return NS_OK;
00434 }
00435 
00436 nsresult nsMsgGroupView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, PRBool /*ensureListed*/)
00437 {
00438 
00439   // check if we're adding a header, and the current day has changed. If it has, we're just going to 
00440   // close and re-open the view so things will be correctly categorized.
00441   if (m_dayChanged)
00442     return HandleDayChange();
00443 
00444   PRBool newThread;
00445   nsMsgGroupThread *thread = AddHdrToThread(newHdr, &newThread); 
00446   if (thread)
00447   {
00448     nsMsgKey msgKey;
00449     PRUint32 msgFlags;
00450     newHdr->GetMessageKey(&msgKey);
00451     newHdr->GetFlags(&msgFlags);
00452 
00453     nsMsgViewIndex threadIndex = ThreadIndexOfMsg(msgKey);
00454     PRInt32 numRowsInserted = 1;
00455     if (newThread && GroupViewUsesDummyRow())
00456       numRowsInserted++;
00457     // may need to fix thread counts
00458     if (threadIndex != nsMsgViewIndex_None)
00459     {
00460       if (newThread)
00461         m_flags[threadIndex] &= ~MSG_FLAG_ELIDED;
00462       else
00463         m_flags[threadIndex] |= MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_ISTHREAD;
00464 
00465       PRInt32 numRowsToInvalidate = 1;
00466       if (! (m_flags[threadIndex] & MSG_FLAG_ELIDED))
00467       {
00468         PRUint32 msgIndexInThread = thread->m_keys.IndexOf(msgKey);
00469         PRBool insertedAtThreadRoot = !msgIndexInThread;
00470         if (!msgIndexInThread && GroupViewUsesDummyRow())
00471           msgIndexInThread++;
00472 
00473         if (!newThread || GroupViewUsesDummyRow())
00474         {
00475           // this msg is the new parent of an expanded thread. AddHdrToThread already
00476           // updated m_keys[threadIndex], so we need to insert the old parent as a child
00477           // and update m_flags accordingly.
00478           if (!newThread && (!msgIndexInThread || (msgIndexInThread == 1 && GroupViewUsesDummyRow())))
00479           {
00480             PRUint32 saveOldFlags = m_flags[threadIndex + msgIndexInThread] & ~(MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_ISTHREAD);
00481             if (!msgIndexInThread)
00482               msgFlags |= MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_ISTHREAD;
00483 
00484             m_flags[threadIndex + msgIndexInThread] = msgFlags;
00485             // this will cause us to insert the old header as the first child, with
00486             // the right key and flags.
00487             msgFlags = saveOldFlags; 
00488             msgIndexInThread++;
00489             msgKey = thread->m_keys[msgIndexInThread];
00490           }
00491 
00492           m_keys.InsertAt(threadIndex + msgIndexInThread, msgKey);
00493           m_flags.InsertAt(threadIndex + msgIndexInThread, msgFlags);
00494           if (msgIndexInThread > 0)
00495           {
00496             m_levels.InsertAt(threadIndex + msgIndexInThread, 1);
00497           }
00498           else // insert new header at level 0, and bump old level 0 to 1
00499           {
00500             m_levels.InsertAt(threadIndex, 0, 1);
00501             m_levels.SetAt(threadIndex + 1, 1);
00502           }
00503         }
00504         // the call to NoteChange() has to happen after we add the key
00505         // as NoteChange() will call RowCountChanged() which will call our GetRowCount()
00506         NoteChange((insertedAtThreadRoot && GroupViewUsesDummyRow()) ? threadIndex + msgIndexInThread - 1 : threadIndex + msgIndexInThread,
00507                       numRowsInserted, nsMsgViewNotificationCode::insertOrDelete);
00508         numRowsToInvalidate = msgIndexInThread;
00509       }
00510       NoteChange(threadIndex, numRowsToInvalidate, nsMsgViewNotificationCode::changed);
00511     }
00512   }
00513   // if thread is expanded, we need to add hdr to view...
00514   return NS_OK;
00515 }
00516 
00517 NS_IMETHODIMP nsMsgGroupView::OnHdrChange(nsIMsgDBHdr *aHdrChanged, PRUint32 aOldFlags, 
00518                                       PRUint32 aNewFlags, nsIDBChangeListener *aInstigator)
00519 {
00520   nsCOMPtr <nsIMsgThread> thread;
00521 
00522   // check if we're adding a header, and the current day has changed. If it has, we're just going to 
00523   // close and re-open the view so things will be correctly categorized.
00524   if (m_dayChanged)
00525     return HandleDayChange();
00526 
00527   nsresult rv = GetThreadContainingMsgHdr(aHdrChanged, getter_AddRefs(thread));
00528   NS_ENSURE_SUCCESS(rv, rv);
00529   PRUint32 deltaFlags = (aOldFlags ^ aNewFlags);
00530   if (deltaFlags & MSG_FLAG_READ)
00531     thread->MarkChildRead(aNewFlags & MSG_FLAG_READ);
00532 
00533   return nsMsgDBView::OnHdrChange(aHdrChanged, aOldFlags, aNewFlags, aInstigator);
00534 }
00535 
00536 NS_IMETHODIMP nsMsgGroupView::OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted, nsMsgKey aParentKey, PRInt32 aFlags, 
00537                             nsIDBChangeListener *aInstigator)
00538 {
00539   // check if we're adding a header, and the current day has changed. If it has, we're just going to 
00540   // close and re-open the view so things will be correctly categorized.
00541   if (m_dayChanged)
00542     return HandleDayChange();
00543 
00544   nsCOMPtr <nsIMsgThread> thread;
00545   nsMsgKey keyDeleted;
00546   aHdrDeleted->GetMessageKey(&keyDeleted);
00547 
00548   nsresult rv = GetThreadContainingMsgHdr(aHdrDeleted, getter_AddRefs(thread));
00549   NS_ENSURE_SUCCESS(rv, rv);
00550   nsMsgViewIndex viewIndexOfThread = GetIndexOfFirstDisplayedKeyInThread(thread);
00551   thread->RemoveChildHdr(aHdrDeleted, nsnull);
00552 
00553   nsMsgGroupThread *groupThread = NS_STATIC_CAST(nsMsgGroupThread *, (nsIMsgThread *) thread);
00554 
00555   PRBool rootDeleted = viewIndexOfThread != nsMsgKey_None &&
00556     m_keys.GetAt(viewIndexOfThread) == keyDeleted;
00557   rv = nsMsgDBView::OnHdrDeleted(aHdrDeleted, aParentKey, aFlags, aInstigator);
00558   if (groupThread->m_dummy)
00559   {
00560     if (!groupThread->NumRealChildren())
00561     {
00562       thread->RemoveChildAt(0); // get rid of dummy
00563       if (viewIndexOfThread != nsMsgKey_None)
00564       {
00565         nsMsgDBView::RemoveByIndex(viewIndexOfThread - 1);
00566         if (m_deletingRows)
00567           mIndicesToNoteChange.Add(viewIndexOfThread - 1);
00568       }
00569     }
00570     else if (rootDeleted && viewIndexOfThread > 0)
00571     {
00572       m_keys.SetAt(viewIndexOfThread - 1, m_keys.GetAt(viewIndexOfThread));
00573       OrExtraFlag(viewIndexOfThread - 1, MSG_VIEW_FLAG_DUMMY | MSG_VIEW_FLAG_ISTHREAD);
00574     }
00575   }
00576   if (!groupThread->m_keys.GetSize())
00577   {
00578     nsHashKey *hashKey = AllocHashKeyForHdr(aHdrDeleted);
00579     if (hashKey)
00580       m_groupsTable.Remove(hashKey);
00581     delete hashKey;
00582   }
00583   return rv;
00584 }
00585 
00586 NS_IMETHODIMP nsMsgGroupView::GetRowProperties(PRInt32 aRow, nsISupportsArray *aProperties)
00587 {
00588   if (!IsValidIndex(aRow))
00589     return NS_MSG_INVALID_DBVIEW_INDEX; 
00590 
00591   if (m_flags[aRow] & MSG_VIEW_FLAG_DUMMY)
00592     return aProperties->AppendElement(kDummyMsgAtom);
00593   return nsMsgDBView::GetRowProperties(aRow, aProperties);
00594 }
00595 
00596 NS_IMETHODIMP nsMsgGroupView::GetCellProperties(PRInt32 aRow, nsITreeColumn *aCol, nsISupportsArray *aProperties)
00597 {
00598   if (m_flags[aRow] & MSG_VIEW_FLAG_DUMMY)
00599     return aProperties->AppendElement(kDummyMsgAtom);
00600   return nsMsgDBView::GetCellProperties(aRow, aCol, aProperties);
00601 }
00602 
00603 NS_IMETHODIMP nsMsgGroupView::GetCellText(PRInt32 aRow, nsITreeColumn* aCol, nsAString& aValue)
00604 {
00605   const PRUnichar* colID;
00606   aCol->GetIdConst(&colID);
00607   if (m_flags[aRow] & MSG_VIEW_FLAG_DUMMY && colID[0] != 'u')
00608   {
00609     nsCOMPtr <nsIMsgDBHdr> msgHdr;
00610     nsresult rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr));
00611     NS_ENSURE_SUCCESS(rv, rv);
00612     nsHashKey *hashKey = AllocHashKeyForHdr(msgHdr);
00613     if (!hashKey)
00614       return NS_OK;
00615 
00616     nsMsgGroupThread *groupThread = (nsMsgGroupThread *) m_groupsTable.Get(hashKey);
00617     if (colID[0] == 's'  && colID[1] == 'u' )
00618     {
00619       aValue.SetCapacity(0);
00620       nsXPIDLString valueText;
00621       switch (m_sortType)
00622       {
00623         case nsMsgViewSortType::byDate:
00624         {
00625           switch (((nsPRUint32Key *)hashKey)->GetValue())
00626           {
00627           case 1:
00628             if (!m_kTodayString.get())
00629               m_kTodayString.Adopt(GetString(NS_LITERAL_STRING("today").get()));
00630             aValue.Assign(m_kTodayString);
00631             break;
00632           case 2:
00633             if (!m_kYesterdayString.get())
00634               m_kYesterdayString.Adopt(GetString(NS_LITERAL_STRING("yesterday").get()));
00635             aValue.Assign(m_kYesterdayString);
00636             break;
00637           case 3:
00638             if (!m_kLastWeekString.get())
00639               m_kLastWeekString.Adopt(GetString(NS_LITERAL_STRING("lastWeek").get()));
00640             aValue.Assign(m_kLastWeekString);
00641             break;
00642           case 4:
00643             if (!m_kTwoWeeksAgoString.get())
00644               m_kTwoWeeksAgoString.Adopt(GetString(NS_LITERAL_STRING("twoWeeksAgo").get()));
00645             aValue.Assign(m_kTwoWeeksAgoString);
00646             break;
00647           case 5:
00648             if (!m_kOldMailString.get())
00649               m_kOldMailString.Adopt(GetString(NS_LITERAL_STRING("older").get()));
00650             aValue.Assign(m_kOldMailString);
00651             break;
00652           default:
00653             NS_ASSERTION(PR_FALSE, "bad age thread");
00654             break;
00655           }
00656           break;
00657         } 
00658         case nsMsgViewSortType::byAuthor:
00659           FetchAuthor(msgHdr, getter_Copies(valueText));
00660           aValue.Assign(valueText.get());
00661           break;
00662         case nsMsgViewSortType::byStatus:      
00663           rv = FetchStatus(m_flags[aRow], getter_Copies(valueText));
00664           if (!valueText)
00665             valueText.Adopt(GetString(NS_LITERAL_STRING("messagesWithNoStatus").get()));
00666           aValue.Assign(valueText);
00667           break;
00668         case nsMsgViewSortType::byTags:
00669           rv = FetchTags(msgHdr, getter_Copies(valueText));
00670           if (valueText.IsEmpty())
00671             valueText.Adopt(GetString(NS_LITERAL_STRING("untaggedMessages").get()));
00672           aValue.Assign(valueText);
00673           break;
00674         case nsMsgViewSortType::byPriority:
00675           FetchPriority(msgHdr, getter_Copies(valueText));
00676           if (!valueText)
00677             valueText.Adopt(GetString(NS_LITERAL_STRING("noPriority").get()));
00678           aValue.Assign(valueText);
00679           break;
00680         case nsMsgViewSortType::byAccount:
00681           FetchAccount(msgHdr, getter_Copies(valueText));
00682           aValue.Assign(valueText);
00683           break;
00684         case nsMsgViewSortType::byRecipient:
00685           FetchRecipients(msgHdr, getter_Copies(valueText));
00686           aValue.Assign(valueText);
00687           break;
00688         case nsMsgViewSortType::byAttachments:
00689           valueText.Adopt(GetString(((nsPRUint32Key *)hashKey)->GetValue()
00690             ? NS_LITERAL_STRING("attachments").get()
00691             : NS_LITERAL_STRING("noAttachments").get()));
00692           aValue.Assign(valueText);
00693           break;
00694         case nsMsgViewSortType::byFlagged:
00695           valueText.Adopt(GetString(((nsPRUint32Key *)hashKey)->GetValue()
00696             ? NS_LITERAL_STRING("groupFlagged").get()
00697             : NS_LITERAL_STRING("notFlagged").get()));
00698           aValue.Assign(valueText);
00699           break;
00700 
00701         default:
00702           NS_ASSERTION(PR_FALSE, "we don't sort by group for this type");
00703           break;
00704       }
00705     }
00706     else if (colID[0] == 't')
00707     {
00708       nsAutoString formattedCountString;
00709       PRUint32 numChildren = (groupThread) ? groupThread->NumRealChildren() : 0;
00710       formattedCountString.AppendInt(numChildren);
00711       aValue.Assign(formattedCountString);
00712     }
00713     delete hashKey;
00714     return NS_OK;
00715   }
00716   return nsMsgDBView::GetCellText(aRow, aCol, aValue);
00717 }
00718 
00719 NS_IMETHODIMP nsMsgGroupView::LoadMessageByViewIndex(nsMsgViewIndex aViewIndex)
00720 {
00721 
00722   if (m_flags[aViewIndex] & MSG_VIEW_FLAG_DUMMY)
00723   {
00724     // if we used to have one item selected, and now we have more than one, we should clear the message pane.
00725     nsCOMPtr <nsIMsgMessagePaneController> controller;
00726     if (mMsgWindow && NS_SUCCEEDED(mMsgWindow->GetMessagePaneController(getter_AddRefs(controller))) && controller)
00727       controller->ClearMsgPane();
00728     // since we are selecting a dummy row, we should also clear out m_currentlyDisplayedMsgUri
00729     m_currentlyDisplayedMsgUri.Truncate();
00730     return NS_OK;
00731   }
00732   else
00733     return nsMsgDBView::LoadMessageByViewIndex(aViewIndex);
00734 }
00735 
00736 nsresult nsMsgGroupView::GetThreadContainingMsgHdr(nsIMsgDBHdr *msgHdr, nsIMsgThread **pThread)
00737 {
00738   nsHashKey *hashKey = AllocHashKeyForHdr(msgHdr);
00739   if (hashKey)
00740   {
00741   nsMsgGroupThread *groupThread = (nsMsgGroupThread *) m_groupsTable.Get(hashKey);
00742   
00743   if (groupThread)
00744     groupThread->QueryInterface(NS_GET_IID(nsIMsgThread), (void **) pThread);
00745   delete hashKey;
00746   }
00747   else
00748     *pThread = nsnull;
00749   return (*pThread) ? NS_OK : NS_ERROR_FAILURE;
00750 }
00751 
00752 PRInt32 nsMsgGroupView::FindLevelInThread(nsIMsgDBHdr *msgHdr,
00753                                           nsMsgViewIndex startOfThread, nsMsgViewIndex viewIndex)
00754 {
00755   return (startOfThread == viewIndex) ? 0 : 1;
00756 }
00757 
00758 
00759 nsMsgViewIndex nsMsgGroupView::ThreadIndexOfMsg(nsMsgKey msgKey, 
00760                                             nsMsgViewIndex msgIndex /* = nsMsgViewIndex_None */,
00761                                             PRInt32 *pThreadCount /* = NULL */,
00762                                             PRUint32 *pFlags /* = NULL */)
00763 {
00764   if (msgIndex != nsMsgViewIndex_None && GroupViewUsesDummyRow())
00765   {
00766     // this case is all we care about at this point.
00767     if (m_flags[msgIndex] & MSG_VIEW_FLAG_ISTHREAD)
00768       return msgIndex;
00769   }
00770   return nsMsgDBView::ThreadIndexOfMsg(msgKey, msgIndex, pThreadCount, pFlags);
00771 }
00772 
00773 PRBool nsMsgGroupView::GroupViewUsesDummyRow()
00774 {
00775   return (m_sortType != nsMsgViewSortType::bySubject);
00776 }