Back to index

lightning-sunbird  0.9+nobinonly
nsMsgDBView.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  *   Jan Varga (varga@ku.sk)
00024  *   HÃ¥kan Waara (hwaara@chello.se)
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either of the GNU General Public License Version 2 or later (the "GPL"),
00028  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 
00040 #include "msgCore.h"
00041 #include "nsReadableUtils.h"
00042 #include "nsIMsgCustomColumnHandler.h"
00043 #include "nsMsgDBView.h"
00044 #include "nsISupports.h"
00045 #include "nsIMsgFolder.h"
00046 #include "nsIDBFolderInfo.h"
00047 #include "nsIMsgDatabase.h"
00048 #include "nsIMsgFolder.h"
00049 #include "MailNewsTypes2.h"
00050 #include "nsMsgUtils.h"
00051 #include "nsXPIDLString.h"
00052 #include "nsQuickSort.h"
00053 #include "nsIMsgImapMailFolder.h"
00054 #include "nsImapCore.h"
00055 #include "nsMsgFolderFlags.h"
00056 #include "nsIMsgLocalMailFolder.h"
00057 #include "nsIDOMElement.h"
00058 #include "nsDateTimeFormatCID.h"
00059 #include "nsMsgMimeCID.h"
00060 #include "nsIPrefService.h"
00061 #include "nsIPrefBranch.h"
00062 #include "nsIPrefBranch2.h"
00063 #include "nsIPrefLocalizedString.h"
00064 #include "nsIMsgSearchSession.h"
00065 #include "nsIMsgCopyService.h"
00066 #include "nsMsgBaseCID.h"
00067 #include "nsISpamSettings.h"
00068 #include "nsIMsgAccountManager.h"
00069 #include "nsITreeColumns.h"
00070 #include "nsTextFormatter.h"
00071 
00072 static NS_DEFINE_CID(kDateTimeFormatCID,    NS_DATETIMEFORMAT_CID);
00073 
00074 nsrefcnt nsMsgDBView::gInstanceCount      = 0;
00075 
00076 #ifdef SUPPORT_PRIORITY_COLORS
00077 nsIAtom * nsMsgDBView::kHighestPriorityAtom = nsnull;
00078 nsIAtom * nsMsgDBView::kHighPriorityAtom = nsnull;
00079 nsIAtom * nsMsgDBView::kLowestPriorityAtom = nsnull;
00080 nsIAtom * nsMsgDBView::kLowPriorityAtom = nsnull;
00081 #endif
00082 
00083 nsIAtom * nsMsgDBView::kUnreadMsgAtom     = nsnull;
00084 nsIAtom * nsMsgDBView::kNewMsgAtom = nsnull;
00085 nsIAtom * nsMsgDBView::kReadMsgAtom       = nsnull;
00086 nsIAtom * nsMsgDBView::kRepliedMsgAtom = nsnull;
00087 nsIAtom * nsMsgDBView::kForwardedMsgAtom = nsnull;
00088 nsIAtom * nsMsgDBView::kOfflineMsgAtom    = nsnull;
00089 nsIAtom * nsMsgDBView::kFlaggedMsgAtom = nsnull;
00090 nsIAtom * nsMsgDBView::kImapDeletedMsgAtom = nsnull;
00091 nsIAtom * nsMsgDBView::kAttachMsgAtom = nsnull;
00092 nsIAtom * nsMsgDBView::kHasUnreadAtom = nsnull;
00093 nsIAtom * nsMsgDBView::kWatchThreadAtom = nsnull;
00094 nsIAtom * nsMsgDBView::kIgnoreThreadAtom = nsnull;
00095 nsIAtom * nsMsgDBView::kHasImageAtom = nsnull;
00096 
00097 nsIAtom * nsMsgDBView::kJunkMsgAtom = nsnull;
00098 nsIAtom * nsMsgDBView::kNotJunkMsgAtom = nsnull;
00099 nsIAtom * nsMsgDBView::kDummyMsgAtom = nsnull;
00100 
00101 nsIAtom * nsMsgDBView::kLabelColorWhiteAtom = nsnull;
00102 nsIAtom * nsMsgDBView::kLabelColorBlackAtom = nsnull;
00103 
00104 PRUnichar * nsMsgDBView::kHighestPriorityString = nsnull;
00105 PRUnichar * nsMsgDBView::kHighPriorityString = nsnull;
00106 PRUnichar * nsMsgDBView::kLowestPriorityString = nsnull;
00107 PRUnichar * nsMsgDBView::kLowPriorityString = nsnull;
00108 PRUnichar * nsMsgDBView::kNormalPriorityString = nsnull;
00109 PRUnichar * nsMsgDBView::kReadString = nsnull;
00110 PRUnichar * nsMsgDBView::kRepliedString = nsnull;
00111 PRUnichar * nsMsgDBView::kForwardedString = nsnull;
00112 PRUnichar * nsMsgDBView::kNewString = nsnull;
00113 
00114 PRUnichar * nsMsgDBView::kKiloByteString = nsnull;
00115 
00116 nsDateFormatSelector  nsMsgDBView::m_dateFormatDefault = kDateFormatShort;
00117 nsDateFormatSelector  nsMsgDBView::m_dateFormatThisWeek = kDateFormatShort;
00118 nsDateFormatSelector  nsMsgDBView::m_dateFormatToday = kDateFormatNone;
00119 
00120 NS_IMPL_ADDREF(nsMsgDBView)
00121 NS_IMPL_RELEASE(nsMsgDBView)
00122 
00123 NS_INTERFACE_MAP_BEGIN(nsMsgDBView)
00124    NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMsgDBView)
00125    NS_INTERFACE_MAP_ENTRY(nsIMsgDBView)
00126    NS_INTERFACE_MAP_ENTRY(nsIDBChangeListener)
00127    NS_INTERFACE_MAP_ENTRY(nsITreeView)
00128    NS_INTERFACE_MAP_ENTRY(nsIJunkMailClassificationListener)
00129 NS_INTERFACE_MAP_END
00130 
00131 nsMsgDBView::nsMsgDBView()
00132 {
00133   /* member initializers and constructor code */
00134   m_sortValid = PR_FALSE;
00135   m_sortOrder = nsMsgViewSortOrder::none;
00136   m_viewFlags = nsMsgViewFlagsType::kNone;
00137   m_cachedMsgKey = nsMsgKey_None;
00138   m_currentlyDisplayedMsgKey = nsMsgKey_None;
00139   m_currentlyDisplayedViewIndex = nsMsgViewIndex_None;
00140   mNumSelectedRows = 0;
00141   mSuppressMsgDisplay = PR_FALSE;
00142   mSuppressCommandUpdating = PR_FALSE;
00143   mSuppressChangeNotification = PR_FALSE;
00144   mGoForwardEnabled = PR_FALSE;
00145   mGoBackEnabled = PR_FALSE;
00146 
00147   mIsNews = PR_FALSE;
00148   mDeleteModel = nsMsgImapDeleteModels::MoveToTrash;
00149   m_deletingRows = PR_FALSE;
00150   mJunkIndices = nsnull;
00151   mNumJunkIndices = 0;
00152   mNumMessagesRemainingInBatch = 0;
00153   mShowSizeInLines = PR_FALSE;
00154 
00155   /* mCommandsNeedDisablingBecauseOfSelection - A boolean that tell us if we needed to disable commands because of what's selected.
00156     If we're offline w/o a downloaded msg selected, or a dummy message was selected.
00157   */
00158   
00159   mCommandsNeedDisablingBecauseOfSelection = PR_FALSE;  
00160   mRemovingRow = PR_FALSE;
00161   m_saveRestoreSelectionDepth = 0;
00162   // initialize any static atoms or unicode strings
00163   if (gInstanceCount == 0) 
00164   {
00165     InitializeAtomsAndLiterals();
00166     InitDisplayFormats();
00167   }
00168   
00169   InitLabelStrings();
00170   gInstanceCount++;
00171 }
00172 
00173 void nsMsgDBView::InitializeAtomsAndLiterals()
00174 {
00175   kUnreadMsgAtom = NS_NewAtom("unread");
00176   kNewMsgAtom = NS_NewAtom("new");
00177   kReadMsgAtom = NS_NewAtom("read");
00178   kRepliedMsgAtom = NS_NewAtom("replied");
00179   kForwardedMsgAtom = NS_NewAtom("forwarded");
00180   kOfflineMsgAtom = NS_NewAtom("offline");
00181   kFlaggedMsgAtom = NS_NewAtom("flagged");
00182   kImapDeletedMsgAtom = NS_NewAtom("imapdeleted");
00183   kAttachMsgAtom = NS_NewAtom("attach");
00184   kHasUnreadAtom = NS_NewAtom("hasUnread");
00185   kWatchThreadAtom = NS_NewAtom("watch");
00186   kIgnoreThreadAtom = NS_NewAtom("ignore");
00187   kHasImageAtom = NS_NewAtom("hasimage");
00188   kJunkMsgAtom = NS_NewAtom("junk");
00189   kNotJunkMsgAtom = NS_NewAtom("notjunk");
00190   kDummyMsgAtom = NS_NewAtom("dummy");
00191 #ifdef SUPPORT_PRIORITY_COLORS
00192   kHighestPriorityAtom = NS_NewAtom("priority-highest");
00193   kHighPriorityAtom = NS_NewAtom("priority-high");
00194   kLowestPriorityAtom = NS_NewAtom("priority-lowest");
00195   kLowPriorityAtom = NS_NewAtom("priority-low");
00196 #endif
00197 
00198   // priority strings
00199   kHighestPriorityString = GetString(NS_LITERAL_STRING("priorityHighest").get());
00200   kHighPriorityString = GetString(NS_LITERAL_STRING("priorityHigh").get());
00201   kLowestPriorityString = GetString(NS_LITERAL_STRING("priorityLowest").get());
00202   kLowPriorityString = GetString(NS_LITERAL_STRING("priorityLow").get());
00203   kNormalPriorityString = GetString(NS_LITERAL_STRING("priorityNormal").get());
00204 
00205   kLabelColorWhiteAtom = NS_NewAtom("lc-white");
00206   kLabelColorBlackAtom = NS_NewAtom("lc-black");
00207 
00208   kReadString = GetString(NS_LITERAL_STRING("read").get());
00209   kRepliedString = GetString(NS_LITERAL_STRING("replied").get());
00210   kForwardedString = GetString(NS_LITERAL_STRING("forwarded").get());
00211   kNewString = GetString(NS_LITERAL_STRING("new").get());
00212   
00213   kKiloByteString = GetString(NS_LITERAL_STRING("kiloByteAbbreviation").get());
00214 }
00215 
00216 nsMsgDBView::~nsMsgDBView()
00217 {
00218   if (m_db)
00219     m_db->RemoveListener(this);
00220 
00221   gInstanceCount--;
00222   if (gInstanceCount <= 0) 
00223   {
00224     NS_IF_RELEASE(kUnreadMsgAtom);
00225     NS_IF_RELEASE(kNewMsgAtom);
00226     NS_IF_RELEASE(kReadMsgAtom);
00227     NS_IF_RELEASE(kRepliedMsgAtom);
00228     NS_IF_RELEASE(kForwardedMsgAtom);
00229     NS_IF_RELEASE(kOfflineMsgAtom);
00230     NS_IF_RELEASE(kFlaggedMsgAtom);
00231     NS_IF_RELEASE(kImapDeletedMsgAtom);
00232     NS_IF_RELEASE(kAttachMsgAtom);
00233     NS_IF_RELEASE(kHasUnreadAtom);
00234     NS_IF_RELEASE(kWatchThreadAtom);
00235     NS_IF_RELEASE(kIgnoreThreadAtom);
00236     NS_IF_RELEASE(kHasImageAtom);
00237     NS_IF_RELEASE(kJunkMsgAtom);
00238     NS_IF_RELEASE(kNotJunkMsgAtom);
00239     NS_IF_RELEASE(kDummyMsgAtom);
00240 
00241 #ifdef SUPPORT_PRIORITY_COLORS
00242     NS_IF_RELEASE(kHighestPriorityAtom);
00243     NS_IF_RELEASE(kHighPriorityAtom);
00244     NS_IF_RELEASE(kLowestPriorityAtom);
00245     NS_IF_RELEASE(kLowPriorityAtom);
00246 #endif
00247 
00248     NS_IF_RELEASE(kLabelColorWhiteAtom);
00249     NS_IF_RELEASE(kLabelColorBlackAtom);
00250 
00251     nsCRT::free(kHighestPriorityString);
00252     nsCRT::free(kHighPriorityString);
00253     nsCRT::free(kLowestPriorityString);
00254     nsCRT::free(kLowPriorityString);
00255     nsCRT::free(kNormalPriorityString);
00256 
00257     nsCRT::free(kReadString);
00258     nsCRT::free(kRepliedString);
00259     nsCRT::free(kForwardedString);
00260     nsCRT::free(kNewString);
00261 
00262     nsCRT::free(kKiloByteString);
00263   }
00264 }
00265 
00266 nsresult nsMsgDBView::InitLabelStrings()
00267 {
00268   nsresult rv = NS_OK;
00269   nsCString prefString;
00270 
00271   for(PRInt32 i = 0; i < PREF_LABELS_MAX; i++)
00272   {
00273     prefString.Assign(PREF_LABELS_DESCRIPTION);
00274     prefString.AppendInt(i + 1);
00275     rv = GetPrefLocalizedString(prefString.get(), mLabelPrefDescriptions[i]);
00276   }
00277   return rv;
00278 }
00279 
00280 // helper function used to fetch strings from the messenger string bundle
00281 PRUnichar * nsMsgDBView::GetString(const PRUnichar *aStringName)
00282 {
00283   nsresult    res = NS_OK;
00284   PRUnichar   *ptrv = nsnull;
00285   
00286   if (!mMessengerStringBundle)
00287   {
00288     static const char propertyURL[] = MESSENGER_STRING_URL;
00289     nsCOMPtr<nsIStringBundleService> sBundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &res);
00290     if (NS_SUCCEEDED(res) && sBundleService) 
00291       res = sBundleService->CreateBundle(propertyURL, getter_AddRefs(mMessengerStringBundle));
00292   }
00293   
00294   if (mMessengerStringBundle)
00295     res = mMessengerStringBundle->GetStringFromName(aStringName, &ptrv);
00296   
00297   if ( NS_SUCCEEDED(res) && (ptrv) )
00298     return ptrv;
00299   else
00300     return nsCRT::strdup(aStringName);
00301 }
00302 
00303 // helper function used to fetch localized strings from the prefs
00304 nsresult nsMsgDBView::GetPrefLocalizedString(const char *aPrefName, nsString& aResult)
00305 {
00306   nsresult rv = NS_OK;
00307   nsCOMPtr<nsIPrefBranch> prefBranch;
00308   nsCOMPtr<nsIPrefLocalizedString> pls;
00309   nsXPIDLString ucsval;
00310 
00311   prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
00312   NS_ENSURE_SUCCESS(rv, rv);
00313 
00314   rv = prefBranch->GetComplexValue(aPrefName, NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(pls));
00315   NS_ENSURE_SUCCESS(rv, rv);
00316   pls->ToString(getter_Copies(ucsval));
00317   aResult = ucsval.get();
00318   return rv;
00319 }
00320 
00321 nsresult nsMsgDBView::AppendKeywordProperties(const char *keywords, nsISupportsArray *properties, PRBool addSelectedTextProperty)
00322 {
00323   // get the top most keyword's color and append that as a property.
00324   nsresult rv;
00325   if (!mTagService)
00326   {
00327     mTagService = do_GetService(NS_MSGTAGSERVICE_CONTRACTID, &rv);
00328     NS_ENSURE_SUCCESS(rv, rv);
00329   }
00330   
00331   nsCString topKey;
00332   rv = mTagService->GetTopKey(keywords, topKey);
00333   NS_ENSURE_SUCCESS(rv, rv);
00334   if (topKey.IsEmpty())
00335     return NS_OK;
00336   
00337   nsCString color;
00338   rv = mTagService->GetColorForKey(topKey, color);
00339   if (NS_SUCCEEDED(rv) && !color.IsEmpty())
00340   {
00341     if (addSelectedTextProperty)
00342       properties->AppendElement(color.EqualsLiteral(LABEL_COLOR_WHITE_STRING) 
00343         ? kLabelColorBlackAtom
00344         : kLabelColorWhiteAtom);  
00345     color.Replace(0, 1, NS_LITERAL_CSTRING(LABEL_COLOR_STRING));
00346     nsCOMPtr <nsIAtom> keywordAtom = do_GetAtom(color.get());
00347     properties->AppendElement(keywordAtom);
00348   }
00349   return rv;
00350 }
00351 
00353 // nsITreeView Implementation Methods (and helper methods)
00355 
00356 nsresult nsMsgDBView::FetchAuthor(nsIMsgDBHdr * aHdr, PRUnichar ** aSenderString)
00357 {
00358   nsXPIDLString unparsedAuthor;
00359   if (!mHeaderParser)
00360     mHeaderParser = do_GetService(NS_MAILNEWS_MIME_HEADER_PARSER_CONTRACTID);
00361 
00362   nsresult rv = aHdr->GetMime2DecodedAuthor(getter_Copies(unparsedAuthor));
00363   
00364   // *sigh* how sad, we need to convert our beautiful unicode string to utf8 
00365   // so we can extract the name part of the address...then convert it back to 
00366   // unicode again.
00367   if (mHeaderParser)
00368   {
00369     nsXPIDLCString name;
00370     rv = mHeaderParser->ExtractHeaderAddressName("UTF-8", NS_ConvertUCS2toUTF8(unparsedAuthor).get(), getter_Copies(name));
00371     if (NS_SUCCEEDED(rv) && (const char*)name)
00372     {
00373       *aSenderString = nsCRT::strdup(NS_ConvertUTF8toUCS2(name).get());
00374       return NS_OK;
00375     }
00376   }
00377   // if we got here then just return the original string
00378   *aSenderString = nsCRT::strdup(unparsedAuthor);
00379   return NS_OK;
00380 }
00381 
00382 nsresult nsMsgDBView::FetchAccount(nsIMsgDBHdr * aHdr, PRUnichar ** aAccount)
00383 {
00384   nsXPIDLCString accountKey;
00385 
00386   nsresult rv = aHdr->GetAccountKey(getter_Copies(accountKey));
00387 
00388   // Cache the account manager?
00389   nsCOMPtr <nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
00390   NS_ENSURE_SUCCESS(rv,rv);
00391   nsCOMPtr <nsIMsgAccount> account;
00392   if (accountKey.IsEmpty())
00393   {
00394   }
00395   else
00396   {
00397     rv = accountManager->GetAccount(accountKey, getter_AddRefs(account));
00398   }
00399   if (account)
00400   {
00401     nsCOMPtr <nsIMsgIncomingServer> server;
00402     account->GetIncomingServer(getter_AddRefs(server));
00403     if (server)
00404       server->GetPrettyName(aAccount);
00405   }
00406   else
00407   {
00408     *aAccount = ToNewUnicode(accountKey);
00409   }
00410   
00411   if (!*aAccount)
00412     *aAccount = nsCRT::strdup(NS_LITERAL_STRING("").get());
00413   return NS_OK;
00414 }
00415 
00416 
00417 nsresult nsMsgDBView::FetchRecipients(nsIMsgDBHdr * aHdr, PRUnichar ** aRecipientsString)
00418 {
00419   nsXPIDLString unparsedRecipients;
00420   if (!mHeaderParser)
00421     mHeaderParser = do_GetService(NS_MAILNEWS_MIME_HEADER_PARSER_CONTRACTID);
00422 
00423   nsresult rv = aHdr->GetMime2DecodedRecipients(getter_Copies(unparsedRecipients));
00424   
00425   // *sigh* how sad, we need to convert our beautiful unicode string to utf8 
00426   // so we can extract the name part of the address...then convert it back to 
00427   // unicode again.
00428   if (mHeaderParser)
00429   {
00430     nsXPIDLCString names;
00431     rv = mHeaderParser->ExtractHeaderAddressNames("UTF-8", NS_ConvertUCS2toUTF8(unparsedRecipients).get(), getter_Copies(names));
00432     if (NS_SUCCEEDED(rv) && (const char*)names)
00433     {
00434       *aRecipientsString = nsCRT::strdup(NS_ConvertUTF8toUCS2(names).get());
00435       return NS_OK;
00436     }
00437   }
00438   // if we got here then just return the original string
00439   *aRecipientsString = nsCRT::strdup(unparsedRecipients);
00440   return NS_OK;
00441 }
00442 
00443 nsresult nsMsgDBView::FetchSubject(nsIMsgDBHdr * aMsgHdr, PRUint32 aFlags, PRUnichar ** aValue)
00444 {
00445   if (aFlags & MSG_FLAG_HAS_RE)
00446   {
00447     nsXPIDLString subject;
00448     aMsgHdr->GetMime2DecodedSubject(getter_Copies(subject));
00449     nsAutoString reSubject;
00450     reSubject.AssignLiteral("Re: ");
00451     reSubject.Append(subject);
00452     *aValue = ToNewUnicode(reSubject);
00453   }
00454   else
00455     aMsgHdr->GetMime2DecodedSubject(aValue);
00456 
00457   return NS_OK;
00458 }
00459 
00460 // in case we want to play around with the date string, I've broken it out into
00461 // a separate routine. 
00462 nsresult nsMsgDBView::FetchDate(nsIMsgDBHdr * aHdr, PRUnichar ** aDateString)
00463 {
00464   PRTime dateOfMsg;
00465   PRTime dateOfMsgLocal;
00466   nsAutoString formattedDateString;
00467 
00468   if (!mDateFormater)
00469     mDateFormater = do_CreateInstance(kDateTimeFormatCID);
00470 
00471   NS_ENSURE_TRUE(mDateFormater, NS_ERROR_FAILURE);
00472   nsresult rv = aHdr->GetDate(&dateOfMsg);
00473 
00474   PRTime currentTime = PR_Now();
00475   PRExplodedTime explodedCurrentTime;
00476   PR_ExplodeTime(currentTime, PR_LocalTimeParameters, &explodedCurrentTime);
00477   PRExplodedTime explodedMsgTime;
00478   PR_ExplodeTime(dateOfMsg, PR_LocalTimeParameters, &explodedMsgTime);
00479 
00480   // if the message is from today, don't show the date, only the time. (i.e. 3:15 pm)
00481   // if the message is from the last week, show the day of the week.   (i.e. Mon 3:15 pm)
00482   // in all other cases, show the full date (03/19/01 3:15 pm)
00483 
00484   nsDateFormatSelector dateFormat = m_dateFormatDefault;
00485   if (explodedCurrentTime.tm_year == explodedMsgTime.tm_year &&
00486       explodedCurrentTime.tm_month == explodedMsgTime.tm_month &&
00487       explodedCurrentTime.tm_mday == explodedMsgTime.tm_mday)
00488   {
00489     // same day...
00490     dateFormat = m_dateFormatToday;
00491   } 
00492   // the following chunk of code causes us to show a day instead of a number if the message was received
00493   // within the last 7 days. i.e. Mon 5:10pm.
00494   // The concrete format used is dependent on a preference setting (see InitDisplayFormats), but the default
00495   // is the format described above.
00496   else if (LL_CMP(currentTime, >, dateOfMsg))
00497   {
00498     // some constants for calculation
00499     static PRInt64 microSecondsPerSecond;
00500     static PRInt64 secondsPerDay;
00501     static PRInt64 microSecondsPerDay;
00502     static PRInt64 microSecondsPer6Days;
00503 
00504     static PRBool bGotConstants = PR_FALSE;
00505     if ( !bGotConstants )
00506     {
00507       // seeds
00508       LL_I2L  ( microSecondsPerSecond,  PR_USEC_PER_SEC );
00509       LL_UI2L ( secondsPerDay,          60 * 60 * 24 );
00510     
00511       // derivees
00512       LL_MUL( microSecondsPerDay,   secondsPerDay,      microSecondsPerSecond );
00513       LL_MUL( microSecondsPer6Days, microSecondsPerDay, 6 );
00514 
00515       bGotConstants = PR_TRUE;
00516     }
00517 
00518     // setting the time variables to local time
00519     PRInt64 GMTLocalTimeShift;
00520     LL_ADD( GMTLocalTimeShift, explodedCurrentTime.tm_params.tp_gmt_offset, explodedCurrentTime.tm_params.tp_dst_offset );
00521     LL_MUL( GMTLocalTimeShift, GMTLocalTimeShift, microSecondsPerSecond );
00522     LL_ADD( currentTime, currentTime, GMTLocalTimeShift );
00523     LL_ADD( dateOfMsgLocal, dateOfMsg, GMTLocalTimeShift );
00524            
00525     // the most recent midnight, counting from current time
00526     PRInt64 todaysMicroSeconds, mostRecentMidnight;
00527     LL_MOD( todaysMicroSeconds, currentTime, microSecondsPerDay );
00528     LL_SUB( mostRecentMidnight, currentTime, todaysMicroSeconds );
00529 
00530     // most recent midnight minus 6 days
00531     PRInt64 mostRecentWeek;
00532     LL_SUB( mostRecentWeek, mostRecentMidnight, microSecondsPer6Days );
00533 
00534     // was the message sent during the last week?
00535     if ( LL_CMP( dateOfMsgLocal, >=, mostRecentWeek ) )
00536     { // yes ....
00537       dateFormat = m_dateFormatThisWeek;
00538     }
00539   }
00540 
00541   if (NS_SUCCEEDED(rv)) 
00542     rv = mDateFormater->FormatPRTime(nsnull /* nsILocale* locale */,
00543                                       dateFormat,
00544                                       kTimeFormatNoSeconds,
00545                                       dateOfMsg,
00546                                       formattedDateString);
00547 
00548   if (NS_SUCCEEDED(rv))
00549     *aDateString = ToNewUnicode(formattedDateString);
00550   
00551   return rv;
00552 }
00553 
00554 nsresult nsMsgDBView::FetchStatus(PRUint32 aFlags, PRUnichar ** aStatusString)
00555 {
00556   const PRUnichar * statusString = nsnull;
00557   
00558   if(aFlags & MSG_FLAG_REPLIED)
00559     statusString = kRepliedString;
00560   else if(aFlags & MSG_FLAG_FORWARDED)
00561     statusString = kForwardedString;
00562   else if(aFlags & MSG_FLAG_NEW)
00563     statusString = kNewString;
00564   else if(aFlags & MSG_FLAG_READ)
00565     statusString = kReadString;
00566   
00567   if (statusString)
00568     *aStatusString = nsCRT::strdup(statusString);
00569   else 
00570     *aStatusString = nsnull;
00571   
00572   return NS_OK;
00573 }
00574 
00575 nsresult nsMsgDBView::FetchSize(nsIMsgDBHdr * aHdr, PRUnichar ** aSizeString)
00576 {
00577   nsAutoString formattedSizeString;
00578   PRUint32 msgSize = 0;
00579   
00580   // for news, show the line count, not the size if the user wants so
00581   if (mShowSizeInLines)
00582   {
00583     aHdr->GetLineCount(&msgSize);
00584     formattedSizeString.AppendInt(msgSize);
00585   }
00586   else 
00587   {
00588     PRUint32 flags = 0;
00589 
00590     aHdr->GetFlags(&flags);
00591     if (flags & MSG_FLAG_PARTIAL)
00592       aHdr->GetUint32Property("onlineSize", &msgSize);
00593 
00594     if (msgSize == 0)
00595       aHdr->GetMessageSize(&msgSize);
00596     
00597     if(msgSize < 1024)
00598       msgSize = 1024;
00599     
00600     PRUint32 sizeInKB = msgSize/1024;
00601     
00602     // kKiloByteString is a localized string that we use to get the right
00603     // format to add on the "KB" or equivalent
00604     nsTextFormatter::ssprintf(formattedSizeString,
00605                               kKiloByteString,
00606                               sizeInKB);
00607   }
00608   
00609   *aSizeString = ToNewUnicode(formattedSizeString);
00610   return (*aSizeString) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
00611 }
00612 
00613 nsresult nsMsgDBView::FetchPriority(nsIMsgDBHdr *aHdr, PRUnichar ** aPriorityString)
00614 {
00615   nsMsgPriorityValue priority = nsMsgPriority::notSet;
00616   const PRUnichar * priorityString = nsnull;
00617   aHdr->GetPriority(&priority);
00618 
00619   switch (priority)
00620   {
00621   case nsMsgPriority::highest:
00622     priorityString = kHighestPriorityString;
00623     break;
00624   case nsMsgPriority::high:
00625     priorityString = kHighPriorityString;
00626     break;
00627   case nsMsgPriority::low:
00628     priorityString = kLowPriorityString;
00629     break;
00630   case nsMsgPriority::lowest:
00631     priorityString = kLowestPriorityString;
00632     break;
00633   case nsMsgPriority::normal:
00634     priorityString = kNormalPriorityString;
00635     break;
00636   default:
00637     break;
00638   }
00639 
00640   *aPriorityString = (priorityString) ? nsCRT::strdup(priorityString) : nsnull;
00641 
00642   return NS_OK;
00643 }
00644 
00645 nsresult nsMsgDBView::FetchKeywords(nsIMsgDBHdr *aHdr, char ** keywordString)
00646 {
00647   nsresult rv = NS_OK;
00648   if (!mTagService)
00649   {
00650     mTagService = do_GetService(NS_MSGTAGSERVICE_CONTRACTID, &rv);
00651     NS_ENSURE_SUCCESS(rv, rv);
00652   }
00653   nsMsgLabelValue label = 0;
00654 
00655   rv = aHdr->GetLabel(&label);
00656   nsXPIDLCString keywords;
00657   aHdr->GetStringProperty("keywords", getter_Copies(keywords));
00658   if (label > 0)
00659   {
00660     nsCAutoString labelStr("$label");
00661     labelStr.Append((char) (label + '0'));
00662     if (!FindInReadable(labelStr, keywords, nsCaseInsensitiveCStringComparator()))
00663     {
00664       if (!keywords.IsEmpty())
00665         keywords.Append(' ');
00666       keywords.Append(labelStr);
00667     }
00668   }
00669   *keywordString = ToNewCString(keywords);
00670   return (*keywordString) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
00671 }
00672 
00673 nsresult nsMsgDBView::FetchTags(nsIMsgDBHdr *aHdr, PRUnichar ** aTagString)
00674 {
00675   nsresult rv = NS_OK;
00676   if (!mTagService)
00677   {
00678     mTagService = do_GetService(NS_MSGTAGSERVICE_CONTRACTID, &rv);
00679     NS_ENSURE_SUCCESS(rv, rv);
00680   }
00681 
00682   nsXPIDLString tags;
00683   nsXPIDLCString keywords;
00684   aHdr->GetStringProperty("keywords", getter_Copies(keywords));
00685 
00686   nsMsgLabelValue label = 0;
00687   rv = aHdr->GetLabel(&label);  
00688   if (label > 0)
00689   {
00690     nsCAutoString labelStr("$label");
00691     labelStr.Append((char) (label + '0'));
00692     if (!FindInReadable(labelStr, keywords, nsCaseInsensitiveCStringComparator()))
00693       FetchLabel(aHdr, getter_Copies(tags));
00694   }
00695 
00696   nsCStringArray keywordsArray;
00697   keywordsArray.ParseString(keywords.get(), " ");
00698   nsAutoString tag;
00699 
00700   for (PRInt32 i = 0; i < keywordsArray.Count(); i++)
00701   {
00702     rv = mTagService->GetTagForKey(*(keywordsArray[i]), tag);
00703     if (NS_SUCCEEDED(rv) && !tag.IsEmpty())
00704     {
00705       if (!tags.IsEmpty())
00706         tags.Append((PRUnichar) ' ');
00707       tags.Append(tag);
00708     }
00709   }
00710 
00711   *aTagString = ToNewUnicode(tags);
00712   return NS_OK;
00713 }
00714 
00715 nsresult nsMsgDBView::FetchLabel(nsIMsgDBHdr *aHdr, PRUnichar ** aLabelString)
00716 {
00717   nsresult rv = NS_OK;
00718   nsMsgLabelValue label = 0;
00719 
00720   NS_ENSURE_ARG_POINTER(aHdr);  
00721   NS_ENSURE_ARG_POINTER(aLabelString);  
00722 
00723   rv = aHdr->GetLabel(&label);
00724   NS_ENSURE_SUCCESS(rv, rv);
00725 
00726   // we don't care if label is not between 1 and PREF_LABELS_MAX inclusive.
00727   if ((label < 1) || (label > PREF_LABELS_MAX))
00728   {
00729     *aLabelString = nsnull;
00730     return NS_OK;
00731   }
00732 
00733   // We need to subtract 1 because mLabelPrefDescriptions is 0 based.
00734   if(!mLabelPrefDescriptions[label - 1].IsEmpty())
00735   {
00736     *aLabelString = nsCRT::strdup(mLabelPrefDescriptions[label - 1].get());
00737     if (!*aLabelString)
00738        return NS_ERROR_OUT_OF_MEMORY;
00739   }
00740   return NS_OK;
00741 }
00742 
00743 
00744 /*if you call SaveAndClearSelection make sure to call RestoreSelection otherwise
00745 m_saveRestoreSelectionDepth will be incorrect and will lead to selection msg problems*/
00746 
00747 nsresult nsMsgDBView::SaveAndClearSelection(nsMsgKey *aCurrentMsgKey, nsMsgKeyArray *aMsgKeyArray)
00748 {
00749   // we don't do anything on nested Save / Restore calls.
00750   m_saveRestoreSelectionDepth++;
00751   if (m_saveRestoreSelectionDepth != 1)
00752     return NS_OK;
00753   
00754   if (!mTreeSelection || !mTree)
00755     return NS_OK;
00756 
00757   // first, freeze selection.
00758   mTreeSelection->SetSelectEventsSuppressed(PR_TRUE);
00759 
00760   // second, save the current index.
00761   if (aCurrentMsgKey)
00762   {
00763     PRInt32 currentIndex;
00764     if (NS_SUCCEEDED(mTreeSelection->GetCurrentIndex(&currentIndex)) && currentIndex >= 0 && currentIndex < GetSize())
00765       *aCurrentMsgKey = m_keys.GetAt(currentIndex);
00766     else
00767       *aCurrentMsgKey = nsMsgKey_None;
00768   }
00769   
00770   // third, get an array of view indices for the selection.
00771   nsUInt32Array selection;
00772   GetSelectedIndices(&selection);
00773   PRInt32 numIndices = selection.GetSize();
00774 
00775   // now store the msg key for each selected item.
00776   nsMsgKey msgKey;
00777   for (PRInt32 index = 0; index < numIndices; index++)
00778   {
00779     msgKey = m_keys.GetAt(selection.GetAt(index));
00780     aMsgKeyArray->Add(msgKey);
00781   }
00782 
00783   // clear the selection, we'll manually restore it later.
00784   if (mTreeSelection)
00785     mTreeSelection->ClearSelection();
00786 
00787   return NS_OK;
00788 }
00789 
00790 nsresult nsMsgDBView::RestoreSelection(nsMsgKey aCurrentMsgKey, nsMsgKeyArray *aMsgKeyArray)
00791 {
00792   // we don't do anything on nested Save / Restore calls.
00793   m_saveRestoreSelectionDepth--;
00794   if (m_saveRestoreSelectionDepth)
00795     return NS_OK;
00796 
00797   if (!mTreeSelection)  // don't assert.
00798     return NS_OK;
00799   
00800   // turn our message keys into corresponding view indices
00801   PRInt32 arraySize = aMsgKeyArray->GetSize();
00802   nsMsgViewIndex     currentViewPosition = nsMsgViewIndex_None;
00803   nsMsgViewIndex     newViewPosition = nsMsgViewIndex_None;
00804 
00805   // if we are threaded, we need to do a little more work
00806   // we need to find (and expand) all the threads that contain messages 
00807   // that we had selected before.
00808   if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
00809   {
00810     for (PRInt32 index = 0; index < arraySize; index ++) 
00811     {
00812       FindKey(aMsgKeyArray->GetAt(index), PR_TRUE /* expand */);
00813     }
00814   }
00815 
00816   for (PRInt32 index = 0; index < arraySize; index ++)
00817   {
00818     newViewPosition = FindKey(aMsgKeyArray->GetAt(index), PR_FALSE);  
00819     // add the index back to the selection.
00820     mTreeSelection->ToggleSelect(newViewPosition);
00821   }
00822 
00823   // make sure the currentView was preserved....
00824   if (aCurrentMsgKey != nsMsgKey_None)
00825     currentViewPosition = FindKey(aCurrentMsgKey, PR_TRUE);
00826 
00827   if (mTree)
00828     mTreeSelection->SetCurrentIndex(currentViewPosition);
00829       
00830   // make sure the current message is once again visible in the thread pane
00831   // so we don't have to go search for it in the thread pane
00832   if (mTree && currentViewPosition != nsMsgViewIndex_None)
00833     mTree->EnsureRowIsVisible(currentViewPosition);
00834 
00835   // unfreeze selection.
00836   mTreeSelection->SetSelectEventsSuppressed(PR_FALSE);
00837   return NS_OK;
00838 }
00839 
00840 nsresult nsMsgDBView::GenerateURIForMsgKey(nsMsgKey aMsgKey, nsIMsgFolder *folder, char ** aURI)
00841 {
00842   NS_ENSURE_ARG(folder);
00843   return(folder->GenerateMessageURI(aMsgKey, aURI));
00844 }
00845 
00846 nsresult nsMsgDBView::CycleThreadedColumn(nsIDOMElement * aElement)
00847 {
00848   nsAutoString currentView;
00849 
00850   // toggle threaded/unthreaded mode
00851   aElement->GetAttribute(NS_LITERAL_STRING("currentView"), currentView);
00852   if (currentView.EqualsLiteral("threaded"))
00853   {
00854     aElement->SetAttribute(NS_LITERAL_STRING("currentView"), NS_LITERAL_STRING("unthreaded"));
00855 
00856   }
00857   else
00858   {
00859      aElement->SetAttribute(NS_LITERAL_STRING("currentView"), NS_LITERAL_STRING("threaded"));
00860      // we must be unthreaded view. create a threaded view and replace ourself.
00861 
00862   }
00863 
00864   // i think we need to create a new view and switch it in this circumstance since
00865   // we are toggline between threaded and non threaded mode.
00866 
00867 
00868   return NS_OK;
00869 }
00870 
00871 NS_IMETHODIMP nsMsgDBView::IsEditable(PRInt32 row, nsITreeColumn* col, PRBool* _retval)
00872 {
00873   //attempt to retreive a custom column handler. If it exists call it and return
00874   const PRUnichar* colID;
00875   col->GetIdConst(&colID);
00876   
00877   nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID);
00878 
00879   if (colHandler) 
00880   {
00881        colHandler->IsEditable(row, col, _retval);
00882        return NS_OK;
00883   }
00884   
00885   *_retval = PR_FALSE;
00886   return NS_OK;
00887 }
00888 
00889 NS_IMETHODIMP nsMsgDBView::SetCellValue(PRInt32 row, nsITreeColumn* col, const nsAString& value)
00890 {
00891   return NS_OK;
00892 }
00893 
00894 NS_IMETHODIMP nsMsgDBView::SetCellText(PRInt32 row, nsITreeColumn* col, const nsAString& value)
00895 {
00896   return NS_OK;
00897 }
00898 
00899 NS_IMETHODIMP nsMsgDBView::GetRowCount(PRInt32 *aRowCount)
00900 {
00901   *aRowCount = GetSize();
00902   return NS_OK;
00903 }
00904 
00905 NS_IMETHODIMP nsMsgDBView::GetSelection(nsITreeSelection * *aSelection)
00906 {
00907   *aSelection = mTreeSelection;
00908   NS_IF_ADDREF(*aSelection);
00909   return NS_OK;
00910 }
00911 
00912 NS_IMETHODIMP nsMsgDBView::SetSelection(nsITreeSelection * aSelection)
00913 {
00914   mTreeSelection = aSelection;
00915   return NS_OK;
00916 }
00917 
00918 NS_IMETHODIMP nsMsgDBView::ReloadMessageWithAllParts()
00919 {
00920   if (m_currentlyDisplayedMsgUri.IsEmpty() || mSuppressMsgDisplay)
00921     return NS_OK;
00922 
00923   nsCAutoString forceAllParts(m_currentlyDisplayedMsgUri);
00924   forceAllParts += (forceAllParts.FindChar('?') == kNotFound) ? "?" : "&";
00925   forceAllParts.AppendLiteral("fetchCompleteMessage=true");
00926   return mMessengerInstance->OpenURL(forceAllParts.get());
00927 }
00928 
00929 NS_IMETHODIMP nsMsgDBView::ReloadMessage()
00930 {
00931   if (m_currentlyDisplayedMsgUri.IsEmpty() || mSuppressMsgDisplay)
00932     return NS_OK;
00933 
00934   return mMessengerInstance->OpenURL(m_currentlyDisplayedMsgUri.get());
00935 }
00936 
00937 nsresult nsMsgDBView::UpdateDisplayMessage(nsMsgViewIndex viewPosition)
00938 {
00939   nsresult rv;
00940   if (mCommandUpdater)
00941   {
00942     // get the subject and the folder for the message and inform the front end that
00943     // we changed the message we are currently displaying.
00944     if (viewPosition != nsMsgViewIndex_None)
00945     {
00946       nsCOMPtr <nsIMsgDBHdr> msgHdr;
00947       rv = GetMsgHdrForViewIndex(viewPosition, getter_AddRefs(msgHdr));
00948       NS_ENSURE_SUCCESS(rv,rv);
00949 
00950       nsXPIDLString subject;
00951       FetchSubject(msgHdr, m_flags[viewPosition], getter_Copies(subject));
00952       
00953       nsXPIDLCString keywords;
00954       rv = msgHdr->GetStringProperty("keywords", getter_Copies(keywords));
00955       NS_ENSURE_SUCCESS(rv,rv);
00956 
00957       nsCOMPtr<nsIMsgFolder> folder = m_viewFolder ? m_viewFolder : m_folder;
00958 
00959       mCommandUpdater->DisplayMessageChanged(folder, subject, keywords);
00960 
00961       if (folder) 
00962       {
00963         rv = folder->SetLastMessageLoaded(m_keys[viewPosition]);
00964         NS_ENSURE_SUCCESS(rv,rv);
00965       }
00966     } // if view position is valid
00967   } // if we have an updater
00968   return NS_OK;
00969 }
00970 
00971 // given a msg key, we will load the message for it.
00972 NS_IMETHODIMP nsMsgDBView::LoadMessageByMsgKey(nsMsgKey aMsgKey)
00973 {
00974   return LoadMessageByViewIndex(FindKey(aMsgKey, PR_FALSE));
00975 }
00976 
00977 NS_IMETHODIMP nsMsgDBView::LoadMessageByViewIndex(nsMsgViewIndex aViewIndex)
00978 {
00979   NS_ASSERTION(aViewIndex != nsMsgViewIndex_None,"trying to load nsMsgViewIndex_None");
00980   if (aViewIndex == nsMsgViewIndex_None) return NS_ERROR_UNEXPECTED;
00981 
00982   nsXPIDLCString uri;
00983   nsresult rv = GetURIForViewIndex(aViewIndex, getter_Copies(uri));
00984   if (!mSuppressMsgDisplay && !m_currentlyDisplayedMsgUri.Equals(uri))
00985   {
00986     NS_ENSURE_SUCCESS(rv,rv);
00987 
00988     mMessengerInstance->OpenURL(uri);
00989     m_currentlyDisplayedMsgKey = m_keys[aViewIndex];
00990     m_currentlyDisplayedMsgUri = uri;
00991     m_currentlyDisplayedViewIndex = aViewIndex;
00992     UpdateDisplayMessage(m_currentlyDisplayedViewIndex);
00993   }
00994   return NS_OK;
00995 }
00996 
00997 NS_IMETHODIMP nsMsgDBView::LoadMessageByUrl(const char *aUrl)
00998 {
00999   NS_ASSERTION(aUrl, "trying to load a null url");
01000   if (!mSuppressMsgDisplay)
01001   {
01002     mMessengerInstance->LoadURL(NULL, aUrl);
01003     m_currentlyDisplayedMsgKey = nsMsgKey_None;
01004     m_currentlyDisplayedMsgUri.Truncate();
01005     m_currentlyDisplayedViewIndex = nsMsgViewIndex_None;
01006   }
01007   return NS_OK;
01008 }
01009 
01010 NS_IMETHODIMP nsMsgDBView::SelectionChanged()
01011 {
01012   // if the currentSelection changed then we have a message to display - not if we are in the middle of deleting rows
01013   if (m_deletingRows)
01014     return NS_OK;
01015 
01016   PRUint32 numSelected = 0;
01017 
01018   GetNumSelected(&numSelected);
01019   nsUInt32Array selection;
01020   GetSelectedIndices(&selection);
01021   nsMsgViewIndex *indices = selection.GetData();
01022   NS_ASSERTION(numSelected == selection.GetSize(), "selected indices is not equal to num of msg selected!!!");
01023 
01024   PRBool commandsNeedDisablingBecauseOfSelection = PR_FALSE;
01025 
01026   if(indices)
01027   {
01028     if (WeAreOffline())
01029       commandsNeedDisablingBecauseOfSelection = !OfflineMsgSelected(indices, numSelected);
01030     if (!NonDummyMsgSelected(indices, numSelected))
01031       commandsNeedDisablingBecauseOfSelection = PR_TRUE;
01032   }
01033   // if only one item is selected then we want to display a message
01034   if (numSelected == 1)
01035   {
01036     PRInt32 startRange;
01037     PRInt32 endRange;
01038     nsresult rv = mTreeSelection->GetRangeAt(0, &startRange, &endRange);
01039     NS_ENSURE_SUCCESS(rv, NS_OK); // tree doesn't care if we failed
01040 
01041     if (startRange >= 0 && startRange == endRange && startRange < GetSize())
01042     {
01043       if (!mRemovingRow)
01044       {
01045         if (!mSuppressMsgDisplay)
01046           LoadMessageByViewIndex(startRange);
01047         else
01048           UpdateDisplayMessage(startRange);
01049       }
01050     }
01051     else
01052       numSelected = 0; // selection seems bogus, so set to 0.
01053   }
01054   else {
01055     // if we have zero or multiple items selected, we shouldn't be displaying any message
01056     m_currentlyDisplayedMsgKey = nsMsgKey_None;
01057     m_currentlyDisplayedMsgUri.Truncate();
01058     m_currentlyDisplayedViewIndex = nsMsgViewIndex_None;
01059 
01060     // if we used to have one item selected, and now we have more than one, we should clear the message pane.
01061     nsCOMPtr <nsIMsgMessagePaneController> controller;
01062     if ((mNumSelectedRows == 1) && (numSelected > 1) && mMsgWindow && NS_SUCCEEDED(mMsgWindow->GetMessagePaneController(getter_AddRefs(controller))) && controller) {
01063       controller->ClearMsgPane();
01064     }
01065   }
01066 
01067   // determine if we need to push command update notifications out to the UI or not.
01068 
01069   // we need to push a command update notification iff, one of the following conditions are met
01070   // (1) the selection went from 0 to 1
01071   // (2) it went from 1 to 0
01072   // (3) it went from 1 to many
01073   // (4) it went from many to 1 or 0
01074   // (5) a different msg was selected - perhaps it was offline or not...matters only when we are offline
01075   // (6) we did a forward/back, or went from having no history to having history - not sure how to tell this.
01076 
01077   // I think we're going to need to keep track of whether forward/back were enabled/should be enabled,
01078   // and when this changes, force a command update.
01079   
01080   PRBool enableGoForward = PR_FALSE;
01081   PRBool enableGoBack = PR_FALSE;
01082 
01083   NavigateStatus(nsMsgNavigationType::forward, &enableGoForward);
01084   NavigateStatus(nsMsgNavigationType::back, &enableGoBack);
01085   if ((numSelected == mNumSelectedRows || 
01086       (numSelected > 1 && mNumSelectedRows > 1)) && (commandsNeedDisablingBecauseOfSelection == mCommandsNeedDisablingBecauseOfSelection)
01087       && enableGoForward == mGoForwardEnabled && enableGoBack == mGoBackEnabled)
01088   {
01089   } 
01090   // don't update commands if we're suppressing them, or if we're removing rows, unless it was the last row.
01091   else if (!mSuppressCommandUpdating && mCommandUpdater && (!mRemovingRow || GetSize() == 0)) // o.t. push an updated
01092   {
01093     mCommandUpdater->UpdateCommandStatus();
01094   }
01095   
01096   mCommandsNeedDisablingBecauseOfSelection = commandsNeedDisablingBecauseOfSelection;
01097   mGoForwardEnabled = enableGoForward;
01098   mGoBackEnabled = enableGoBack;
01099   mNumSelectedRows = numSelected;
01100   return NS_OK;
01101 }
01102 
01103 nsresult nsMsgDBView::GetSelectedIndices(nsUInt32Array *selection)
01104 {
01105   if (mTreeSelection)
01106   {
01107     PRInt32 selectionCount; 
01108     nsresult rv = mTreeSelection->GetRangeCount(&selectionCount);
01109     for (PRInt32 i = 0; i < selectionCount; i++)
01110     {
01111       PRInt32 startRange;
01112       PRInt32 endRange;
01113       rv = mTreeSelection->GetRangeAt(i, &startRange, &endRange);
01114       NS_ENSURE_SUCCESS(rv, NS_OK); 
01115       PRInt32 viewSize = GetSize();
01116       if (startRange >= 0 && startRange < viewSize)
01117       {
01118         for (PRInt32 rangeIndex = startRange; rangeIndex <= endRange && rangeIndex < viewSize; rangeIndex++)
01119           selection->Add(rangeIndex);
01120       }
01121     }
01122   }
01123   else
01124   {
01125     // if there is no tree selection object then we must be in stand alone message mode.
01126     // in that case the selected indices are really just the current message key.
01127     nsMsgViewIndex viewIndex = FindViewIndex(m_currentlyDisplayedMsgKey);
01128     if (viewIndex != nsMsgViewIndex_None)
01129       selection->Add(viewIndex);
01130   }
01131   return NS_OK;
01132 }
01133 
01134 NS_IMETHODIMP nsMsgDBView::GetRowProperties(PRInt32 index, nsISupportsArray *properties)
01135 {
01136   if (!IsValidIndex(index))
01137     return NS_MSG_INVALID_DBVIEW_INDEX; 
01138 
01139   // this is where we tell the tree to apply styles to a particular row
01140   nsCOMPtr <nsIMsgDBHdr> msgHdr;
01141   nsresult rv = NS_OK;
01142 
01143   rv = GetMsgHdrForViewIndex(index, getter_AddRefs(msgHdr));
01144 
01145   if (NS_FAILED(rv) || !msgHdr) {
01146     ClearHdrCache();
01147     return NS_MSG_INVALID_DBVIEW_INDEX;
01148   }
01149 
01150   nsXPIDLCString keywordProperty;
01151   FetchKeywords(msgHdr, getter_Copies(keywordProperty));
01152   if (!keywordProperty.IsEmpty())
01153     AppendKeywordProperties(keywordProperty.get(), properties, PR_FALSE);
01154 
01155   // give the custom column handlers a chance to style the row.
01156   for (int i = 0; i < m_customColumnHandlers.Count(); i++)
01157     m_customColumnHandlers[i]->GetRowProperties(index, properties);
01158 
01159   return NS_OK;
01160 }
01161 
01162 NS_IMETHODIMP nsMsgDBView::GetColumnProperties(nsITreeColumn* col, nsISupportsArray *properties)
01163 {
01164   return NS_OK;
01165 }
01166 
01167 NS_IMETHODIMP nsMsgDBView::GetCellProperties(PRInt32 aRow, nsITreeColumn *col, nsISupportsArray *properties)
01168 {
01169   if (!IsValidIndex(aRow))
01170     return NS_MSG_INVALID_DBVIEW_INDEX; 
01171 
01172   // this is where we tell the tree to apply styles to a particular row
01173   // i.e. if the row is an unread message...
01174 
01175   nsCOMPtr <nsIMsgDBHdr> msgHdr;
01176   nsresult rv = NS_OK;
01177 
01178   rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr));
01179 
01180   if (NS_FAILED(rv) || !msgHdr) 
01181   {
01182     ClearHdrCache();
01183     return NS_MSG_INVALID_DBVIEW_INDEX;
01184   }
01185 
01186   PRUint32 flags;
01187   msgHdr->GetFlags(&flags);
01188 
01189   if (!(flags & MSG_FLAG_READ))
01190     properties->AppendElement(kUnreadMsgAtom);  
01191   else 
01192     properties->AppendElement(kReadMsgAtom);  
01193 
01194   if (flags & MSG_FLAG_REPLIED)
01195     properties->AppendElement(kRepliedMsgAtom);
01196   
01197   if (flags & MSG_FLAG_FORWARDED)
01198     properties->AppendElement(kForwardedMsgAtom);
01199   
01200   if (flags & MSG_FLAG_NEW)
01201     properties->AppendElement(kNewMsgAtom);
01202 
01203   nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder);
01204 
01205   if ((flags & MSG_FLAG_OFFLINE) || (localFolder && !(flags & MSG_FLAG_PARTIAL)))
01206     properties->AppendElement(kOfflineMsgAtom);  
01207   
01208   if (flags & MSG_FLAG_ATTACHMENT) 
01209     properties->AppendElement(kAttachMsgAtom);
01210 
01211   if ((mDeleteModel == nsMsgImapDeleteModels::IMAPDelete) && (flags & MSG_FLAG_IMAP_DELETED)) 
01212     properties->AppendElement(kImapDeletedMsgAtom);
01213 
01214   if (mRedirectorTypeAtom)
01215     properties->AppendElement(mRedirectorTypeAtom);
01216 
01217   if (mMessageTypeAtom)
01218     properties->AppendElement(mMessageTypeAtom);
01219 
01220   nsXPIDLCString imageSize;
01221   msgHdr->GetStringProperty("imageSize", getter_Copies(imageSize));
01222   if (!imageSize.IsEmpty())
01223   {
01224     properties->AppendElement(kHasImageAtom);
01225   }
01226 
01227   nsXPIDLCString junkScoreStr;
01228   msgHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr));
01229   if (!junkScoreStr.IsEmpty())
01230   {
01231     // I set the cut off at 50. this may change
01232     // it works for our bayesian plugin, as "0" is good, and "100" is junk
01233     // but it might need tweaking for other plugins
01234     properties->AppendElement(atoi(junkScoreStr.get()) > 50 ? kJunkMsgAtom : kNotJunkMsgAtom);
01235   }
01236 
01237   nsXPIDLCString keywords;
01238 
01239   FetchKeywords(msgHdr, getter_Copies(keywords));
01240   if (!keywords.IsEmpty())
01241     AppendKeywordProperties(keywords.get(), properties, PR_TRUE);
01242 
01243   // this is a double fetch of the keywords property since we also fetch
01244   // it for the tags - do we want to do this?
01245   // I'm not sure anyone uses the kw- property, though it could be nice
01246   // for people wanting to extend the thread pane.
01247   nsXPIDLCString keywordProperty;
01248   msgHdr->GetStringProperty("keywords", getter_Copies(keywordProperty));
01249   if (!keywordProperty.IsEmpty())
01250   {
01251     nsCAutoString keywords(keywordProperty);
01252     nsCAutoString nextKeyword;
01253     PRInt32 spaceIndex = 0;
01254     do
01255     {
01256       spaceIndex = keywords.FindChar(' ');
01257       PRInt32 endOfKeyword = (spaceIndex == -1) ? keywords.Length() : spaceIndex;
01258       keywords.Left(nextKeyword, endOfKeyword);
01259       nextKeyword.Insert("kw-", 0);
01260       nsCOMPtr <nsIAtom> keywordAtom = do_GetAtom(nextKeyword.get());
01261       properties->AppendElement(keywordAtom);
01262       if (spaceIndex > 0)
01263         keywords.Cut(0, endOfKeyword + 1);
01264     }
01265     while (spaceIndex > 0);
01266   }
01267 
01268 #ifdef SUPPORT_PRIORITY_COLORS
01269   // add special styles for priority
01270   nsMsgPriorityValue priority;
01271   msgHdr->GetPriority(&priority);
01272   switch (priority)
01273   {
01274   case nsMsgPriority::highest:
01275     properties->AppendElement(kHighestPriorityAtom);
01276     break;
01277   case nsMsgPriority::high:
01278     properties->AppendElement(kHighPriorityAtom);
01279     break;
01280   case nsMsgPriority::low:
01281     properties->AppendElement(kLowPriorityAtom);
01282     break;
01283   case nsMsgPriority::lowest:
01284     properties->AppendElement(kLowestPriorityAtom);
01285     break;
01286   default:
01287     break;
01288   }
01289 #endif
01290 
01291   const PRUnichar* colID;
01292   col->GetIdConst(&colID);
01293   if (colID[0] == 'f')  
01294   {
01295     if (m_flags[aRow] & MSG_FLAG_MARKED) 
01296     {
01297       properties->AppendElement(kFlaggedMsgAtom); 
01298     }
01299   }
01300 
01301   if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
01302   {
01303     if (m_flags[aRow] & MSG_VIEW_FLAG_ISTHREAD)
01304     {
01305       nsCOMPtr <nsIMsgThread> thread;
01306       rv = GetThreadContainingIndex(aRow, getter_AddRefs(thread));
01307       if (NS_SUCCEEDED(rv) && thread)
01308       {
01309         PRUint32 numUnreadChildren;
01310         thread->GetNumUnreadChildren(&numUnreadChildren);
01311         if (numUnreadChildren > 0)
01312           properties->AppendElement(kHasUnreadAtom);
01313         thread->GetFlags(&flags);
01314         if (flags & MSG_FLAG_WATCHED) 
01315           properties->AppendElement(kWatchThreadAtom);
01316         if (flags & MSG_FLAG_IGNORED) 
01317           properties->AppendElement(kIgnoreThreadAtom);
01318       }
01319     }
01320   }     
01321 
01322   //custom column handlers are called at the end of getCellProperties
01323   //to make life easier for extension writers
01324   nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID);
01325 
01326   if (colHandler != nsnull) 
01327   {
01328     colHandler->GetCellProperties(aRow, col, properties);
01329     return NS_OK;
01330   }
01331   
01332   return NS_OK;
01333 }
01334 
01335 NS_IMETHODIMP nsMsgDBView::IsContainer(PRInt32 index, PRBool *_retval)
01336 {
01337   if (!IsValidIndex(index))
01338       return NS_MSG_INVALID_DBVIEW_INDEX; 
01339 
01340   if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
01341   {
01342     PRUint32 flags = m_flags[index];
01343     *_retval = (flags & MSG_VIEW_FLAG_HASCHILDREN);
01344   }
01345   else
01346     *_retval = PR_FALSE;
01347   return NS_OK;
01348 }
01349 
01350 NS_IMETHODIMP nsMsgDBView::IsContainerOpen(PRInt32 index, PRBool *_retval)
01351 {
01352   if (!IsValidIndex(index))
01353       return NS_MSG_INVALID_DBVIEW_INDEX; 
01354 
01355   if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
01356   {
01357     PRUint32 flags = m_flags[index];
01358     *_retval = (flags & MSG_VIEW_FLAG_HASCHILDREN) && !(flags & MSG_FLAG_ELIDED);
01359   }
01360   else
01361     *_retval = PR_FALSE;
01362   return NS_OK;
01363 }
01364 
01365 NS_IMETHODIMP nsMsgDBView::IsContainerEmpty(PRInt32 index, PRBool *_retval)
01366 {
01367   if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
01368   {
01369     PRUint32 flags = m_flags[index];
01370     *_retval = !(flags & MSG_VIEW_FLAG_HASCHILDREN);
01371   }
01372   else
01373     *_retval = PR_FALSE;
01374   return NS_OK;
01375 }
01376 
01377 NS_IMETHODIMP nsMsgDBView::IsSeparator(PRInt32 index, PRBool *_retval)
01378 {
01379   if (!IsValidIndex(index))
01380     return NS_MSG_INVALID_DBVIEW_INDEX;
01381 
01382   *_retval = PR_FALSE;
01383 
01384   return NS_OK;
01385 }
01386 
01387 NS_IMETHODIMP nsMsgDBView::GetParentIndex(PRInt32 rowIndex, PRInt32 *_retval)
01388 {  
01389   *_retval = -1;
01390 
01391   PRInt32 rowIndexLevel;
01392   GetLevel(rowIndex, &rowIndexLevel);
01393 
01394   PRInt32 i;
01395   for(i = rowIndex; i >= 0; i--) 
01396   {
01397     PRInt32 l;
01398     GetLevel(i, &l);
01399     if (l < rowIndexLevel) 
01400     {
01401       *_retval = i;
01402       break;
01403     }
01404   }
01405 
01406   return NS_OK;
01407 }
01408 
01409 NS_IMETHODIMP nsMsgDBView::HasNextSibling(PRInt32 rowIndex, PRInt32 afterIndex, PRBool *_retval)
01410 {
01411   *_retval = PR_FALSE;
01412 
01413   PRInt32 rowIndexLevel;
01414   GetLevel(rowIndex, &rowIndexLevel);
01415 
01416   PRInt32 i;
01417   PRInt32 count;
01418   GetRowCount(&count);
01419   for(i = afterIndex + 1; i < count; i++) 
01420   {
01421     PRInt32 l;
01422     GetLevel(i, &l);
01423     if (l < rowIndexLevel)
01424       break;
01425     if (l == rowIndexLevel) 
01426     {
01427       *_retval = PR_TRUE;
01428       break;
01429     }
01430   }                                                                       
01431 
01432   return NS_OK;
01433 }
01434 
01435 NS_IMETHODIMP nsMsgDBView::GetLevel(PRInt32 index, PRInt32 *_retval)
01436 {
01437   if (!IsValidIndex(index))
01438     return NS_MSG_INVALID_DBVIEW_INDEX;
01439 
01440   if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
01441     *_retval = m_levels[index];
01442   else
01443     *_retval = 0;
01444   return NS_OK;
01445 }
01446 
01447 // search view will override this since headers can span db's
01448 nsresult nsMsgDBView::GetMsgHdrForViewIndex(nsMsgViewIndex index, nsIMsgDBHdr **msgHdr)
01449 {
01450   nsresult rv = NS_OK;
01451   nsMsgKey key = m_keys.GetAt(index);
01452   if (key == nsMsgKey_None || !m_db)
01453     return NS_MSG_INVALID_DBVIEW_INDEX;
01454   
01455   if (key == m_cachedMsgKey)
01456   {
01457     *msgHdr = m_cachedHdr;
01458     NS_IF_ADDREF(*msgHdr);
01459   }
01460   else
01461   {
01462     rv = m_db->GetMsgHdrForKey(key, msgHdr);
01463     if (NS_SUCCEEDED(rv))
01464     {
01465       m_cachedHdr = *msgHdr;
01466       m_cachedMsgKey = key;
01467     }
01468   }
01469 
01470   return rv;
01471 }
01472 
01473 nsresult nsMsgDBView::GetFolderForViewIndex(nsMsgViewIndex index, nsIMsgFolder **aFolder)
01474 {
01475   NS_IF_ADDREF(*aFolder = m_folder);
01476   return NS_OK;
01477 }
01478 
01479 nsresult nsMsgDBView::GetDBForViewIndex(nsMsgViewIndex index, nsIMsgDatabase **db)
01480 {
01481   NS_IF_ADDREF(*db = m_db);
01482   return NS_OK;
01483 }
01484 
01485 NS_IMETHODIMP nsMsgDBView::GetImageSrc(PRInt32 aRow, nsITreeColumn* aCol, nsAString& aValue)
01486 {
01487   //attempt to retreive a custom column handler. If it exists call it and return
01488   const PRUnichar* colID;
01489   aCol->GetIdConst(&colID);
01490   
01491   nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID);
01492 
01493   if (colHandler) 
01494   {
01495        colHandler->GetImageSrc(aRow, aCol, aValue);
01496        return NS_OK;
01497   }
01498     
01499   return NS_OK;
01500 }
01501 
01502 NS_IMETHODIMP
01503 nsMsgDBView::GetProgressMode(PRInt32 aRow, nsITreeColumn* aCol, PRInt32* _retval)
01504 {
01505   return NS_OK;
01506 }
01507 
01508 NS_IMETHODIMP nsMsgDBView::GetCellValue(PRInt32 aRow, nsITreeColumn* aCol, nsAString& aValue)
01509 {
01510   return NS_OK;
01511 }
01512 
01513 //add a custom column handler
01514 NS_IMETHODIMP nsMsgDBView::AddColumnHandler(const nsAString& column, nsIMsgCustomColumnHandler* handler)
01515 {
01516 
01517   PRInt32 index = m_customColumnHandlerIDs.IndexOf(column);
01518     
01519   nsAutoString strColID(column);    
01520   
01521   //does not exist
01522   if (index == -1) 
01523   {
01524     m_customColumnHandlerIDs.AppendString(strColID);
01525     m_customColumnHandlers.AppendObject(handler);    
01526   }
01527   else
01528   {
01529     //insert new handler into the appropriate place in the COMPtr array
01530     //no need to replace the column ID (it's the same)
01531     m_customColumnHandlers.ReplaceObjectAt(handler, index);    
01532     
01533   }
01534   
01535   return NS_OK;
01536 }
01537 
01538 //remove a custom column handler
01539 NS_IMETHODIMP nsMsgDBView::RemoveColumnHandler(const nsAString& aColID)
01540 {
01541 
01542   PRInt32 index = m_customColumnHandlerIDs.IndexOf(aColID);
01543   
01544   if (index != -1)
01545   {
01546     m_customColumnHandlerIDs.RemoveStringAt(index);
01547     m_customColumnHandlers.RemoveObjectAt(index);
01548     
01549     return NS_OK;
01550   }
01551   
01552   return NS_ERROR_FAILURE; //can't remove a column that isn't currently custom handled
01553 }  
01554 
01555 //TODO: NS_ENSURE_SUCCESS
01556 nsIMsgCustomColumnHandler* nsMsgDBView::GetCurColumnHandlerFromDBInfo()
01557 {
01558   if (!m_db)
01559     return nsnull;  
01560     
01561   nsresult rv;
01562   
01563   nsCOMPtr<nsIDBFolderInfo>  dbInfo;
01564   
01565   m_db->GetDBFolderInfo(getter_AddRefs(dbInfo));
01566   
01567   if (!dbInfo)
01568     return nsnull;
01569   
01570   nsAutoString colID;
01571   rv = dbInfo->GetProperty("customSortCol", colID);            
01572 
01573   return GetColumnHandler(colID.get());
01574 }
01575 
01576 nsIMsgCustomColumnHandler* nsMsgDBView::GetColumnHandler(const PRUnichar *colID)
01577 {
01578   nsIMsgCustomColumnHandler* columnHandler = nsnull;
01579 
01580   PRInt32 index = m_customColumnHandlerIDs.IndexOf(nsDependentString(colID));
01581   
01582   if (index > -1)
01583     columnHandler = m_customColumnHandlers[index];
01584   
01585   return columnHandler;
01586 }  
01587 
01588 NS_IMETHODIMP nsMsgDBView::GetColumnHandler(const nsAString& aColID, nsIMsgCustomColumnHandler** aHandler)
01589 {
01590   NS_ENSURE_ARG_POINTER(aHandler);
01591   nsAutoString column(aColID);
01592   NS_IF_ADDREF(*aHandler = GetColumnHandler(column.get()));
01593   return (*aHandler) ? NS_OK : NS_ERROR_FAILURE;
01594 }
01595 
01596 NS_IMETHODIMP nsMsgDBView::GetCellText(PRInt32 aRow, nsITreeColumn* aCol, nsAString& aValue)
01597 {
01598   nsresult rv = NS_OK;
01599 
01600   if (!IsValidIndex(aRow))
01601     return NS_MSG_INVALID_DBVIEW_INDEX;
01602 
01603   nsCOMPtr <nsIMsgDBHdr> msgHdr;
01604   rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr));
01605   
01606   if (NS_FAILED(rv) || !msgHdr) 
01607   {
01608     ClearHdrCache();
01609     return NS_MSG_INVALID_DBVIEW_INDEX;
01610   }
01611 
01612   aValue.SetCapacity(0);
01613   // XXX fix me by making Fetch* take an nsAString& parameter
01614   nsXPIDLString valueText;
01615   nsCOMPtr <nsIMsgThread> thread;
01616 
01617   const PRUnichar* colID;
01618   aCol->GetIdConst(&colID);
01619   
01620   //attempt to retreive a custom column handler. If it exists call it and return
01621   nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID);
01622       
01623   if (colHandler) 
01624   {
01625        colHandler->GetCellText(aRow, aCol, aValue);
01626        return NS_OK;
01627   }
01628   
01629   switch (colID[0])
01630   {
01631   case 's':
01632     if (colID[1] == 'u') // subject
01633       rv = FetchSubject(msgHdr, m_flags[aRow], getter_Copies(valueText));
01634     else if (colID[1] == 'e') // sender
01635       rv = FetchAuthor(msgHdr, getter_Copies(valueText));
01636     else if (colID[1] == 'i') // size
01637       rv = FetchSize(msgHdr, getter_Copies(valueText));
01638     else if (colID[1] == 't') // status
01639     {
01640       PRUint32 flags;
01641       msgHdr->GetFlags(&flags);
01642       rv = FetchStatus(flags, getter_Copies(valueText));
01643     }
01644     aValue.Assign(valueText);
01645     break;
01646   case 'r': // recipient
01647     rv = FetchRecipients(msgHdr, getter_Copies(valueText));
01648     aValue.Assign(valueText);
01649     break;
01650   case 'd':  // date
01651     rv = FetchDate(msgHdr, getter_Copies(valueText));
01652     aValue.Assign(valueText);
01653     break;
01654   case 'p': // priority
01655     rv = FetchPriority(msgHdr, getter_Copies(valueText));
01656     aValue.Assign(valueText);
01657     break;
01658   case 'a': // account
01659     if (colID[1] == 'c') // account
01660     {
01661       rv = FetchAccount(msgHdr, getter_Copies(valueText));
01662       aValue.Assign(valueText);
01663     }
01664     break;
01665   case 't':   
01666     // total msgs in thread column
01667     if (colID[1] == 'o' && (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
01668     {
01669       if (m_flags[aRow] & MSG_VIEW_FLAG_ISTHREAD)
01670       {
01671         rv = GetThreadContainingIndex(aRow, getter_AddRefs(thread));
01672         if (NS_SUCCEEDED(rv) && thread)
01673         {
01674           nsAutoString formattedCountString;
01675           PRUint32 numChildren;
01676           thread->GetNumChildren(&numChildren);
01677           formattedCountString.AppendInt(numChildren);
01678           aValue.Assign(formattedCountString);
01679         }
01680       }
01681     }
01682     else if (colID[1] == 'a') // tags
01683     {
01684       rv = FetchTags(msgHdr, getter_Copies(valueText));
01685       aValue.Assign(valueText);
01686     }
01687     break;
01688   case 'u':
01689     // unread msgs in thread col
01690     if (colID[6] == 'C' && (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
01691     {
01692       if (m_flags[aRow] & MSG_VIEW_FLAG_ISTHREAD)
01693       {
01694         rv = GetThreadContainingIndex(aRow, getter_AddRefs(thread));
01695         if (NS_SUCCEEDED(rv) && thread)
01696         {
01697           nsAutoString formattedCountString;
01698           PRUint32 numUnreadChildren;
01699           thread->GetNumUnreadChildren(&numUnreadChildren);
01700           if (numUnreadChildren > 0)
01701           {
01702             formattedCountString.AppendInt(numUnreadChildren);
01703             aValue.Assign(formattedCountString);
01704           }
01705         }
01706       }
01707     }
01708     break;
01709   case 'j':
01710     {
01711       nsXPIDLCString junkScoreStr;
01712       msgHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr));
01713       CopyASCIItoUCS2(junkScoreStr, aValue);
01714     }
01715     break;
01716   case 'i': // id
01717     {
01718       nsAutoString keyString;
01719       nsMsgKey key;
01720       msgHdr->GetMessageKey(&key);
01721       keyString.AppendInt(key);
01722       aValue.Assign(keyString);
01723     }
01724   default:
01725     break;
01726   }
01727 
01728   return NS_OK;
01729 }
01730 
01731 NS_IMETHODIMP nsMsgDBView::SetTree(nsITreeBoxObject *tree)
01732 {
01733   mTree = tree;
01734   return NS_OK;
01735 }
01736 
01737 NS_IMETHODIMP nsMsgDBView::ToggleOpenState(PRInt32 index)
01738 {
01739   PRUint32 numChanged;
01740   nsresult rv = ToggleExpansion(index, &numChanged);
01741   NS_ENSURE_SUCCESS(rv,rv);
01742   return NS_OK;
01743 }
01744 
01745 NS_IMETHODIMP nsMsgDBView::CycleHeader(nsITreeColumn* aCol)
01746 {
01747     // let HandleColumnClick() in threadPane.js handle it
01748     // since it will set / clear the sort indicators.
01749     return NS_OK;
01750 }
01751 
01752 NS_IMETHODIMP nsMsgDBView::CycleCell(PRInt32 row, nsITreeColumn* col)
01753 {
01754   if (!IsValidIndex(row))
01755     return NS_MSG_INVALID_DBVIEW_INDEX;
01756 
01757   const PRUnichar* colID;
01758   col->GetIdConst(&colID);
01759   
01760   //attempt to retreive a custom column handler. If it exists call it and return
01761   nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID);
01762       
01763   if (colHandler) 
01764   {
01765        colHandler->CycleCell(row, col);
01766        return NS_OK;
01767   }  
01768   
01769   switch (colID[0])
01770   {
01771   case 'u': // unreadButtonColHeader
01772     if (colID[6] == 'B') 
01773       ApplyCommandToIndices(nsMsgViewCommandType::toggleMessageRead, (nsMsgViewIndex *) &row, 1);
01774    break;
01775   case 't': // tag cell, threaded cell or total cell
01776     if (colID[1] == 'h') 
01777     {
01778       ExpandAndSelectThreadByIndex(row, PR_FALSE);
01779     }
01780     else if (colID[1] == 'a')
01781     {
01782       // ### Do we want to keep this behaviour but switch it to tags?
01783       // We could enumerate over the tags and go to the next one - it looks
01784       // to me like this wasn't working before tags landed, so maybe not
01785       // worth bothering with.
01786     }
01787     break;
01788   case 'f': // flagged column
01789     // toggle the flagged status of the element at row.
01790     if (m_flags[row] & MSG_FLAG_MARKED)
01791       ApplyCommandToIndices(nsMsgViewCommandType::unflagMessages, (nsMsgViewIndex *) &row, 1);
01792     else
01793       ApplyCommandToIndices(nsMsgViewCommandType::flagMessages, (nsMsgViewIndex *) &row, 1);
01794     break;
01795   case 'j': // junkStatus column
01796     {
01797       if (mIsNews) // junk not supported for news yet.
01798         return NS_OK;
01799 
01800       nsCOMPtr <nsIMsgDBHdr> msgHdr;
01801 
01802       nsresult rv = GetMsgHdrForViewIndex(row, getter_AddRefs(msgHdr));
01803       if (NS_SUCCEEDED(rv) && msgHdr) 
01804       {
01805         nsXPIDLCString junkScoreStr;
01806         rv = msgHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr));
01807         if (junkScoreStr.IsEmpty() || (atoi(junkScoreStr.get()) < 50))
01808           ApplyCommandToIndices(nsMsgViewCommandType::junk, (nsMsgViewIndex *) &row, 1);
01809         else
01810           ApplyCommandToIndices(nsMsgViewCommandType::unjunk, (nsMsgViewIndex *) &row, 1);
01811       }
01812     }
01813     break;
01814   default:
01815     break;
01816 
01817   }
01818   return NS_OK;
01819 }
01820 
01821 NS_IMETHODIMP nsMsgDBView::PerformAction(const PRUnichar *action)
01822 {
01823   return NS_OK;
01824 }
01825 
01826 NS_IMETHODIMP nsMsgDBView::PerformActionOnRow(const PRUnichar *action, PRInt32 row)
01827 {
01828   return NS_OK;
01829 }
01830 
01831 NS_IMETHODIMP nsMsgDBView::PerformActionOnCell(const PRUnichar *action, PRInt32 row, nsITreeColumn* col)
01832 {
01833   return NS_OK;
01834 }
01835 
01837 // end nsITreeView Implementation Methods
01839 
01840 NS_IMETHODIMP nsMsgDBView::Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, nsMsgViewFlagsTypeValue viewFlags, PRInt32 *pCount)
01841 {
01842   m_viewFlags = viewFlags;
01843   m_sortOrder = sortOrder;
01844   m_sortType = sortType;
01845 
01846   nsresult rv;
01847   nsCOMPtr<nsIMsgAccountManager> accountManager = 
01848            do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
01849   NS_ENSURE_SUCCESS(rv, rv);
01850   PRBool userNeedsToAuthenticate = PR_FALSE;
01851   // if we're PasswordProtectLocalCache, then we need to find out if the server is authenticated.
01852   (void) accountManager->GetUserNeedsToAuthenticate(&userNeedsToAuthenticate);
01853   if (userNeedsToAuthenticate)
01854     return NS_MSG_USER_NOT_AUTHENTICATED;
01855 
01856   if (folder) // search view will have a null folder
01857   {
01858 
01859     nsCOMPtr <nsIDBFolderInfo> folderInfo;
01860     rv = folder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(m_db));
01861     NS_ENSURE_SUCCESS(rv,rv);
01862     m_db->AddListener(this);
01863     m_folder = folder;
01864     m_viewFolder = folder;
01865 
01866     SetMRUTimeForFolder(m_folder);
01867 
01868     // determine if we are in a news folder or not.
01869     // if yes, we'll show lines instead of size, and special icons in the thread pane
01870     nsCOMPtr <nsIMsgIncomingServer> server;
01871     rv = folder->GetServer(getter_AddRefs(server));
01872     NS_ENSURE_SUCCESS(rv,rv);
01873     nsXPIDLCString type;
01874     rv = server->GetType(getter_Copies(type));
01875     NS_ENSURE_SUCCESS(rv,rv);
01876 
01877     // turn the redirector type into an atom
01878     nsXPIDLCString redirectorType;
01879     rv = server->GetRedirectorType(getter_Copies(redirectorType));
01880     NS_ENSURE_SUCCESS(rv,rv);
01881     if (redirectorType.IsEmpty())
01882       mRedirectorTypeAtom = nsnull;
01883     else
01884       mRedirectorTypeAtom = do_GetAtom(redirectorType.get());
01885 
01886     mIsNews = !strcmp("nntp",type.get());
01887 
01888     if (type.IsEmpty())
01889       mMessageTypeAtom = nsnull;
01890     else  // special case nntp --> news since we'll break themes if we try to be consistent
01891       mMessageTypeAtom = do_GetAtom(mIsNews ? "news" : type.get());
01892 
01893     GetImapDeleteModel(nsnull);
01894 
01895     if (mIsNews)
01896     {
01897       nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
01898       if (prefs)
01899       {
01900         PRBool temp;
01901         rv = prefs->GetBoolPref("news.show_size_in_lines", &temp);
01902         if (NS_SUCCEEDED(rv))
01903           mShowSizeInLines = temp;
01904       }
01905     }
01906   }
01907   return NS_OK;
01908 }
01909 
01910 NS_IMETHODIMP nsMsgDBView::Close()
01911 {
01912   PRInt32 oldSize = GetSize();
01913   // this is important, because the tree will ask us for our
01914   // row count, which get determine from the number of keys.
01915   m_keys.RemoveAll();
01916   // be consistent
01917   m_flags.RemoveAll();
01918   m_levels.RemoveAll();
01919 
01920   // clear these out since they no longer apply if we're switching a folder
01921   nsMemory::Free(mJunkIndices);
01922   mJunkIndices = nsnull;
01923   mNumJunkIndices = 0;
01924 
01925   // this needs to happen after we remove all the keys, since RowCountChanged() will call our GetRowCount()
01926   if (mTree) 
01927     mTree->RowCountChanged(0, -oldSize);
01928 
01929   ClearHdrCache();
01930   if (m_db)
01931   {
01932     m_db->RemoveListener(this);
01933     m_db = nsnull;
01934   }
01935   return NS_OK;
01936 }
01937 
01938 NS_IMETHODIMP nsMsgDBView::OpenWithHdrs(nsISimpleEnumerator *aHeaders, nsMsgViewSortTypeValue aSortType, 
01939                                         nsMsgViewSortOrderValue aSortOrder, nsMsgViewFlagsTypeValue aViewFlags, 
01940                                         PRInt32 *aCount)
01941 {
01942   NS_ASSERTION(PR_FALSE, "not implemented");
01943   return NS_ERROR_NOT_IMPLEMENTED;
01944 }
01945 
01946 NS_IMETHODIMP nsMsgDBView::Init(nsIMessenger * aMessengerInstance, nsIMsgWindow * aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater)
01947 {
01948   mMsgWindow = aMsgWindow;
01949   mMessengerInstance = aMessengerInstance;
01950   mCommandUpdater = aCmdUpdater;
01951 
01952   return NS_OK;
01953 }
01954 
01955 NS_IMETHODIMP nsMsgDBView::SetSuppressCommandUpdating(PRBool aSuppressCommandUpdating)
01956 {
01957   mSuppressCommandUpdating = aSuppressCommandUpdating;
01958   return NS_OK;
01959 }
01960 
01961 NS_IMETHODIMP nsMsgDBView::GetSuppressCommandUpdating(PRBool * aSuppressCommandUpdating)
01962 {
01963   *aSuppressCommandUpdating = mSuppressCommandUpdating;
01964   return NS_OK;
01965 }
01966 
01967 NS_IMETHODIMP nsMsgDBView::SetSuppressMsgDisplay(PRBool aSuppressDisplay)
01968 {
01969   PRBool forceDisplay = PR_FALSE;
01970   if (mSuppressMsgDisplay && (mSuppressMsgDisplay != aSuppressDisplay))
01971     forceDisplay = PR_TRUE;
01972 
01973   mSuppressMsgDisplay = aSuppressDisplay;
01974   if (forceDisplay)
01975   {
01976     // get the view indexfor the currently selected message
01977     nsMsgViewIndex viewIndex;
01978     nsresult rv = GetViewIndexForFirstSelectedMsg(&viewIndex);
01979     if (NS_SUCCEEDED(rv) && viewIndex != nsMsgViewIndex_None)
01980        LoadMessageByViewIndex(viewIndex);
01981   }
01982 
01983   return NS_OK;
01984 }
01985 
01986 NS_IMETHODIMP nsMsgDBView::GetSuppressMsgDisplay(PRBool * aSuppressDisplay)
01987 {
01988   *aSuppressDisplay = mSuppressMsgDisplay;
01989   return NS_OK;
01990 }
01991 
01992 NS_IMETHODIMP nsMsgDBView::GetUsingLines(PRBool * aUsingLines)
01993 {
01994   *aUsingLines = mShowSizeInLines;
01995   return NS_OK;
01996 }
01997 
01998 int PR_CALLBACK CompareViewIndices (const void *v1, const void *v2, void *)
01999 {
02000        nsMsgViewIndex i1 = *(nsMsgViewIndex*) v1;
02001        nsMsgViewIndex i2 = *(nsMsgViewIndex*) v2;
02002        return i1 - i2;
02003 }
02004 
02005 NS_IMETHODIMP nsMsgDBView::GetIndicesForSelection(nsMsgViewIndex **indices,  PRUint32 *length)
02006 {
02007   NS_ENSURE_ARG_POINTER(length);
02008   *length = 0;
02009   NS_ENSURE_ARG_POINTER(indices);
02010   *indices = nsnull;
02011 
02012   nsUInt32Array selection;
02013   GetSelectedIndices(&selection);
02014   *length = selection.GetSize();
02015   PRUint32 numIndicies = *length;
02016   if (!numIndicies) return NS_OK;
02017 
02018   *indices = (nsMsgViewIndex *)nsMemory::Alloc(numIndicies * sizeof(nsMsgViewIndex));
02019   if (!*indices) return NS_ERROR_OUT_OF_MEMORY;
02020   for (PRUint32 i=0;i<numIndicies;i++) {
02021     (*indices)[i] = selection.GetAt(i);
02022   }
02023   return NS_OK;
02024 }
02025 
02026 NS_IMETHODIMP nsMsgDBView::GetURIsForSelection(char ***uris, PRUint32 *length)
02027 {
02028   nsresult rv = NS_OK;
02029 
02030   NS_ENSURE_ARG_POINTER(length);
02031   *length = 0;
02032   NS_ENSURE_ARG_POINTER(uris);
02033   *uris = nsnull;
02034 
02035   nsUInt32Array selection;
02036   GetSelectedIndices(&selection);
02037   *length = selection.GetSize();
02038   PRUint32 numIndicies = *length;
02039   if (!numIndicies) return NS_OK;
02040 
02041   nsCOMPtr <nsIMsgFolder> folder = m_folder;
02042   char **outArray, **next;
02043   next = outArray = (char **)nsMemory::Alloc(numIndicies * sizeof(char *));
02044   if (!outArray) return NS_ERROR_OUT_OF_MEMORY;
02045   for (PRUint32 i=0;i<numIndicies;i++) 
02046   {
02047     nsMsgViewIndex selectedIndex = selection.GetAt(i);
02048     if (!m_folder) // must be a cross folder view, like search results
02049       GetFolderForViewIndex(selectedIndex, getter_AddRefs(folder));
02050     rv = GenerateURIForMsgKey(m_keys[selectedIndex], folder, next);
02051     NS_ENSURE_SUCCESS(rv,rv);
02052     if (!*next) return NS_ERROR_OUT_OF_MEMORY;
02053     next++;
02054   }
02055 
02056   *uris = outArray;
02057   return NS_OK;
02058 }
02059 
02060 NS_IMETHODIMP nsMsgDBView::GetURIForViewIndex(nsMsgViewIndex index, char **result)
02061 {
02062   nsresult rv;
02063   nsCOMPtr <nsIMsgFolder> folder = m_folder;
02064   if (!folder)
02065   {
02066     rv = GetFolderForViewIndex(index, getter_AddRefs(folder));
02067     NS_ENSURE_SUCCESS(rv,rv);
02068   }
02069   if (index == nsMsgViewIndex_None || m_flags[index] & MSG_VIEW_FLAG_DUMMY)
02070     return NS_MSG_INVALID_DBVIEW_INDEX;
02071   return GenerateURIForMsgKey(m_keys[index], folder, result);
02072 }
02073 
02074 NS_IMETHODIMP nsMsgDBView::DoCommandWithFolder(nsMsgViewCommandTypeValue command, nsIMsgFolder *destFolder)
02075 {
02076   nsUInt32Array selection;
02077 
02078   NS_ENSURE_ARG_POINTER(destFolder);
02079 
02080   GetSelectedIndices(&selection);
02081 
02082   nsMsgViewIndex *indices = selection.GetData();
02083   PRInt32 numIndices = selection.GetSize();
02084 
02085   nsresult rv = NS_OK;
02086   switch (command) {
02087     case nsMsgViewCommandType::copyMessages:
02088     case nsMsgViewCommandType::moveMessages:
02089         // since the FE could have constructed the list of indices in
02090         // any order (e.g. order of discontiguous selection), we have to
02091         // sort the indices in order to find out which nsMsgViewIndex will
02092         // be deleted first.
02093         if (numIndices > 1)
02094           NS_QuickSort(indices, numIndices, sizeof(nsMsgViewIndex), CompareViewIndices, nsnull);
02095         NoteStartChange(nsMsgViewNotificationCode::none, 0, 0);
02096         rv = ApplyCommandToIndicesWithFolder(command, indices, numIndices, destFolder);
02097         NoteEndChange(nsMsgViewNotificationCode::none, 0, 0);
02098         break;
02099     default:
02100         NS_ASSERTION(PR_FALSE, "invalid command type");
02101         rv = NS_ERROR_UNEXPECTED;
02102         break;
02103   }
02104   return rv;
02105 
02106 }
02107 
02108 NS_IMETHODIMP nsMsgDBView::DoCommand(nsMsgViewCommandTypeValue command)
02109 {
02110   nsUInt32Array selection;
02111 
02112   GetSelectedIndices(&selection);
02113 
02114   nsMsgViewIndex *indices = selection.GetData();
02115   PRInt32 numIndices = selection.GetSize();
02116 
02117   nsresult rv = NS_OK;
02118   switch (command)
02119   {
02120 
02121   case nsMsgViewCommandType::downloadSelectedForOffline:
02122     return DownloadForOffline(mMsgWindow, indices, numIndices);
02123   case nsMsgViewCommandType::downloadFlaggedForOffline:
02124     return DownloadFlaggedForOffline(mMsgWindow);
02125   case nsMsgViewCommandType::markMessagesRead:
02126   case nsMsgViewCommandType::markMessagesUnread:
02127   case nsMsgViewCommandType::toggleMessageRead:
02128   case nsMsgViewCommandType::flagMessages:
02129   case nsMsgViewCommandType::unflagMessages:
02130   case nsMsgViewCommandType::deleteMsg:
02131   case nsMsgViewCommandType::undeleteMsg:
02132   case nsMsgViewCommandType::deleteNoTrash:
02133   case nsMsgViewCommandType::markThreadRead:
02134   case nsMsgViewCommandType::junk:
02135   case nsMsgViewCommandType::unjunk:
02136     // since the FE could have constructed the list of indices in
02137     // any order (e.g. order of discontiguous selection), we have to
02138     // sort the indices in order to find out which nsMsgViewIndex will
02139     // be deleted first.
02140     if (numIndices > 1)
02141       NS_QuickSort (indices, numIndices, sizeof(nsMsgViewIndex), CompareViewIndices, nsnull);
02142     NoteStartChange(nsMsgViewNotificationCode::none, 0, 0);
02143     rv = ApplyCommandToIndices(command, indices, numIndices);
02144     NoteEndChange(nsMsgViewNotificationCode::none, 0, 0);
02145     break;
02146   case nsMsgViewCommandType::selectAll:
02147     if (mTreeSelection && mTree) 
02148     {
02149         // if in threaded mode, we need to expand all before selecting
02150         if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
02151             rv = ExpandAll();
02152         mTreeSelection->SelectAll();
02153         mTree->Invalidate();
02154     }
02155     break;
02156   case nsMsgViewCommandType::selectThread:
02157     rv = ExpandAndSelectThread();
02158     break;
02159   case nsMsgViewCommandType::selectFlagged:
02160     if (!mTreeSelection)
02161       rv = NS_ERROR_UNEXPECTED;
02162     else
02163     {
02164       mTreeSelection->SetSelectEventsSuppressed(PR_TRUE);
02165       mTreeSelection->ClearSelection();
02166       // XXX ExpandAll?
02167       nsMsgViewIndex numIndices = GetSize();
02168       for (nsMsgViewIndex curIndex = 0; curIndex < numIndices; curIndex++)
02169       {
02170         if (m_flags.GetAt(curIndex) & MSG_FLAG_MARKED)
02171           mTreeSelection->ToggleSelect(curIndex);
02172       }
02173       mTreeSelection->SetSelectEventsSuppressed(PR_FALSE);
02174     }
02175     break;
02176   case nsMsgViewCommandType::markAllRead:
02177     if (m_folder)
02178       rv = m_folder->MarkAllMessagesRead();
02179     break;
02180   case nsMsgViewCommandType::toggleThreadWatched:
02181     rv = ToggleWatched(indices,    numIndices);
02182     break;
02183   case nsMsgViewCommandType::expandAll:
02184     rv = ExpandAll();
02185     m_viewFlags |= nsMsgViewFlagsType::kExpandAll;
02186     SetViewFlags(m_viewFlags);
02187     NS_ASSERTION(mTree, "no tree, see bug #114956");
02188     if(mTree)
02189       mTree->Invalidate();
02190     break;
02191   case nsMsgViewCommandType::collapseAll:
02192     rv = CollapseAll();
02193     m_viewFlags &= ~nsMsgViewFlagsType::kExpandAll;
02194     SetViewFlags(m_viewFlags);
02195     NS_ASSERTION(mTree, "no tree, see bug #114956");
02196     if(mTree)
02197       mTree->Invalidate();
02198     break;
02199   default:
02200     NS_ASSERTION(PR_FALSE, "invalid command type");
02201     rv = NS_ERROR_UNEXPECTED;
02202     break;
02203   }
02204   return rv;
02205 }
02206 
02207 PRBool nsMsgDBView::ServerSupportsFilterAfterTheFact()
02208 {
02209   if (!m_folder)  // cross folder virtual folders might not have a folder set.
02210     return PR_FALSE; 
02211 
02212   // can't manually run news filters yet
02213   if (mIsNews)
02214     return PR_FALSE;
02215 
02216   nsCOMPtr <nsIMsgIncomingServer> server;
02217   nsresult rv = m_folder->GetServer(getter_AddRefs(server));
02218   if (NS_FAILED(rv))
02219     return PR_FALSE; // unexpected
02220 
02221   // filter after the fact is implement using search
02222   // so if you can't search, you can't filter after the fact
02223   PRBool canSearch;
02224   rv = server->GetCanSearchMessages(&canSearch);
02225   if (NS_FAILED(rv))
02226     return PR_FALSE; // unexpected
02227 
02228   return canSearch;
02229 }
02230 
02231 NS_IMETHODIMP nsMsgDBView::GetCommandStatus(nsMsgViewCommandTypeValue command, PRBool *selectable_p, nsMsgViewCommandCheckStateValue *selected_p)
02232 {
02233   nsresult rv = NS_OK;
02234 
02235   PRBool haveSelection;
02236   PRInt32 rangeCount;
02237   nsUInt32Array selection;
02238   GetSelectedIndices(&selection);
02239   PRInt32 numIndices = selection.GetSize();
02240   nsMsgViewIndex *indices = selection.GetData();
02241   // if range count is non-zero, we have at least one item selected, so we have a selection
02242   if (mTreeSelection && NS_SUCCEEDED(mTreeSelection->GetRangeCount(&rangeCount)) && rangeCount > 0)
02243     haveSelection = NonDummyMsgSelected(indices, numIndices);
02244   else 
02245     haveSelection = PR_FALSE;
02246 
02247   switch (command)
02248   {
02249   case nsMsgViewCommandType::deleteMsg:
02250   case nsMsgViewCommandType::deleteNoTrash:
02251     {
02252       PRBool canDelete;
02253       // news folders can't delete (or move messages)
02254       // but we use delete for cancel messages.
02255       if (m_folder && !mIsNews && NS_SUCCEEDED(m_folder->GetCanDeleteMessages(&canDelete)) && !canDelete)
02256         *selectable_p = PR_FALSE;
02257       else
02258         *selectable_p = haveSelection;
02259     }
02260     break;
02261   case nsMsgViewCommandType::applyFilters:
02262     // disable if no messages
02263     // XXX todo, check that we have filters, and at least one is enabled
02264     *selectable_p = GetSize();  
02265     if (*selectable_p)
02266       *selectable_p = ServerSupportsFilterAfterTheFact();
02267     break;
02268   case nsMsgViewCommandType::runJunkControls:
02269     // disable if no messages
02270     // no JMC on news yet
02271     // XXX todo, check that we have JMC enabled?
02272     *selectable_p = GetSize() && !mIsNews; 
02273     break;
02274   case nsMsgViewCommandType::deleteJunk:
02275     {
02276       // disable if no messages, or if we can't delete (like news and certain imap folders)
02277       PRBool canDelete;
02278       *selectable_p = GetSize() && (m_folder && NS_SUCCEEDED(m_folder->GetCanDeleteMessages(&canDelete)) && canDelete);
02279     }
02280     break;
02281   case nsMsgViewCommandType::markMessagesRead:
02282   case nsMsgViewCommandType::markMessagesUnread:
02283   case nsMsgViewCommandType::toggleMessageRead:
02284   case nsMsgViewCommandType::flagMessages:
02285   case nsMsgViewCommandType::unflagMessages:
02286   case nsMsgViewCommandType::toggleThreadWatched:
02287   case nsMsgViewCommandType::markThreadRead:
02288   case nsMsgViewCommandType::downloadSelectedForOffline:
02289     *selectable_p = haveSelection;
02290     break;
02291   case nsMsgViewCommandType::junk:
02292   case nsMsgViewCommandType::unjunk:
02293     *selectable_p = haveSelection && !mIsNews;  // no junk for news yet
02294     break;
02295   case nsMsgViewCommandType::cmdRequiringMsgBody:
02296     *selectable_p = haveSelection && (!WeAreOffline() || OfflineMsgSelected(indices, numIndices));
02297     break;
02298   case nsMsgViewCommandType::downloadFlaggedForOffline:
02299   case nsMsgViewCommandType::markAllRead:
02300     *selectable_p = PR_TRUE;
02301     break;
02302   default:
02303     NS_ASSERTION(PR_FALSE, "invalid command type");
02304     rv = NS_ERROR_FAILURE;
02305   }
02306   return rv;
02307 }
02308 
02309 nsresult 
02310 nsMsgDBView::CopyMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, PRInt32 numIndices, PRBool isMove, nsIMsgFolder *destFolder)
02311 {
02312   if (m_deletingRows)
02313   {
02314     NS_ASSERTION(PR_FALSE, "Last move did not complete");
02315     return NS_OK;
02316   }
02317   
02318   m_deletingRows = isMove && mDeleteModel != nsMsgImapDeleteModels::IMAPDelete;
02319 
02320   nsresult rv;
02321   NS_ENSURE_ARG_POINTER(destFolder);
02322   nsCOMPtr<nsISupportsArray> messageArray;
02323   NS_NewISupportsArray(getter_AddRefs(messageArray));
02324   for (nsMsgViewIndex index = 0; index < (nsMsgViewIndex) numIndices; index++) 
02325   {
02326     nsMsgKey key;
02327     nsMsgViewIndex viewIndex = indices[index];
02328     if (viewIndex == nsMsgViewIndex_None)
02329       continue;
02330     key = m_keys.GetAt(viewIndex);
02331     nsCOMPtr <nsIMsgDBHdr> msgHdr;
02332     rv = m_db->GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
02333     if (NS_SUCCEEDED(rv) && msgHdr)
02334     {
02335       messageArray->AppendElement(msgHdr);
02336       // if we are deleting rows, save off the keys
02337       if (m_deletingRows)
02338         mIndicesToNoteChange.Add(indices[index]);
02339     }
02340   }
02341   
02342   nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
02343   NS_ENSURE_SUCCESS(rv, rv);
02344   return copyService->CopyMessages(m_folder /* source folder */, messageArray, destFolder, isMove, nsnull /* listener */, window, PR_TRUE /*allowUndo*/);
02345 }
02346 
02347 nsresult
02348 nsMsgDBView::ApplyCommandToIndicesWithFolder(nsMsgViewCommandTypeValue command, nsMsgViewIndex* indices,
02349                     PRInt32 numIndices, nsIMsgFolder *destFolder)
02350 {
02351   nsresult rv = NS_OK;
02352 
02353   NS_ENSURE_ARG_POINTER(destFolder);
02354 
02355   switch (command) {
02356     case nsMsgViewCommandType::copyMessages:
02357         NS_ASSERTION(!(m_folder == destFolder), "The source folder and the destination folder are the same");
02358         if (m_folder != destFolder)
02359           rv = CopyMessages(mMsgWindow, indices, numIndices, PR_FALSE /* isMove */, destFolder);
02360         break;
02361     case nsMsgViewCommandType::moveMessages:
02362         NS_ASSERTION(!(m_folder == destFolder), "The source folder and the destination folder are the same");
02363         if (m_folder != destFolder)
02364           rv = CopyMessages(mMsgWindow, indices, numIndices, PR_TRUE  /* isMove */, destFolder);
02365         break;
02366     default:
02367         NS_ASSERTION(PR_FALSE, "unhandled command");
02368         rv = NS_ERROR_UNEXPECTED;
02369         break;
02370     }
02371     return rv;
02372 }
02373 
02374 nsresult
02375 nsMsgDBView::ApplyCommandToIndices(nsMsgViewCommandTypeValue command, nsMsgViewIndex* indices,
02376                                    PRInt32 numIndices)
02377 {
02378   NS_ASSERTION(numIndices >= 0, "nsMsgDBView::ApplyCommandToIndices(): "
02379                "numIndices is negative!");
02380 
02381   if (numIndices == 0) 
02382       return NS_OK; // return quietly, just in case
02383 
02384   nsCOMPtr<nsIMsgFolder> folder;
02385   nsresult rv = GetFolderForViewIndex(indices[0], getter_AddRefs(folder));
02386 
02387   if (command == nsMsgViewCommandType::deleteMsg)
02388     return DeleteMessages(mMsgWindow, indices, numIndices, PR_FALSE);
02389   if (command == nsMsgViewCommandType::deleteNoTrash)
02390     return DeleteMessages(mMsgWindow, indices, numIndices, PR_TRUE);
02391 
02392   nsMsgKeyArray imapUids;
02393   nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder);
02394   PRBool thisIsImapFolder = (imapFolder != nsnull);
02395   nsCOMPtr<nsIJunkMailPlugin> junkPlugin;
02396 
02397   // if this is a junk command, start a batch or add to an existing one. 
02398   //  
02399   if (    command == nsMsgViewCommandType::junk
02400        || command == nsMsgViewCommandType::unjunk ) 
02401   {
02402     // get the folder from the first item; we assume that
02403     // all messages in the view are from the same folder (no
02404     // more junk status column in the 'search messages' dialog
02405     // like in earlier versions...)
02406      //
02407      NS_ENSURE_SUCCESS(rv, rv);
02408 
02409      nsCOMPtr<nsIMsgIncomingServer> server;
02410      rv = folder->GetServer(getter_AddRefs(server));
02411      NS_ENSURE_SUCCESS(rv, rv);
02412 
02413     if (command == nsMsgViewCommandType::junk)
02414     {
02415       // append this batch of junk message indices to the
02416       // array of junk message indices to be acted upon
02417       // once OnMessageClassified() is run for the last message
02418       //
02419       // note: although message classification is done
02420       // asynchronously, it is not done in a different thread,
02421       // so the manipulations of mJunkIndices here and in
02422       // OnMessageClassified() cannot interrupt each other
02423       //
02424       mNumJunkIndices += numIndices;
02425       mJunkIndices = (nsMsgViewIndex *)nsMemory::Realloc(mJunkIndices, mNumJunkIndices * sizeof(nsMsgViewIndex));
02426       memcpy(mJunkIndices + (mNumJunkIndices - numIndices), indices, numIndices * sizeof(nsMsgViewIndex));
02427     }
02428    
02429     nsCOMPtr<nsIMsgFilterPlugin> filterPlugin;
02430     rv = server->GetSpamFilterPlugin(getter_AddRefs(filterPlugin));
02431     NS_ENSURE_SUCCESS(rv, rv);
02432 
02433     junkPlugin = do_QueryInterface(filterPlugin, &rv);
02434     NS_ENSURE_SUCCESS(rv, rv);
02435 
02436     // note that if we aren't starting a batch we are
02437     // actually coalescing the batch of messages this
02438     // function was called for with the previous
02439     // batch(es)
02440     mNumMessagesRemainingInBatch += numIndices;
02441   }
02442          
02443   folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, PR_FALSE, PR_TRUE /*dbBatching*/);
02444 
02445   for (int32 i = 0; i < numIndices; i++)
02446   {
02447     if (thisIsImapFolder && command != nsMsgViewCommandType::markThreadRead)
02448       imapUids.Add(GetAt(indices[i]));
02449     
02450     switch (command)
02451     {
02452     case nsMsgViewCommandType::markMessagesRead:
02453       rv = SetReadByIndex(indices[i], PR_TRUE);
02454       break;
02455     case nsMsgViewCommandType::markMessagesUnread:
02456       rv = SetReadByIndex(indices[i], PR_FALSE);
02457       break;
02458     case nsMsgViewCommandType::toggleMessageRead:
02459       rv = ToggleReadByIndex(indices[i]);
02460       break;
02461     case nsMsgViewCommandType::flagMessages:
02462       rv = SetFlaggedByIndex(indices[i], PR_TRUE);
02463       break;
02464     case nsMsgViewCommandType::unflagMessages:
02465       rv = SetFlaggedByIndex(indices[i], PR_FALSE);
02466       break;
02467     case nsMsgViewCommandType::markThreadRead:
02468       rv = SetThreadOfMsgReadByIndex(indices[i], imapUids, PR_TRUE);
02469       break;
02470     case nsMsgViewCommandType::junk:
02471       rv = SetAsJunkByIndex(junkPlugin.get(), indices[i],
02472                              nsIJunkMailPlugin::JUNK);
02473       break;
02474     case nsMsgViewCommandType::unjunk:
02475     rv = SetAsJunkByIndex(junkPlugin.get(), indices[i], 
02476                              nsIJunkMailPlugin::GOOD);
02477       break;
02478     case nsMsgViewCommandType::undeleteMsg:
02479       break; // this is completely handled in the imap code below.
02480     default:
02481       NS_ASSERTION(PR_FALSE, "unhandled command");
02482       break;
02483     }
02484   }
02485 
02486   folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, PR_TRUE, PR_TRUE /*dbBatching*/);
02487 
02488   if (thisIsImapFolder)
02489   {
02490     imapMessageFlagsType flags = kNoImapMsgFlag;
02491     PRBool addFlags = PR_FALSE;
02492     PRBool isRead = PR_FALSE;
02493     
02494     switch (command)
02495     {
02496     case nsMsgViewCommandType::markThreadRead:
02497     case nsMsgViewCommandType::markMessagesRead:
02498       flags |= kImapMsgSeenFlag;
02499       addFlags = PR_TRUE;
02500       break;
02501     case nsMsgViewCommandType::markMessagesUnread:
02502       flags |= kImapMsgSeenFlag;
02503       addFlags = PR_FALSE;
02504       break;
02505     case nsMsgViewCommandType::toggleMessageRead:
02506       {
02507         flags |= kImapMsgSeenFlag;
02508         m_db->IsRead(GetAt(indices[0]), &isRead);
02509         if (isRead)
02510           addFlags = PR_TRUE;
02511         else
02512           addFlags = PR_FALSE;
02513       }
02514       break;
02515     case nsMsgViewCommandType::flagMessages:
02516       flags |= kImapMsgFlaggedFlag;
02517       addFlags = PR_TRUE;
02518       break;
02519     case nsMsgViewCommandType::unflagMessages:
02520       flags |= kImapMsgFlaggedFlag;
02521       addFlags = PR_FALSE;
02522       break;
02523     case nsMsgViewCommandType::undeleteMsg:
02524       flags = kImapMsgDeletedFlag;
02525       addFlags = PR_FALSE;
02526       break;
02527     case nsMsgViewCommandType::junk:
02528         return imapFolder->StoreCustomKeywords(mMsgWindow,
02529                     "Junk",
02530                     "NonJunk",
02531                     imapUids.GetArray(), imapUids.GetSize(),
02532                     nsnull);
02533     case nsMsgViewCommandType::unjunk:
02534         return imapFolder->StoreCustomKeywords(mMsgWindow,
02535                     "NonJunk",
02536                     "Junk",
02537                     imapUids.GetArray(), imapUids.GetSize(),
02538                     nsnull);
02539 
02540     default:
02541       break;
02542     }
02543     
02544     if (flags != kNoImapMsgFlag)   // can't get here without thisIsImapThreadPane == TRUE
02545       imapFolder->StoreImapFlags(flags, addFlags, imapUids.GetArray(), imapUids.GetSize(), nsnull);
02546     
02547   }
02548    
02549   return rv;
02550 }
02551 
02552 // view modifications methods by index
02553 
02554 // This method just removes the specified line from the view. It does
02555 // NOT delete it from the database.
02556 nsresult nsMsgDBView::RemoveByIndex(nsMsgViewIndex index)
02557 {
02558   if (!IsValidIndex(index))
02559     return NS_MSG_INVALID_DBVIEW_INDEX;
02560   m_keys.RemoveAt(index);
02561   m_flags.RemoveAt(index);
02562   m_levels.RemoveAt(index);
02563 
02564   // the call to NoteChange() has to happen after we remove the key
02565   // as NoteChange() will call RowCountChanged() which will call our GetRowCount()
02566   if (!m_deletingRows)
02567     NoteChange(index, -1, nsMsgViewNotificationCode::insertOrDelete); // an example where view is not the listener - D&D messages
02568   
02569   return NS_OK;
02570 }
02571 
02572 nsresult nsMsgDBView::DeleteMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, PRInt32 numIndices, PRBool deleteStorage)
02573 {
02574   if (m_deletingRows)  
02575   {
02576     NS_WARNING("Last delete did not complete");
02577     return NS_OK;
02578   }
02579 
02580   if (mDeleteModel != nsMsgImapDeleteModels::IMAPDelete)
02581     m_deletingRows = PR_TRUE;
02582 
02583   nsresult rv;
02584   nsCOMPtr<nsISupportsArray> messageArray;
02585   NS_NewISupportsArray(getter_AddRefs(messageArray));
02586   for (nsMsgViewIndex index = 0; index < (nsMsgViewIndex) numIndices; index++)
02587   {
02588     if (m_flags[indices[index]] & MSG_VIEW_FLAG_DUMMY)
02589       continue;
02590     nsMsgKey key = m_keys.GetAt(indices[index]);
02591     nsCOMPtr <nsIMsgDBHdr> msgHdr;
02592     rv = m_db->GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
02593     if (NS_SUCCEEDED(rv) && msgHdr)
02594     {
02595       messageArray->AppendElement(msgHdr);
02596       // if we are deleting rows, save off the keys
02597       if (m_deletingRows)
02598         mIndicesToNoteChange.Add(indices[index]);
02599     }
02600   }
02601   
02602   rv = m_folder->DeleteMessages(messageArray, window, deleteStorage, PR_FALSE, nsnull, PR_TRUE /*allow Undo*/ );
02603   if (NS_FAILED(rv))
02604     m_deletingRows = PR_FALSE;
02605   return rv;
02606 }
02607 
02608 nsresult nsMsgDBView::DownloadForOffline(nsIMsgWindow *window, nsMsgViewIndex *indices, PRInt32 numIndices)
02609 {
02610   nsresult rv = NS_OK;
02611   nsCOMPtr<nsISupportsArray> messageArray;
02612   NS_NewISupportsArray(getter_AddRefs(messageArray));
02613   for (nsMsgViewIndex index = 0; index < (nsMsgViewIndex) numIndices; index++)
02614   {
02615     nsMsgKey key = m_keys.GetAt(indices[index]);
02616     nsCOMPtr <nsIMsgDBHdr> msgHdr;
02617     rv = m_db->GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
02618     NS_ENSURE_SUCCESS(rv,rv);
02619     if (msgHdr)
02620     {
02621       PRUint32 flags;
02622       msgHdr->GetFlags(&flags);
02623       if (!(flags & MSG_FLAG_OFFLINE))
02624         messageArray->AppendElement(msgHdr);
02625     }
02626   }
02627   m_folder->DownloadMessagesForOffline(messageArray, window);
02628   return rv;
02629 }
02630 
02631 nsresult nsMsgDBView::DownloadFlaggedForOffline(nsIMsgWindow *window)
02632 {
02633   nsresult rv = NS_OK;
02634   nsCOMPtr<nsISupportsArray> messageArray;
02635   NS_NewISupportsArray(getter_AddRefs(messageArray));
02636   nsCOMPtr <nsISimpleEnumerator> enumerator;
02637   rv = m_db->EnumerateMessages(getter_AddRefs(enumerator));
02638   if (NS_SUCCEEDED(rv) && enumerator)
02639   {
02640     PRBool hasMore;
02641     
02642     while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) && (hasMore == PR_TRUE)) 
02643     {
02644       nsCOMPtr <nsIMsgDBHdr> pHeader;
02645       rv = enumerator->GetNext(getter_AddRefs(pHeader));
02646       NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
02647       if (pHeader && NS_SUCCEEDED(rv))
02648       {
02649         PRUint32 flags;
02650         pHeader->GetFlags(&flags);
02651         if ((flags & MSG_FLAG_MARKED) && !(flags & MSG_FLAG_OFFLINE))
02652           messageArray->AppendElement(pHeader);
02653       }
02654     }
02655   }
02656   m_folder->DownloadMessagesForOffline(messageArray, window);
02657   return rv;
02658 }
02659 
02660 // read/unread handling.
02661 nsresult nsMsgDBView::ToggleReadByIndex(nsMsgViewIndex index)
02662 {
02663   if (!IsValidIndex(index))
02664     return NS_MSG_INVALID_DBVIEW_INDEX;
02665   return SetReadByIndex(index, !(m_flags[index] & MSG_FLAG_READ));
02666 }
02667 
02668 nsresult nsMsgDBView::SetReadByIndex(nsMsgViewIndex index, PRBool read)
02669 {
02670   nsresult rv;
02671   
02672   if (!IsValidIndex(index))
02673     return NS_MSG_INVALID_DBVIEW_INDEX;
02674   if (read) 
02675   {
02676     OrExtraFlag(index, MSG_FLAG_READ);
02677     // MarkRead() will clear this flag in the db
02678     // and then call OnKeyChange(), but
02679     // because we are the instigator of the change
02680     // we'll ignore the change.
02681     //
02682     // so we need to clear it in m_flags
02683     // to keep the db and m_flags in sync
02684     AndExtraFlag(index, ~MSG_FLAG_NEW);
02685   }
02686   else 
02687   {
02688     AndExtraFlag(index, ~MSG_FLAG_READ);
02689   }
02690   
02691   nsCOMPtr <nsIMsgDatabase> dbToUse;
02692   rv = GetDBForViewIndex(index, getter_AddRefs(dbToUse));
02693   NS_ENSURE_SUCCESS(rv, rv);
02694   
02695   rv = dbToUse->MarkRead(m_keys[index], read, this);
02696   NoteChange(index, 1, nsMsgViewNotificationCode::changed);
02697   if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
02698   {
02699     nsMsgViewIndex threadIndex = ThreadIndexOfMsg(m_keys[index], index, nsnull, nsnull);
02700     if (threadIndex != index)
02701       NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
02702   }
02703   return rv;
02704 }
02705 
02706 nsresult nsMsgDBView::SetThreadOfMsgReadByIndex(nsMsgViewIndex index, nsMsgKeyArray &keysMarkedRead, PRBool /*read*/)
02707 {
02708        nsresult rv;
02709 
02710        if (!IsValidIndex(index))
02711               return NS_MSG_INVALID_DBVIEW_INDEX;
02712        rv = MarkThreadOfMsgRead(m_keys[index], index, keysMarkedRead, PR_TRUE);
02713        return rv;
02714 }
02715 
02716 nsresult nsMsgDBView::SetFlaggedByIndex(nsMsgViewIndex index, PRBool mark)
02717 {
02718        nsresult rv;
02719 
02720        if (!IsValidIndex(index))
02721               return NS_MSG_INVALID_DBVIEW_INDEX;
02722 
02723   nsCOMPtr <nsIMsgDatabase> dbToUse;
02724   rv = GetDBForViewIndex(index, getter_AddRefs(dbToUse));
02725   NS_ENSURE_SUCCESS(rv, rv);
02726 
02727        if (mark)
02728               OrExtraFlag(index, MSG_FLAG_MARKED);
02729        else
02730               AndExtraFlag(index, ~MSG_FLAG_MARKED);
02731 
02732        rv = dbToUse->MarkMarked(m_keys[index], mark, this);
02733        NoteChange(index, 1, nsMsgViewNotificationCode::changed);
02734        return rv;
02735 }
02736 
02737 nsresult nsMsgDBView::SetStringPropertyByIndex(nsMsgViewIndex index, const char *aProperty, const char *aValue)
02738 {  
02739   if (!IsValidIndex(index))
02740     return NS_MSG_INVALID_DBVIEW_INDEX;
02741   
02742   nsCOMPtr <nsIMsgDatabase> dbToUse;
02743   nsresult rv = GetDBForViewIndex(index, getter_AddRefs(dbToUse));
02744   NS_ENSURE_SUCCESS(rv, rv);
02745   
02746   rv = dbToUse->SetStringProperty(m_keys[index], aProperty, aValue);
02747   NoteChange(index, 1, nsMsgViewNotificationCode::changed);
02748   return rv;
02749 }
02750 
02751 nsresult nsMsgDBView::SetAsJunkByIndex(nsIJunkMailPlugin *aJunkPlugin,
02752                                           nsMsgViewIndex aIndex,
02753                                           nsMsgJunkStatus aNewClassification)
02754 {
02755     // get the message header (need this to get string properties)
02756     //
02757     nsCOMPtr <nsIMsgDBHdr> msgHdr;
02758     nsresult rv = GetMsgHdrForViewIndex(aIndex, getter_AddRefs(msgHdr));
02759     NS_ENSURE_SUCCESS(rv, rv);
02760 
02761     // get the old junk score
02762     //
02763     nsXPIDLCString junkScoreStr;
02764     rv = msgHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr));
02765 
02766     // and the old origin
02767     //
02768     nsXPIDLCString oldOriginStr;
02769     rv = msgHdr->GetStringProperty("junkscoreorigin", 
02770                                    getter_Copies(oldOriginStr));
02771 
02772     // if this was not classified by the user, say so
02773     //
02774     nsMsgJunkStatus oldUserClassification;
02775     if (oldOriginStr.get()[0] != 'u') {
02776         oldUserClassification = nsIJunkMailPlugin::UNCLASSIFIED;
02777     } else {
02778         // otherwise, pass the actual user classification
02779         //
02780         if (junkScoreStr.IsEmpty()) {
02781             oldUserClassification = nsIJunkMailPlugin::UNCLASSIFIED;
02782         } else if (atoi(junkScoreStr) > 50) {
02783             oldUserClassification = nsIJunkMailPlugin::JUNK;
02784         } else {
02785             oldUserClassification = nsIJunkMailPlugin::GOOD;
02786         }
02787     }
02788 
02789     // get the URI for this message so we can pass it to the plugin
02790     //
02791     nsXPIDLCString uri;
02792     rv = GetURIForViewIndex(aIndex, getter_Copies(uri));
02793     NS_ENSURE_SUCCESS(rv, rv);
02794 
02795     // tell the plugin about this change, so that it can (potentially)
02796     // adjust its database appropriately
02797     //
02798     rv = aJunkPlugin->SetMessageClassification(
02799         uri, oldUserClassification, aNewClassification, mMsgWindow, this);
02800     NS_ENSURE_SUCCESS(rv, rv);
02801 
02802     // this routine is only reached if the user someone touched the UI
02803     // and told us the junk status of this message.
02804     // Set origin first so that listeners on the junkscore will
02805     // know the correct origin.
02806     rv = SetStringPropertyByIndex(aIndex, "junkscoreorigin", "user");
02807     NS_ASSERTION(NS_SUCCEEDED(rv), "SetStringPropertyByIndex failed");
02808 
02809     // set the junk score on the message itself
02810     // 
02811     rv = SetStringPropertyByIndex(
02812         aIndex, "junkscore", 
02813         aNewClassification == nsIJunkMailPlugin::JUNK ? "100" : "0");
02814     NS_ENSURE_SUCCESS(rv, rv);
02815 
02816     return rv;
02817 }
02818 
02819 nsresult
02820 nsMsgDBView::GetFolderFromMsgURI(const char *aMsgURI, nsIMsgFolder **aFolder)
02821 {
02822   NS_IF_ADDREF(*aFolder = m_folder);
02823   return NS_OK;
02824 }
02825 
02826 NS_IMETHODIMP
02827 nsMsgDBView::OnMessageClassified(const char *aMsgURI,
02828                                  nsMsgJunkStatus aClassification)
02829 {
02830   // Note: we know all messages in a batch have the same
02831   // classification, since unlike OnMessageClassified
02832   // methods in other classes (such as nsLocalMailFolder
02833   // and nsImapMailFolder), this class, nsMsgDBView, currently
02834   // only triggers message classifications due to a command to
02835   // mark some of the messages in the view as junk, or as not
02836   // junk - so the classification is dictated to the filter,
02837   // not suggested by it.
02838   //
02839   // for this reason the only thing we (may) have to do is
02840   // perform the action on all of the junk messages
02841   //
02842 
02843   NS_ASSERTION( (aClassification == nsIJunkMailPlugin::GOOD) || (mJunkIndices != nsnull), "the classification of a manually-marked junk message has been classified as junk, yet there seem to be no such outstanding messages");
02844   
02845   // is this the last message in the batch?
02846   
02847   if (--mNumMessagesRemainingInBatch == 0)
02848   {
02849     if ( mNumJunkIndices > 0 )
02850     {
02851       PerformActionsOnJunkMsgs();
02852       nsMemory::Free(mJunkIndices);
02853       mJunkIndices = nsnull;
02854       mNumJunkIndices = 0;
02855     }
02856   }
02857   return NS_OK;
02858 }
02859 
02860 nsresult
02861 nsMsgDBView::PerformActionsOnJunkMsgs()
02862 {
02863   PRBool movingJunkMessages,markingJunkMessagesRead;
02864   nsCOMPtr <nsIMsgFolder> junkTargetFolder;
02865 
02866   // question: is it possible for the junk mail move/mark as read
02867   // options to change after we've handled some of the batches but
02868   // before we've handled the last one? if so, we can decide when
02869   // handling each batch whether to save its indices or forget
02870   // them, and then perform the known action when handling the 
02871   // last batch; however if the options can change between batches
02872   // we may have to remember in separate arrays the indices to
02873   // mark as read and the indices to move
02874   //
02875   // for now, we assume the options do not change between batches
02876   //
02877   
02878   nsresult rv = DetermineActionsForJunkMsgs(&movingJunkMessages, &markingJunkMessagesRead, getter_AddRefs(junkTargetFolder));
02879   NS_ENSURE_SUCCESS(rv,rv);
02880        
02881   // nothing to do, bail out
02882   if (!(movingJunkMessages || markingJunkMessagesRead))
02883     return NS_OK;
02884 
02885   NS_ASSERTION( (mNumJunkIndices > 0), "no indices of marked-as-junk messages to act on");
02886 
02887   if (mNumJunkIndices > 1)
02888     NS_QuickSort(mJunkIndices, mNumJunkIndices, sizeof(nsMsgViewIndex), CompareViewIndices, nsnull);
02889 
02890   if (markingJunkMessagesRead)
02891   {
02892     // notes on marking junk as read:
02893     // 1. there are 2 occasions on which junk messages are marked as 
02894     //    read: after a manual marking (here and in the front end) and after
02895     //    automatic classification by the bayesian filter (see code for local
02896     //    mail folders and for imap mail folders). The server-specific
02897     //    markAsReadOnSpam pref only applies to the latter, the former is
02898     //    controlled by "mailnews.ui.junk.manualMarkAsJunkMarksRead".
02899     // 2. even though move/delete on manual mark may be
02900     //    turned off, we might still need to mark as read
02901 
02902     NoteStartChange(nsMsgViewNotificationCode::none, 0, 0);
02903     rv = ApplyCommandToIndices(nsMsgViewCommandType::markMessagesRead, mJunkIndices, mNumJunkIndices);
02904     NoteEndChange(nsMsgViewNotificationCode::none, 0, 0);
02905     NS_ASSERTION(NS_SUCCEEDED(rv), "marking marked-as-junk messages as read failed");
02906   }
02907   if (movingJunkMessages) 
02908   {
02909     // check if one of the messages to be junked is actually selected
02910     // if more than one message being junked, one must be selected.
02911     // if no tree selection at all, must be in stand-alone message window.
02912     PRBool junkedMsgSelected = mNumJunkIndices > 1 || !mTreeSelection;
02913     for (nsMsgViewIndex junkIndex = 0; !junkedMsgSelected && junkIndex < mNumJunkIndices; junkIndex++)
02914       mTreeSelection->IsSelected(mJunkIndices[junkIndex], &junkedMsgSelected);
02915 
02916     // if a junked msg is selected, tell the FE to call SetNextMessageAfterDelete() because a delete is coming
02917     if (junkedMsgSelected && mCommandUpdater)
02918     {
02919       rv = mCommandUpdater->UpdateNextMessageAfterDelete();
02920       NS_ENSURE_SUCCESS(rv,rv);
02921     }
02922   
02923     NoteStartChange(nsMsgViewNotificationCode::none, 0, 0);
02924     if (junkTargetFolder) 
02925       rv = ApplyCommandToIndicesWithFolder(nsMsgViewCommandType::moveMessages, mJunkIndices, mNumJunkIndices, junkTargetFolder);
02926     else
02927       rv = ApplyCommandToIndices(nsMsgViewCommandType::deleteMsg, mJunkIndices, mNumJunkIndices);
02928     NoteEndChange(nsMsgViewNotificationCode::none, 0, 0);
02929 
02930     NS_ASSERTION(NS_SUCCEEDED(rv), "move or deletion of marked-as-junk messages failed");
02931   }
02932   return rv;
02933 }
02934 
02935 nsresult
02936 nsMsgDBView::DetermineActionsForJunkMsgs(PRBool* movingJunkMessages, PRBool* markingJunkMessagesRead, nsIMsgFolder** junkTargetFolder)
02937 {
02938   // there are two possible actions which may be performed
02939   // on messages marked as spam: marking as read and moving
02940   // somewhere...
02941 
02942   *movingJunkMessages = false;
02943   *markingJunkMessagesRead = false;
02944   
02945   // ... the 'somewhere', junkTargetFolder, can be a folder,
02946   // but if it remains null we'll delete the messages
02947   
02948   *junkTargetFolder = nsnull;
02949 
02950   nsCOMPtr<nsIMsgFolder> folder;
02951   nsresult rv = GetFolderForViewIndex(mJunkIndices[0], getter_AddRefs(folder));
02952   NS_ENSURE_SUCCESS(rv, rv);
02953 
02954   nsCOMPtr<nsIMsgIncomingServer> server;
02955   rv = folder->GetServer(getter_AddRefs(server));
02956   NS_ENSURE_SUCCESS(rv, rv);
02957     
02958   nsCOMPtr <nsISpamSettings> spamSettings;
02959   rv = server->GetSpamSettings(getter_AddRefs(spamSettings));
02960   NS_ENSURE_SUCCESS(rv, rv);
02961   
02962   // if the spam system is completely disabled we won't do anything
02963   // question: is this a valid choice?
02964   PRInt32 spamLevel;
02965   (void)spamSettings->GetLevel(&spamLevel);
02966   if (!spamLevel)
02967     return NS_OK;
02968     
02969   // When the user explicitly marks a message as junk, we can mark it as read,
02970   // too. This is independent of the "markAsReadOnSpam" pref, which applies
02971   // only to automatically-classified messages.
02972   // Note that this behaviour should match the one in the front end for marking
02973   // as junk via toolbar/context menu.
02974   nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
02975   if (NS_SUCCEEDED(rv))
02976   {
02977     prefBranch->GetBoolPref("mailnews.ui.junk.manualMarkAsJunkMarksRead",
02978                             markingJunkMessagesRead);
02979   }
02980 
02981   // now let's determine whether we'll be taking the second action,
02982   // the move / deletion (and also determine which of these two)
02983    
02984   PRBool manualMark; 
02985   (void)spamSettings->GetManualMark(&manualMark);
02986   if (!manualMark)
02987     return NS_OK;
02988   
02989   PRInt32 manualMarkMode;
02990   (void)spamSettings->GetManualMarkMode(&manualMarkMode);
02991   NS_ASSERTION(manualMarkMode == nsISpamSettings::MANUAL_MARK_MODE_MOVE
02992             || manualMarkMode == nsISpamSettings::MANUAL_MARK_MODE_DELETE,
02993             "bad manual mark mode");
02994   
02995   // the folder must allow us to execute the move (or the deletion)
02996   PRUint32 folderFlags;
02997   folder->GetFlags(&folderFlags);
02998   
02999   if (manualMarkMode == nsISpamSettings::MANUAL_MARK_MODE_MOVE) 
03000   {
03001     // if this is a junk folder
03002     // (not only "the" junk folder for this account)
03003     // don't do the move
03004     if (folderFlags & MSG_FOLDER_FLAG_JUNK)
03005       return NS_OK;
03006     
03007     nsXPIDLCString spamFolderURI;
03008     rv = spamSettings->GetSpamFolderURI(getter_Copies(spamFolderURI));
03009     NS_ENSURE_SUCCESS(rv,rv);
03010     
03011     NS_ASSERTION(!spamFolderURI.IsEmpty(), "spam folder URI is empty, can't move");
03012     if (!spamFolderURI.IsEmpty()) 
03013     {
03014       //nsCOMPtr<nsIMsgFolder> destFolder;
03015       rv = GetExistingFolder(spamFolderURI.get(), junkTargetFolder);
03016       NS_ENSURE_SUCCESS(rv,rv);
03017 
03018       *movingJunkMessages = true;
03019     }
03020     return NS_OK;
03021   }
03022   
03023   // at this point manualMarkMode == nsISpamSettings::MANUAL_MARK_MODE_DELETE)
03024 
03025   // if this is in the trash, let's not delete
03026   if (folderFlags & MSG_FOLDER_FLAG_TRASH)
03027     return NS_OK;
03028 
03029   return folder->GetCanDeleteMessages(movingJunkMessages);
03030 }
03031 
03032 // reversing threads involves reversing the threads but leaving the
03033 // expanded messages ordered relative to the thread, so we
03034 // make a copy of each array and copy them over.
03035 nsresult nsMsgDBView::ReverseThreads()
03036 {
03037     nsUInt32Array *newFlagArray = new nsUInt32Array;
03038     if (!newFlagArray) 
03039         return NS_ERROR_OUT_OF_MEMORY;
03040     nsMsgKeyArray *newKeyArray = new nsMsgKeyArray;
03041     if (!newKeyArray) 
03042     {
03043         delete newFlagArray;
03044         return NS_ERROR_OUT_OF_MEMORY;
03045     }
03046     nsUint8Array *newLevelArray = new nsUint8Array;
03047     if (!newLevelArray) 
03048     {
03049         delete newFlagArray;
03050         delete newKeyArray;
03051         return NS_ERROR_OUT_OF_MEMORY;
03052     }
03053 
03054     PRInt32 sourceIndex, destIndex;
03055     PRInt32 viewSize = GetSize();
03056 
03057     newKeyArray->SetSize(m_keys.GetSize());
03058     newFlagArray->SetSize(m_flags.GetSize());
03059     newLevelArray->SetSize(m_levels.GetSize());
03060 
03061     for (sourceIndex = 0, destIndex = viewSize - 1; sourceIndex < viewSize;) 
03062     {
03063         PRInt32 endThread;  // find end of current thread.
03064         PRBool inExpandedThread = PR_FALSE;
03065         for (endThread = sourceIndex; endThread < viewSize; endThread++) 
03066         {
03067             PRUint32 flags = m_flags.GetAt(endThread);
03068             if (!inExpandedThread && (flags & (MSG_VIEW_FLAG_ISTHREAD|MSG_VIEW_FLAG_HASCHILDREN)) && !(flags & MSG_FLAG_ELIDED))
03069                 inExpandedThread = PR_TRUE;
03070             else if (flags & MSG_VIEW_FLAG_ISTHREAD) 
03071             {
03072                 if (inExpandedThread)
03073                     endThread--;
03074                 break;
03075             }
03076         }
03077 
03078         if (endThread == viewSize)
03079             endThread--;
03080         PRInt32 saveEndThread = endThread;
03081         while (endThread >= sourceIndex)
03082         {
03083             newKeyArray->SetAt(destIndex, m_keys.GetAt(endThread));
03084             newFlagArray->SetAt(destIndex, m_flags.GetAt(endThread));
03085             newLevelArray->SetAt(destIndex, m_levels.GetAt(endThread));
03086             endThread--;
03087             destIndex--;
03088         }
03089         sourceIndex = saveEndThread + 1;
03090     }
03091     // this copies the contents of both arrays - it would be cheaper to
03092     // just assign the new data ptrs to the old arrays and "forget" the new
03093     // arrays' data ptrs, so they won't be freed when the arrays are deleted.
03094     m_keys.RemoveAll();
03095     m_flags.RemoveAll();
03096     m_levels.RemoveAll();
03097     m_keys.InsertAt(0, newKeyArray);
03098     m_flags.InsertAt(0, newFlagArray);
03099     m_levels.InsertAt(0, newLevelArray);
03100 
03101     // if we swizzle data pointers for these arrays, this won't be right.
03102     delete newFlagArray;
03103     delete newKeyArray;
03104     delete newLevelArray;
03105 
03106     return NS_OK;
03107 }
03108 
03109 nsresult nsMsgDBView::ReverseSort()
03110 {
03111     PRUint32 num = GetSize();
03112        
03113     nsCOMPtr <nsISupportsArray> folders;
03114     GetFolders(getter_AddRefs(folders));
03115 
03116     // go up half the array swapping values
03117     for (PRUint32 i = 0; i < (num / 2); i++) 
03118     {
03119         // swap flags
03120         PRUint32 end = num - i - 1;
03121         PRUint32 tempFlags = m_flags.GetAt(i);
03122         m_flags.SetAt(i, m_flags.GetAt(end));
03123         m_flags.SetAt(end, tempFlags);
03124 
03125         // swap keys
03126         nsMsgKey tempKey = m_keys.GetAt(i);
03127         m_keys.SetAt(i, m_keys.GetAt(end));
03128         m_keys.SetAt(end, tempKey);
03129 
03130         if (folders)
03131         {
03132             // swap folders -- 
03133             // needed when search is done across multiple folders
03134             nsCOMPtr<nsISupports> tmpSupports = dont_AddRef(folders->ElementAt(i));
03135             nsCOMPtr<nsISupports> endSupports = dont_AddRef(folders->ElementAt(end));
03136             folders->SetElementAt(i, endSupports);
03137             folders->SetElementAt(end, tmpSupports);
03138         }
03139         // no need to swap elements in m_levels, 
03140         // since we won't call ReverseSort() if we
03141         // are in threaded mode, so m_levels are all the same.
03142     }
03143 
03144     return NS_OK;
03145 }
03146 
03147 struct IdDWord
03148 {
03149     nsMsgKey    id;
03150     PRUint32    bits;
03151     PRUint32    dword;
03152     nsISupports* folder;
03153 };
03154 
03155 struct IdKey : public IdDWord
03156 {
03157     PRUint8     key[1];
03158 };
03159 
03160 struct IdKeyPtr : public IdDWord
03161 {
03162     PRUint8     *key;
03163 };
03164 
03165 int PR_CALLBACK
03166 FnSortIdKey(const void *pItem1, const void *pItem2, void *privateData)
03167 {
03168     PRInt32 retVal = 0;
03169     nsresult rv;
03170 
03171     IdKey** p1 = (IdKey**)pItem1;
03172     IdKey** p2 = (IdKey**)pItem2;
03173 
03174     nsIMsgDatabase *db = (nsIMsgDatabase *)privateData;
03175 
03176     rv = db->CompareCollationKeys((*p1)->key, (*p1)->dword, (*p2)->key, (*p2)->dword, &retVal);
03177     NS_ASSERTION(NS_SUCCEEDED(rv),"compare failed");
03178 
03179     if (retVal != 0)
03180         return(retVal);
03181     if ((*p1)->id >= (*p2)->id)
03182         return(1);
03183     else
03184         return(-1);
03185 }
03186 
03187 int PR_CALLBACK
03188 FnSortIdKeyPtr(const void *pItem1, const void *pItem2, void *privateData)
03189 {
03190     PRInt32 retVal = 0;
03191     nsresult rv;
03192 
03193     IdKeyPtr** p1 = (IdKeyPtr**)pItem1;
03194     IdKeyPtr** p2 = (IdKeyPtr**)pItem2;
03195 
03196     nsIMsgDatabase *db = (nsIMsgDatabase *)privateData;
03197 
03198     rv = db->CompareCollationKeys((*p1)->key, (*p1)->dword, (*p2)->key, (*p2)->dword, &retVal);
03199     NS_ASSERTION(NS_SUCCEEDED(rv),"compare failed");
03200 
03201     if (retVal != 0)
03202         return(retVal);
03203     if ((*p1)->id >= (*p2)->id)
03204         return(1);
03205     else
03206         return(-1);
03207 }
03208 
03209 int PR_CALLBACK
03210 FnSortIdDWord(const void *pItem1, const void *pItem2, void *privateData)
03211 {
03212     IdDWord** p1 = (IdDWord**)pItem1;
03213     IdDWord** p2 = (IdDWord**)pItem2;
03214 
03215     if ((*p1)->dword > (*p2)->dword)
03216         return(1);
03217     else if ((*p1)->dword < (*p2)->dword)
03218         return(-1);
03219     else if ((*p1)->id >= (*p2)->id)
03220         return(1);
03221     else
03222         return(-1);
03223 }
03224 
03225 
03226 // XXX are these still correct?
03227 //To compensate for memory alignment required for
03228 //systems such as HP-UX these values must be 4 bytes
03229 //aligned.  Don't break this when modify the constants
03230 const int kMaxSubjectKey = 160;
03231 const int kMaxLocationKey = 160;  // also used for account
03232 const int kMaxAuthorKey = 160;
03233 const int kMaxRecipientKey = 80; 
03234 
03235 nsresult nsMsgDBView::GetFieldTypeAndLenForSort(nsMsgViewSortTypeValue sortType, PRUint16 *pMaxLen, eFieldType *pFieldType)
03236 {
03237     NS_ENSURE_ARG_POINTER(pMaxLen);
03238     NS_ENSURE_ARG_POINTER(pFieldType);
03239 
03240     switch (sortType) 
03241     {
03242         case nsMsgViewSortType::bySubject:
03243             *pFieldType = kCollationKey;
03244             *pMaxLen = kMaxSubjectKey;
03245             break;
03246         case nsMsgViewSortType::byAccount:
03247         case nsMsgViewSortType::byTags:
03248         case nsMsgViewSortType::byLocation:
03249             *pFieldType = kCollationKey;
03250             *pMaxLen = kMaxLocationKey;
03251             break;
03252         case nsMsgViewSortType::byRecipient:
03253             *pFieldType = kCollationKey;
03254             *pMaxLen = kMaxRecipientKey;
03255             break;
03256         case nsMsgViewSortType::byAuthor:
03257             *pFieldType = kCollationKey;
03258             *pMaxLen = kMaxAuthorKey;
03259             break;
03260         case nsMsgViewSortType::byDate:
03261         case nsMsgViewSortType::byPriority:
03262         case nsMsgViewSortType::byThread:
03263         case nsMsgViewSortType::byId:
03264         case nsMsgViewSortType::bySize:
03265         case nsMsgViewSortType::byFlagged:
03266         case nsMsgViewSortType::byUnread:
03267         case nsMsgViewSortType::byStatus:
03268         case nsMsgViewSortType::byJunkStatus:
03269         case nsMsgViewSortType::byAttachments:
03270             *pFieldType = kU32;
03271             *pMaxLen = 0;
03272             break;
03273         case nsMsgViewSortType::byCustom:
03274         {        
03275           nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandlerFromDBInfo();
03276           
03277           if (colHandler != nsnull)
03278           {
03279             PRBool isString;
03280             colHandler->IsString(&isString);
03281             
03282             if (isString)
03283             {
03284               *pFieldType = kCollationKey;
03285               *pMaxLen = kMaxRecipientKey; //80 - do we need a seperate k?
03286             } 
03287             else
03288             {
03289               *pFieldType = kU32;
03290               *pMaxLen = 0;
03291             }
03292           }
03293           break;
03294         }
03295         default:
03296             return NS_ERROR_UNEXPECTED;
03297     }
03298 
03299     return NS_OK;
03300 }
03301 
03302 #define MSG_STATUS_MASK (MSG_FLAG_REPLIED | MSG_FLAG_FORWARDED)
03303 
03304 nsresult nsMsgDBView::GetStatusSortValue(nsIMsgDBHdr *msgHdr, PRUint32 *result)
03305 {
03306   NS_ENSURE_ARG_POINTER(msgHdr);
03307   NS_ENSURE_ARG_POINTER(result);
03308 
03309   PRUint32 messageFlags;
03310   nsresult rv = msgHdr->GetFlags(&messageFlags);
03311   NS_ENSURE_SUCCESS(rv,rv);
03312 
03313   if (messageFlags & MSG_FLAG_NEW) 
03314   {
03315     // happily, new by definition stands alone
03316     *result = 0;
03317     return NS_OK;
03318   }
03319 
03320   switch (messageFlags & MSG_STATUS_MASK) 
03321   {
03322     case MSG_FLAG_REPLIED:
03323         *result = 2;
03324         break;
03325     case MSG_FLAG_FORWARDED|MSG_FLAG_REPLIED:
03326         *result = 1;
03327         break;
03328     case MSG_FLAG_FORWARDED:
03329         *result = 3;
03330         break;
03331     default:
03332         *result = (messageFlags & MSG_FLAG_READ) ? 4 : 5;
03333         break;
03334     }
03335 
03336     return NS_OK;
03337 }
03338 
03339 nsresult nsMsgDBView::GetLongField(nsIMsgDBHdr *msgHdr, nsMsgViewSortTypeValue sortType, PRUint32 *result, nsIMsgCustomColumnHandler* colHandler)
03340 {
03341   nsresult rv;
03342   NS_ENSURE_ARG_POINTER(msgHdr);
03343   NS_ENSURE_ARG_POINTER(result);
03344 
03345   PRBool isRead;
03346   PRUint32 bits;
03347 
03348   switch (sortType) 
03349   {
03350     case nsMsgViewSortType::bySize:
03351       rv = (mShowSizeInLines) ? msgHdr->GetLineCount(result) : msgHdr->GetMessageSize(result);
03352       break;
03353     case nsMsgViewSortType::byPriority: 
03354         nsMsgPriorityValue priority;
03355         rv = msgHdr->GetPriority(&priority);
03356 
03357         // treat "none" as "normal" when sorting.
03358         if (priority == nsMsgPriority::none)
03359             priority = nsMsgPriority::normal;
03360 
03361         // we want highest priority to have lowest value
03362         // so ascending sort will have highest priority first.
03363         *result = nsMsgPriority::highest - priority;
03364         break;
03365     case nsMsgViewSortType::byStatus:
03366         rv = GetStatusSortValue(msgHdr,result);
03367         break;
03368     case nsMsgViewSortType::byFlagged:
03369         bits = 0;
03370         rv = msgHdr->GetFlags(&bits);
03371         *result = !(bits & MSG_FLAG_MARKED);  //make flagged come out on top.
03372         break;
03373     case nsMsgViewSortType::byUnread:
03374         rv = msgHdr->GetIsRead(&isRead);
03375         if (NS_SUCCEEDED(rv)) 
03376             *result = !isRead;
03377         break;
03378     case nsMsgViewSortType::byJunkStatus:
03379       {
03380         nsXPIDLCString junkScoreStr;
03381         rv = msgHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr));
03382         // unscored messages should come before messages that are scored
03383         // junkScoreStr is "", and "0" - "100"
03384         // normalize to 0 - 101
03385         *result = junkScoreStr.IsEmpty() ? (0) : atoi(junkScoreStr.get()) + 1;
03386       }
03387       break;
03388           case nsMsgViewSortType::byAttachments:
03389         bits = 0;
03390         rv = msgHdr->GetFlags(&bits);
03391         *result = !(bits & MSG_FLAG_ATTACHMENT);
03392                 break;
03393     case nsMsgViewSortType::byDate:
03394       // when sorting threads by date, we want the date of the newest msg
03395       // in the thread
03396       if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay 
03397         && ! (m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
03398       {
03399         nsCOMPtr <nsIMsgThread> thread;
03400         rv = m_db->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread));
03401         NS_ENSURE_SUCCESS(rv, rv);
03402         thread->GetNewestMsgDate(result);
03403       }
03404       else
03405         rv = msgHdr->GetDateInSeconds(result);
03406       break;
03407     case nsMsgViewSortType::byCustom:
03408       if (colHandler != nsnull)
03409       {
03410         colHandler->GetSortLongForRow(msgHdr, result);
03411         rv = NS_OK;
03412       } 
03413       else 
03414       {
03415         NS_ASSERTION(PR_FALSE, "should not be here (Sort Type: byCustom (Long), but no custom handler)");
03416         rv = NS_ERROR_UNEXPECTED;
03417       }
03418       break;
03419     case nsMsgViewSortType::byId:
03420         // handled by caller, since caller knows the key
03421     default:
03422         NS_ASSERTION(0,"should not be here");
03423         rv = NS_ERROR_UNEXPECTED;
03424         break;
03425     }
03426     
03427     NS_ENSURE_SUCCESS(rv,rv);
03428     return NS_OK;
03429 }
03430 
03431 nsresult 
03432 nsMsgDBView::GetCollationKey(nsIMsgDBHdr *msgHdr, nsMsgViewSortTypeValue sortType, PRUint8 **result, PRUint32 *len, nsIMsgCustomColumnHandler* colHandler)
03433 {
03434   nsresult rv;
03435   NS_ENSURE_ARG_POINTER(msgHdr);
03436   NS_ENSURE_ARG_POINTER(result);
03437 
03438   switch (sortType)
03439   {
03440     case nsMsgViewSortType::bySubject:
03441         rv = msgHdr->GetSubjectCollationKey(result, len);
03442         break;
03443     case nsMsgViewSortType::byLocation:
03444         rv = GetLocationCollationKey(msgHdr, result, len);
03445         break;
03446     case nsMsgViewSortType::byRecipient:
03447         rv = msgHdr->GetRecipientsCollationKey(result, len);
03448         break;
03449     case nsMsgViewSortType::byAuthor:
03450         rv = msgHdr->GetAuthorCollationKey(result, len);
03451         break;
03452     case nsMsgViewSortType::byAccount:
03453     case nsMsgViewSortType::byTags:
03454       {
03455         nsXPIDLString str;
03456         nsCOMPtr <nsIMsgDatabase> dbToUse = m_db;
03457     
03458         if (!dbToUse) // probably search view
03459           GetDBForViewIndex(0, getter_AddRefs(dbToUse));
03460 
03461         rv = (sortType == nsMsgViewSortType::byAccount)
03462             ? FetchAccount(msgHdr, getter_Copies(str))
03463             : FetchTags(msgHdr, getter_Copies(str));
03464 
03465         if (NS_SUCCEEDED(rv) && dbToUse)
03466           rv = dbToUse->CreateCollationKey(str, result, len);
03467       }
03468       break;
03469     case nsMsgViewSortType::byCustom:
03470       if (colHandler != nsnull)
03471       {
03472         nsAutoString strKey;
03473         rv = colHandler->GetSortStringForRow(msgHdr, strKey);
03474         NS_ASSERTION(NS_SUCCEEDED(rv),"failed to get sort string for custom row");
03475         nsAutoString strTemp(strKey);
03476         
03477         rv = m_db->CreateCollationKey(strKey, result, len);
03478       } 
03479       else
03480       {
03481         NS_ASSERTION(PR_FALSE,"should not be here (Sort Type: byCustom (String), but no custom handler)");
03482         //rv = NS_ERROR_UNEXPECTED;
03483       }
03484       break;
03485     default:
03486         rv = NS_ERROR_UNEXPECTED;
03487         break;
03488     }
03489 
03490     // bailing out with failure will stop the sort and leave us in
03491     // a bad state.  try to continue on, instead
03492     NS_ASSERTION(NS_SUCCEEDED(rv),"failed to get the collation key");
03493     if (NS_FAILED(rv))
03494     {
03495         *result = nsnull;
03496         *len = 0;
03497     }
03498     return NS_OK;
03499 }
03500 
03501 // As the location collation key is created getting folder from the msgHdr,
03502 // it is defined in this file and not from the db.
03503 nsresult 
03504 nsMsgDBView::GetLocationCollationKey(nsIMsgDBHdr *msgHdr, PRUint8 **result, PRUint32 *len)
03505 {
03506     nsCOMPtr <nsIMsgFolder> folder;
03507 
03508     nsresult rv = msgHdr->GetFolder(getter_AddRefs(folder));
03509     NS_ENSURE_SUCCESS(rv,rv);
03510 
03511     if (!folder)
03512       return NS_ERROR_NULL_POINTER;
03513 
03514     nsCOMPtr <nsIMsgDatabase> dbToUse;
03515     rv = folder->GetMsgDatabase(nsnull, getter_AddRefs(dbToUse));
03516     NS_ENSURE_SUCCESS(rv,rv);
03517 
03518     nsXPIDLString locationString; 
03519     rv = folder->GetPrettiestName(getter_Copies(locationString));
03520     NS_ENSURE_SUCCESS(rv,rv);
03521 
03522     rv = dbToUse->CreateCollationKey(locationString, result, len);
03523     NS_ENSURE_SUCCESS(rv,rv);
03524 
03525     return NS_OK;
03526 }
03527 
03528 nsresult nsMsgDBView::SaveSortInfo(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder)
03529 {
03530   if (m_viewFolder)
03531   {
03532     nsCOMPtr <nsIDBFolderInfo> folderInfo;
03533     nsCOMPtr <nsIMsgDatabase> db;
03534     nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
03535     if (NS_SUCCEEDED(rv) && folderInfo)
03536     {
03537       // save off sort type and order, view type and flags
03538       folderInfo->SetSortType(sortType);
03539       folderInfo->SetSortOrder(sortOrder);
03540     }
03541   }
03542   return NS_OK;
03543 }
03544 
03545 NS_IMETHODIMP nsMsgDBView::Sort(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder)
03546 {
03547   nsresult rv;
03548   
03549   if (m_sortType == sortType && m_sortValid && sortType != nsMsgViewSortType::byCustom) 
03550   {
03551     if (m_sortOrder == sortOrder) 
03552     {
03553       // same as it ever was.  do nothing
03554       return NS_OK;
03555     }   
03556     else 
03557     {
03558       SaveSortInfo(sortType, sortOrder);
03559       if (! (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
03560       {
03561         (void ) ReverseSort(); // doesn't fail.
03562       }
03563       else 
03564       {
03565         rv = ReverseThreads();
03566         NS_ENSURE_SUCCESS(rv,rv);
03567       }
03568       
03569       m_sortOrder = sortOrder;
03570       // we just reversed the sort order...we still need to invalidate the view
03571       return NS_OK;
03572     }
03573   }
03574   
03575   if (sortType == nsMsgViewSortType::byThread) 
03576     return NS_OK;
03577   
03578   SaveSortInfo(sortType, sortOrder);
03579   // figure out how much memory we'll need, and the malloc it
03580   PRUint16 maxLen;
03581   eFieldType fieldType;
03582   
03583   rv = GetFieldTypeAndLenForSort(sortType, &maxLen, &fieldType);
03584   NS_ENSURE_SUCCESS(rv,rv);
03585   
03586   nsVoidArray ptrs;
03587   PRUint32 arraySize = GetSize();
03588   
03589   if (!arraySize)
03590     return NS_OK;
03591   
03592   nsCOMPtr <nsISupportsArray> folders;
03593   GetFolders(getter_AddRefs(folders));
03594   
03595   IdKey** pPtrBase = (IdKey**)PR_Malloc(arraySize * sizeof(IdKey*));
03596   NS_ASSERTION(pPtrBase, "out of memory, can't sort");
03597   if (!pPtrBase) return NS_ERROR_OUT_OF_MEMORY;
03598   ptrs.AppendElement((void *)pPtrBase); // remember this pointer so we can free it later
03599   
03600   // build up the beast, so we can sort it.
03601   PRUint32 numSoFar = 0;
03602   const PRUint32 keyOffset = offsetof(IdKey, key);
03603   // calc max possible size needed for all the rest
03604   PRUint32 maxSize = (keyOffset + maxLen) * (arraySize - numSoFar);
03605   
03606   const PRUint32 maxBlockSize = (PRUint32) 0xf000L;
03607   PRUint32 allocSize = PR_MIN(maxBlockSize, maxSize);
03608   char *pTemp = (char *) PR_Malloc(allocSize);
03609   NS_ASSERTION(pTemp, "out of memory, can't sort");
03610   if (!pTemp) 
03611   {   
03612     FreeAll(&ptrs);
03613     return NS_ERROR_OUT_OF_MEMORY;
03614   }
03615   
03616   ptrs.AppendElement(pTemp); // remember this pointer so we can free it later
03617   
03618   char *pBase = pTemp;
03619   PRBool more = PR_TRUE;
03620   
03621   nsCOMPtr <nsIMsgDBHdr> msgHdr;
03622   PRUint8 *keyValue = nsnull;
03623   PRUint32 longValue;
03624   while (more && numSoFar < arraySize) 
03625   {
03626     nsMsgKey thisKey = m_keys.GetAt(numSoFar);
03627     if (sortType != nsMsgViewSortType::byId) 
03628     {
03629       rv = GetMsgHdrForViewIndex(numSoFar, getter_AddRefs(msgHdr));
03630       NS_ASSERTION(NS_SUCCEEDED(rv) && msgHdr, "header not found");
03631       if (NS_FAILED(rv) || !msgHdr) 
03632       {
03633         FreeAll(&ptrs);
03634         return NS_ERROR_UNEXPECTED;
03635       }
03636     }
03637     else
03638     {
03639       msgHdr = nsnull;
03640     }
03641     
03642     //check if a custom column handler exists. If it does then grab it and pass it in 
03643     //to either GetCollationKey or GetLongField
03644     nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandlerFromDBInfo();
03645 
03646     // could be a problem here if the ones that appear here are different than the ones already in the array
03647     PRUint32 actualFieldLen = 0;
03648     if (fieldType == kCollationKey) 
03649     {
03650       rv = GetCollationKey(msgHdr, sortType, &keyValue, &actualFieldLen, colHandler);
03651       NS_ENSURE_SUCCESS(rv,rv);
03652 
03653       longValue = actualFieldLen;
03654     }
03655     else 
03656     {
03657       if (sortType == nsMsgViewSortType::byId) 
03658       {
03659         longValue = thisKey;
03660       }
03661       else 
03662       {
03663         rv = GetLongField(msgHdr, sortType, &longValue, colHandler);
03664         NS_ENSURE_SUCCESS(rv,rv);
03665       }
03666     }
03667     
03668     // check to see if this entry fits into the block we have allocated so far
03669     // pTemp - pBase = the space we have used so far
03670     // sizeof(EntryInfo) + fieldLen = space we need for this entry
03671     // allocSize = size of the current block
03672     if ((PRUint32)(pTemp - pBase) + (keyOffset + actualFieldLen) >= allocSize)
03673     {
03674       maxSize = (keyOffset + maxLen) * (arraySize - numSoFar);
03675       allocSize = PR_MIN(maxBlockSize, maxSize);
03676       // make sure allocSize is big enough for the current value
03677       allocSize = PR_MAX(allocSize, keyOffset + actualFieldLen);
03678       pTemp = (char *) PR_Malloc(allocSize);
03679       NS_ASSERTION(pTemp, "out of memory, can't sort");
03680       if (!pTemp) 
03681       {
03682         FreeAll(&ptrs);
03683         return NS_ERROR_OUT_OF_MEMORY;
03684       }
03685       pBase = pTemp;
03686       ptrs.AppendElement(pTemp); // remember this pointer so we can free it later
03687     }
03688     
03689     // now store this entry away in the allocated memory
03690     IdKey *info = (IdKey*)pTemp;
03691     pPtrBase[numSoFar] = info;
03692     info->id = thisKey;
03693     info->bits = m_flags.GetAt(numSoFar);
03694     info->dword = longValue;
03695     //info->pad = 0;
03696     
03697     if (folders)
03698     {
03699       nsCOMPtr<nsISupports> curFolder;
03700       folders->GetElementAt(numSoFar, getter_AddRefs(curFolder));
03701       info->folder = curFolder;
03702     }
03703     
03704     memcpy(info->key, keyValue, actualFieldLen);
03705     //In order to align memory for systems that require it, such as HP-UX
03706     //calculate the correct value to pad the actualFieldLen value
03707     const PRUint32 align = sizeof(IdKey) - sizeof(IdDWord) - 1;
03708     actualFieldLen = (actualFieldLen + align) & ~align;
03709     
03710     pTemp += keyOffset + actualFieldLen;
03711     ++numSoFar;
03712     PR_Free(keyValue);
03713   }
03714     
03715   // do the sort
03716   switch (fieldType) 
03717   {
03718     case kCollationKey:
03719     {
03720       
03721       nsCOMPtr <nsIMsgDatabase> dbToUse = m_db;
03722     
03723       if (!dbToUse) // probably search view
03724         GetDBForViewIndex(0, getter_AddRefs(dbToUse));
03725       if (dbToUse)
03726         NS_QuickSort(pPtrBase, numSoFar, sizeof(IdKey*), FnSortIdKey, dbToUse);
03727     }
03728       break;
03729     case kU32:
03730       NS_QuickSort(pPtrBase, numSoFar, sizeof(IdKey*), FnSortIdDWord, nsnull);
03731       break;
03732     default:
03733       NS_ASSERTION(0, "not supposed to get here");
03734       break;
03735   }
03736     
03737   // now put the IDs into the array in proper order
03738   for (PRUint32 i = 0; i < numSoFar; i++) 
03739   {
03740     m_keys.SetAt(i, pPtrBase[i]->id);
03741     m_flags.SetAt(i, pPtrBase[i]->bits);
03742     
03743     if (folders)
03744       folders->SetElementAt(i, pPtrBase[i]->folder);
03745   }
03746   
03747   m_sortType = sortType;
03748   m_sortOrder = sortOrder;
03749   
03750   if (sortOrder == nsMsgViewSortOrder::descending) 
03751   {
03752     rv = ReverseSort();
03753     NS_ASSERTION(NS_SUCCEEDED(rv),"failed to reverse sort");
03754   }
03755   
03756   // free all the memory we allocated
03757   FreeAll(&ptrs);
03758   
03759   m_sortValid = PR_TRUE;
03760   //m_db->SetSortInfo(sortType, sortOrder);
03761   
03762   return NS_OK;
03763 }
03764 
03765 void nsMsgDBView::FreeAll(nsVoidArray *ptrs)
03766 {
03767   PRInt32 i;
03768   PRInt32 count = (PRInt32) ptrs->Count();
03769   if (count == 0) 
03770     return;
03771 
03772   for (i=(count - 1);i>=0;i--) 
03773     PR_Free((void *) ptrs->ElementAt(i));
03774   ptrs->Clear();
03775 }
03776 
03777 nsMsgViewIndex nsMsgDBView::GetIndexOfFirstDisplayedKeyInThread(nsIMsgThread *threadHdr)
03778 {
03779   nsMsgViewIndex  retIndex = nsMsgViewIndex_None;
03780   PRUint32        childIndex = 0;
03781   // We could speed up the unreadOnly view by starting our search with the first
03782   // unread message in the thread. Sometimes, that will be wrong, however, so
03783   // let's skip it until we're sure it's necessary.
03784   //   (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly) 
03785   //          ? threadHdr->GetFirstUnreadKey(m_db) : threadHdr->GetChildAt(0);
03786   PRUint32 numThreadChildren;
03787   threadHdr->GetNumChildren(&numThreadChildren);
03788   while (retIndex == nsMsgViewIndex_None && childIndex < numThreadChildren)
03789   {
03790     nsMsgKey childKey;
03791     threadHdr->GetChildKeyAt(childIndex++, &childKey);
03792     retIndex = FindViewIndex(childKey);
03793   }
03794   return retIndex;
03795 }
03796 
03797 nsresult nsMsgDBView::GetFirstMessageHdrToDisplayInThread(nsIMsgThread *threadHdr, nsIMsgDBHdr **result)
03798 {
03799   nsresult rv;
03800   
03801   if (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly)
03802     rv = threadHdr->GetFirstUnreadChild(result);
03803   else
03804     rv = threadHdr->GetChildHdrAt(0, result);
03805   return rv;
03806 }
03807 
03808 // Find the view index of the thread containing the passed msgKey, if
03809 // the thread is in the view. MsgIndex is passed in as a shortcut if
03810 // it turns out the msgKey is the first message in the thread,
03811 // then we can avoid looking for the msgKey.
03812 nsMsgViewIndex nsMsgDBView::ThreadIndexOfMsg(nsMsgKey msgKey, 
03813                                             nsMsgViewIndex msgIndex /* = nsMsgViewIndex_None */,
03814                                             PRInt32 *pThreadCount /* = NULL */,
03815                                             PRUint32 *pFlags /* = NULL */)
03816 {
03817   if (! (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
03818     return nsMsgViewIndex_None;
03819   nsCOMPtr <nsIMsgThread> threadHdr;
03820   nsCOMPtr <nsIMsgDBHdr> msgHdr;
03821   nsresult rv = m_db->GetMsgHdrForKey(msgKey, getter_AddRefs(msgHdr));
03822   NS_ENSURE_SUCCESS(rv, nsMsgViewIndex_None);
03823   rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(threadHdr));
03824   NS_ENSURE_SUCCESS(rv, nsMsgViewIndex_None);
03825   
03826   nsMsgViewIndex retIndex = nsMsgViewIndex_None;
03827   
03828   if (threadHdr != nsnull)
03829   {
03830     if (msgIndex == nsMsgViewIndex_None)
03831       msgIndex = FindViewIndex(msgKey);
03832     
03833     if (msgIndex == nsMsgViewIndex_None)  // key is not in view, need to find by thread
03834     {
03835       msgIndex = GetIndexOfFirstDisplayedKeyInThread(threadHdr);
03836       //nsMsgKey            threadKey = (msgIndex == nsMsgViewIndex_None) ? nsMsgKey_None : GetAt(msgIndex);
03837       if (pFlags)
03838         threadHdr->GetFlags(pFlags);
03839     }
03840     nsMsgViewIndex startOfThread = msgIndex;
03841     while ((PRInt32) startOfThread >= 0 && m_levels[startOfThread] != 0)
03842       startOfThread--;
03843     retIndex = startOfThread;
03844     if (pThreadCount)
03845     {
03846       PRInt32 numChildren = 0;
03847       nsMsgViewIndex threadIndex = startOfThread;
03848       do
03849       {
03850         threadIndex++;
03851         numChildren++;
03852       }
03853       while ((int32) threadIndex < m_levels.GetSize() && m_levels[threadIndex] != 0);
03854       *pThreadCount = numChildren;
03855     }
03856   }
03857   return retIndex;
03858 }
03859 
03860 nsMsgKey nsMsgDBView::GetKeyOfFirstMsgInThread(nsMsgKey key)
03861 {
03862   nsCOMPtr <nsIMsgThread> pThread;
03863   nsCOMPtr <nsIMsgDBHdr> msgHdr;
03864   nsresult rv = m_db->GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
03865   NS_ENSURE_SUCCESS(rv, rv);
03866   rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(pThread));
03867   NS_ENSURE_SUCCESS(rv, rv);
03868   nsMsgKey    firstKeyInThread = nsMsgKey_None;
03869   
03870   NS_ASSERTION(pThread, "error getting msg from thread");
03871   if (!pThread)
03872     return firstKeyInThread;
03873   
03874   // ### dmb UnreadOnly - this is wrong. But didn't seem to matter in 4.x
03875   pThread->GetChildKeyAt(0, &firstKeyInThread);
03876   return firstKeyInThread;
03877 }
03878 
03879 NS_IMETHODIMP nsMsgDBView::GetKeyAt(nsMsgViewIndex index, nsMsgKey *result)
03880 {
03881   NS_ENSURE_ARG(result);
03882   *result = GetAt(index);
03883   return NS_OK;
03884 }
03885 
03886 nsMsgViewIndex nsMsgDBView::FindHdr(nsIMsgDBHdr *msgHdr)
03887 {
03888   nsMsgKey msgKey;
03889   msgHdr->GetMessageKey(&msgKey);
03890   return FindViewIndex(msgKey);
03891 }
03892 
03893 nsMsgKey nsMsgDBView::GetAt(nsMsgViewIndex index) 
03894 {
03895   if (index >= m_keys.GetSize() || index == nsMsgViewIndex_None)
03896     return nsMsgKey_None;
03897   else
03898     return(m_keys.GetAt(index));
03899 }
03900 
03901 nsMsgViewIndex       nsMsgDBView::FindKey(nsMsgKey key, PRBool expand)
03902 {
03903   nsMsgViewIndex retIndex = nsMsgViewIndex_None;
03904   retIndex = (nsMsgViewIndex) (m_keys.FindIndex(key));
03905   // for dummy headers, try to expand if the caller says so. And if the thread is
03906   // expanded, ignore the dummy header and return the real header index.
03907   if (retIndex != nsMsgViewIndex_None && m_flags[retIndex] & MSG_VIEW_FLAG_DUMMY &&  !(m_flags[retIndex] & MSG_FLAG_ELIDED))
03908     return (nsMsgViewIndex) m_keys.FindIndex(key, retIndex + 1);
03909   if (key != nsMsgKey_None && (retIndex == nsMsgViewIndex_None || m_flags[retIndex] & MSG_VIEW_FLAG_DUMMY)
03910     && expand && m_db)
03911   {
03912     nsMsgKey threadKey = GetKeyOfFirstMsgInThread(key);
03913     if (threadKey != nsMsgKey_None)
03914     {
03915       nsMsgViewIndex threadIndex = FindKey(threadKey, PR_FALSE);
03916       if (threadIndex != nsMsgViewIndex_None)
03917       {
03918         PRUint32 flags = m_flags[threadIndex];
03919         if ((flags & MSG_FLAG_ELIDED) && NS_SUCCEEDED(ExpandByIndex(threadIndex, nsnull))
03920           || (flags & MSG_VIEW_FLAG_DUMMY))
03921           retIndex = (nsMsgViewIndex) m_keys.FindIndex(key, threadIndex + 1);
03922       }
03923     }
03924   }
03925   return retIndex;
03926 }
03927 
03928 nsresult nsMsgDBView::GetThreadCount(nsMsgKey messageKey, PRUint32 *pThreadCount)
03929 {
03930   nsresult rv = NS_MSG_MESSAGE_NOT_FOUND;
03931   nsCOMPtr <nsIMsgDBHdr> msgHdr;
03932   rv = m_db->GetMsgHdrForKey(messageKey, getter_AddRefs(msgHdr));
03933   NS_ENSURE_SUCCESS(rv, rv);
03934   nsCOMPtr <nsIMsgThread> pThread;
03935   rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(pThread));
03936   if (NS_SUCCEEDED(rv) && pThread != nsnull)
03937     rv = pThread->GetNumChildren(pThreadCount);
03938   return rv;
03939 }
03940 
03941 // This counts the number of messages in an expanded thread, given the
03942 // index of the first message in the thread.
03943 PRInt32 nsMsgDBView::CountExpandedThread(nsMsgViewIndex index)
03944 {
03945   PRInt32 numInThread = 0;
03946   nsMsgViewIndex startOfThread = index;
03947   while ((PRInt32) startOfThread >= 0 && m_levels[startOfThread] != 0)
03948     startOfThread--;
03949   nsMsgViewIndex threadIndex = startOfThread;
03950   do
03951   {
03952     threadIndex++;
03953     numInThread++;
03954   }
03955   while ((PRInt32) threadIndex < m_levels.GetSize() && m_levels[threadIndex] != 0);
03956   
03957   return numInThread;
03958 }
03959 
03960 // returns the number of lines that would be added (> 0) or removed (< 0) 
03961 // if we were to try to expand/collapse the passed index.
03962 nsresult nsMsgDBView::ExpansionDelta(nsMsgViewIndex index, PRInt32 *expansionDelta)
03963 {
03964        PRUint32 numChildren;
03965        nsresult      rv;
03966 
03967        *expansionDelta = 0;
03968        if ( index > ((nsMsgViewIndex) m_keys.GetSize()))
03969               return NS_MSG_MESSAGE_NOT_FOUND;
03970        char   flags = m_flags[index];
03971 
03972        if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
03973               return NS_OK;
03974 
03975        // The client can pass in the key of any message
03976        // in a thread and get the expansion delta for the thread.
03977 
03978        if (!(m_viewFlags & nsMsgViewFlagsType::kUnreadOnly))
03979        {
03980               rv = GetThreadCount(m_keys[index], &numChildren);
03981               NS_ENSURE_SUCCESS(rv, rv);
03982        }
03983        else
03984        {
03985               numChildren = CountExpandedThread(index);
03986        }
03987 
03988        if (flags & MSG_FLAG_ELIDED)
03989               *expansionDelta = numChildren - 1;
03990        else
03991               *expansionDelta = - (PRInt32) (numChildren - 1);
03992 
03993        return NS_OK;
03994 }
03995 
03996 nsresult nsMsgDBView::ToggleExpansion(nsMsgViewIndex index, PRUint32 *numChanged)
03997 {
03998   NS_ENSURE_ARG(numChanged);
03999   *numChanged = 0;
04000        nsMsgViewIndex threadIndex = ThreadIndexOfMsg(GetAt(index), index);
04001        if (threadIndex == nsMsgViewIndex_None)
04002        {
04003               NS_ASSERTION(PR_FALSE, "couldn't find thread");
04004               return NS_MSG_MESSAGE_NOT_FOUND;
04005        }
04006        PRInt32       flags = m_flags[threadIndex];
04007 
04008        // if not a thread, or doesn't have children, no expand/collapse
04009        // If we add sub-thread expand collapse, this will need to be relaxed
04010        if (!(flags & MSG_VIEW_FLAG_ISTHREAD) || !(flags & MSG_VIEW_FLAG_HASCHILDREN))
04011               return NS_MSG_MESSAGE_NOT_FOUND;
04012        if (flags & MSG_FLAG_ELIDED)
04013               return ExpandByIndex(threadIndex, numChanged);
04014        else
04015               return CollapseByIndex(threadIndex, numChanged);
04016 
04017 }
04018 
04019 nsresult nsMsgDBView::ExpandAndSelectThread()
04020 {
04021     nsresult rv;
04022 
04023     NS_ASSERTION(mTreeSelection, "no tree selection");
04024     if (!mTreeSelection) return NS_ERROR_UNEXPECTED;
04025 
04026     PRInt32 index;
04027     rv = mTreeSelection->GetCurrentIndex(&index);
04028     NS_ENSURE_SUCCESS(rv,rv);
04029 
04030     rv = ExpandAndSelectThreadByIndex(index, PR_FALSE);
04031     NS_ENSURE_SUCCESS(rv,rv);
04032     return NS_OK;
04033 }
04034 
04035 nsresult nsMsgDBView::ExpandAndSelectThreadByIndex(nsMsgViewIndex index, PRBool augment)
04036 {
04037   nsresult rv;
04038 
04039   nsMsgViewIndex threadIndex;
04040   PRBool inThreadedMode = (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay);
04041   
04042   if (inThreadedMode) 
04043   {
04044     threadIndex = ThreadIndexOfMsg(GetAt(index), index);
04045     if (threadIndex == nsMsgViewIndex_None) 
04046     {
04047       NS_ASSERTION(PR_FALSE, "couldn't find thread");
04048       return NS_MSG_MESSAGE_NOT_FOUND;
04049     }
04050   }
04051   else 
04052   {
04053     threadIndex = index;
04054   }
04055 
04056   PRInt32 flags = m_flags[threadIndex];
04057   PRInt32 count = 0;
04058 
04059   if (inThreadedMode && (flags & MSG_VIEW_FLAG_ISTHREAD) && (flags & MSG_VIEW_FLAG_HASCHILDREN)) 
04060   {
04061     // if closed, expand this thread.
04062     if (flags & MSG_FLAG_ELIDED) 
04063     {
04064       PRUint32 numExpanded;
04065       rv = ExpandByIndex(threadIndex, &numExpanded);
04066       NS_ENSURE_SUCCESS(rv,rv);
04067     }
04068 
04069     // get the number of messages in the expanded thread
04070     // so we know how many to select
04071     count = CountExpandedThread(threadIndex); 
04072   }
04073   else 
04074   {
04075     count = 1;
04076   }
04077   NS_ASSERTION(count > 0, "bad count");
04078 
04079   // update the selection
04080 
04081   NS_ASSERTION(mTreeSelection, "no tree selection");
04082   if (!mTreeSelection) return NS_ERROR_UNEXPECTED;
04083 
04084   // the count should be 1 or greater. if there was only one message in the thread, we just select it.
04085   // if more, we select all of them.
04086   mTreeSelection->RangedSelect(threadIndex + count - 1, threadIndex, augment);
04087   return NS_OK;
04088 }
04089 
04090 nsresult nsMsgDBView::ExpandAll()
04091 {
04092   if (mTree)
04093     mTree->BeginUpdateBatch();
04094   for (PRInt32 i = GetSize() - 1; i >= 0; i--) 
04095   {
04096     PRUint32 numExpanded;
04097     PRUint32 flags = m_flags[i];
04098     if (flags & MSG_FLAG_ELIDED)
04099       ExpandByIndex(i, &numExpanded);
04100   }
04101   if (mTree)
04102     mTree->EndUpdateBatch();
04103   return NS_OK;
04104 }
04105 
04106 nsresult nsMsgDBView::GetThreadContainingMsgHdr(nsIMsgDBHdr *msgHdr, nsIMsgThread **pThread)
04107 {
04108   return m_db->GetThreadContainingMsgHdr(msgHdr, pThread);
04109 }
04110 
04111 nsresult nsMsgDBView::ExpandByIndex(nsMsgViewIndex index, PRUint32 *pNumExpanded)
04112 {
04113   PRUint32                  flags = m_flags[index];
04114   nsMsgKey           firstIdInThread;
04115   //nsMsgKey        startMsg = nsMsgKey_None;
04116   nsresult           rv = NS_OK;
04117   PRUint32                  numExpanded = 0;
04118   
04119   NS_ASSERTION(flags & MSG_FLAG_ELIDED, "can't expand an already expanded thread");
04120   flags &= ~MSG_FLAG_ELIDED;
04121   
04122   if ((PRUint32) index > m_keys.GetSize())
04123     return NS_MSG_MESSAGE_NOT_FOUND;
04124   
04125   firstIdInThread = m_keys[index];
04126   nsCOMPtr <nsIMsgDBHdr> msgHdr;
04127   nsCOMPtr <nsIMsgThread> pThread;
04128   m_db->GetMsgHdrForKey(firstIdInThread, getter_AddRefs(msgHdr));
04129   if (msgHdr == nsnull)
04130   {
04131     NS_ASSERTION(PR_FALSE, "couldn't find message to expand");
04132     return NS_MSG_MESSAGE_NOT_FOUND;
04133   }
04134   rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(pThread));
04135   NS_ENSURE_SUCCESS(rv, rv);
04136   m_flags[index] = flags;
04137   NoteChange(index, 1, nsMsgViewNotificationCode::changed);
04138   if (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly)
04139   {
04140     if (flags & MSG_FLAG_READ)
04141       m_levels.Add(0);      // keep top level hdr in thread, even though read.
04142     rv = ListUnreadIdsInThread(pThread,  index, &numExpanded);
04143   }
04144   else
04145     rv = ListIdsInThread(pThread,  index, &numExpanded);
04146   
04147   NoteStartChange(index + 1, numExpanded, nsMsgViewNotificationCode::insertOrDelete);
04148   
04149   NoteEndChange(index + 1, numExpanded, nsMsgViewNotificationCode::insertOrDelete);
04150   if (pNumExpanded != nsnull)
04151     *pNumExpanded = numExpanded;
04152   return rv;
04153 }
04154 
04155 nsresult nsMsgDBView::CollapseAll()
04156 {
04157   for (PRInt32 i = 0; i < GetSize(); i++)
04158   {
04159     PRUint32 numExpanded;
04160     PRUint32 flags = m_flags[i];
04161     if (!(flags & MSG_FLAG_ELIDED) && (flags & MSG_VIEW_FLAG_HASCHILDREN))
04162       CollapseByIndex(i, &numExpanded);
04163   }
04164   return NS_OK;
04165 }
04166 
04167 nsresult nsMsgDBView::CollapseByIndex(nsMsgViewIndex index, PRUint32 *pNumCollapsed)
04168 {
04169   nsMsgKey           firstIdInThread;
04170   nsresult    rv;
04171   PRInt32     flags = m_flags[index];
04172   PRInt32     threadCount = 0;
04173   
04174   if (flags & MSG_FLAG_ELIDED || !(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) || !(flags & MSG_VIEW_FLAG_HASCHILDREN))
04175     return NS_OK;
04176   flags  |= MSG_FLAG_ELIDED;
04177   
04178   if (index > m_keys.GetSize())
04179     return NS_MSG_MESSAGE_NOT_FOUND;
04180   
04181   firstIdInThread = m_keys[index];
04182   nsCOMPtr <nsIMsgDBHdr> msgHdr;
04183   rv = m_db->GetMsgHdrForKey(firstIdInThread, getter_AddRefs(msgHdr));
04184   if (NS_FAILED(rv) || msgHdr == nsnull)
04185   {
04186     NS_ASSERTION(PR_FALSE, "error collapsing thread");
04187     return NS_MSG_MESSAGE_NOT_FOUND;
04188   }
04189   
04190   m_flags[index] = flags;
04191   NoteChange(index, 1, nsMsgViewNotificationCode::changed);
04192   
04193   rv = ExpansionDelta(index, &threadCount);
04194   if (NS_SUCCEEDED(rv))
04195   {
04196     PRInt32 numRemoved = threadCount; // don't count first header in thread
04197     NoteStartChange(index + 1, -numRemoved, nsMsgViewNotificationCode::insertOrDelete);
04198     // start at first id after thread.
04199     for (int i = 1; i <= threadCount && index + 1 < m_keys.GetSize(); i++)
04200     {
04201       m_keys.RemoveAt(index + 1);
04202       m_flags.RemoveAt(index + 1);
04203       m_levels.RemoveAt(index + 1);
04204     }
04205     if (pNumCollapsed != nsnull)
04206       *pNumCollapsed = numRemoved; 
04207     NoteEndChange(index + 1, -numRemoved, nsMsgViewNotificationCode::insertOrDelete);
04208   }
04209   return rv;
04210 }
04211 
04212 nsresult nsMsgDBView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, PRBool /*ensureListed*/)
04213 {
04214     nsresult rv = NS_OK;
04215     // views can override this behaviour, which is to append to view.
04216     // This is the mail behaviour, but threaded views will want
04217     // to insert in order...
04218     if (newHdr)
04219        rv = AddHdr(newHdr);
04220     return rv;
04221 }
04222 
04223 nsresult nsMsgDBView::GetThreadContainingIndex(nsMsgViewIndex index, nsIMsgThread **resultThread)
04224 {
04225   nsCOMPtr <nsIMsgDBHdr> msgHdr;
04226 
04227   NS_ENSURE_TRUE(m_db, NS_ERROR_NULL_POINTER);
04228 
04229   nsresult rv = m_db->GetMsgHdrForKey(m_keys[index], getter_AddRefs(msgHdr));
04230   NS_ENSURE_SUCCESS(rv, rv);
04231   return GetThreadContainingMsgHdr(msgHdr, resultThread);
04232 }
04233 
04234 nsMsgViewIndex nsMsgDBView::GetIndexForThread(nsIMsgDBHdr *hdr)
04235 {
04236   nsMsgViewIndex retIndex = nsMsgViewIndex_None;
04237   nsMsgViewIndex prevInsertIndex = nsMsgViewIndex_None;
04238   nsMsgKey insertKey;
04239   hdr->GetMessageKey(&insertKey);
04240   
04241   if (m_sortOrder == nsMsgViewSortOrder::ascending)
04242   {
04243     // loop backwards looking for top level message with id > id of header we're inserting 
04244     // and put new header before found header, or at end.
04245     for (PRInt32 i = GetSize() - 1; i >= 0; i--) 
04246     {
04247       if (m_levels[i] == 0)
04248       {
04249         if (insertKey < m_keys.GetAt(i))
04250           prevInsertIndex = i;
04251         else if (insertKey >= m_keys.GetAt(i))
04252         {
04253           retIndex = (prevInsertIndex == nsMsgViewIndex_None) ? nsMsgViewIndex_None : i + 1;
04254           if (prevInsertIndex == nsMsgViewIndex_None)
04255           {
04256             retIndex = nsMsgViewIndex_None;
04257           }
04258           else
04259           {
04260             for (retIndex = i + 1; retIndex < (nsMsgViewIndex)GetSize(); retIndex++)
04261             {
04262               if (m_levels[retIndex] == 0)
04263                 break;
04264             }
04265           }
04266           break;
04267         }
04268         
04269       }
04270     }
04271   }
04272   else
04273   {
04274     // loop forwards looking for top level message with id < id of header we're inserting and put 
04275     // new header before found header, or at beginning.
04276     for (PRInt32 i = 0; i < GetSize(); i++) 
04277     {
04278       if (!m_levels[i])
04279       {
04280         if (insertKey > m_keys.GetAt(i))
04281         {
04282           retIndex = i;
04283           break;
04284         }
04285       }
04286     }
04287   }
04288   return retIndex;
04289 }
04290 
04291 nsMsgViewIndex nsMsgDBView::GetInsertIndexHelper(nsIMsgDBHdr *msgHdr, nsMsgKeyArray *keys, 
04292                                                  nsMsgViewSortOrderValue sortOrder, nsMsgViewSortTypeValue sortType)
04293 {
04294   nsMsgViewIndex highIndex = keys->GetSize();
04295   nsMsgViewIndex lowIndex = 0;
04296   IdKeyPtr EntryInfo1, EntryInfo2;
04297   EntryInfo1.key = nsnull;
04298   EntryInfo2.key = nsnull;
04299   void *comparisonContext = nsnull;
04300   
04301   nsresult rv;
04302   PRUint16    maxLen;
04303   eFieldType fieldType;
04304   rv = GetFieldTypeAndLenForSort(sortType, &maxLen, &fieldType);
04305   const void *pValue1 = &EntryInfo1, *pValue2 = &EntryInfo2;
04306   
04307   int (* PR_CALLBACK comparisonFun) (const void *pItem1, const void *pItem2, void *privateData)=nsnull;
04308   int retStatus = 0;
04309   msgHdr->GetMessageKey(&EntryInfo1.id);
04310   
04311   //check if a custom column handler exists. If it does then grab it and pass it in 
04312   //to either GetCollationKey or GetLongField
04313   nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandlerFromDBInfo();
04314   
04315   switch (fieldType)
04316   {
04317     case kCollationKey:
04318       rv = GetCollationKey(msgHdr, sortType, &EntryInfo1.key, &EntryInfo1.dword, colHandler);
04319       NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key");
04320       comparisonFun = FnSortIdKeyPtr;
04321       comparisonContext = m_db.get();
04322       break;
04323     case kU32:
04324       if (sortType == nsMsgViewSortType::byId) 
04325         EntryInfo1.dword = EntryInfo1.id;
04326       else
04327         GetLongField(msgHdr, sortType, &EntryInfo1.dword, colHandler);
04328       comparisonFun = FnSortIdDWord;
04329       break;
04330     default:
04331       return highIndex;
04332   }
04333   while (highIndex > lowIndex)
04334   {
04335     nsMsgViewIndex tryIndex = (lowIndex + highIndex - 1) / 2;
04336     EntryInfo2.id = keys->GetAt(tryIndex);
04337     nsCOMPtr <nsIMsgDBHdr> tryHdr;
04338     nsCOMPtr <nsIMsgDatabase> db;
04339     GetDBForViewIndex(tryIndex, getter_AddRefs(db));
04340     if (db)
04341       rv = db->GetMsgHdrForKey(EntryInfo2.id, getter_AddRefs(tryHdr));
04342     if (!tryHdr)
04343       break;
04344     if (fieldType == kCollationKey)
04345     {
04346       PR_FREEIF(EntryInfo2.key);
04347       rv = GetCollationKey(tryHdr, sortType, &EntryInfo2.key, &EntryInfo2.dword, colHandler);
04348       NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key");
04349     }
04350     else if (fieldType == kU32)
04351     {
04352       if (sortType == nsMsgViewSortType::byId) {
04353         EntryInfo2.dword = EntryInfo2.id;
04354       }
04355       else {
04356         GetLongField(tryHdr, sortType, &EntryInfo2.dword, colHandler);
04357       }
04358     }
04359     retStatus = (*comparisonFun)(&pValue1, &pValue2, comparisonContext);
04360     if (retStatus == 0)
04361     {
04362       highIndex = tryIndex;
04363       break;
04364     }
04365     if (sortOrder == nsMsgViewSortOrder::descending)    //switch retStatus based on sort order
04366       retStatus = ~retStatus;
04367     
04368     if (retStatus < 0)
04369     {
04370       highIndex = tryIndex;
04371     }
04372     else
04373     {
04374       lowIndex = tryIndex + 1;
04375     }
04376   }
04377   
04378   PR_Free(EntryInfo1.key);
04379   PR_Free(EntryInfo2.key);
04380   return highIndex;
04381 }
04382 
04383 nsMsgViewIndex nsMsgDBView::GetInsertIndex(nsIMsgDBHdr *msgHdr)
04384 {
04385   if (!GetSize())
04386     return 0;
04387 
04388   if ((m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) != 0
04389         && !(m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
04390         && m_sortOrder != nsMsgViewSortType::byId)
04391     return GetIndexForThread(msgHdr);
04392 
04393   return GetInsertIndexHelper(msgHdr, &m_keys, m_sortOrder, m_sortType);
04394 }
04395 
04396 nsresult      nsMsgDBView::AddHdr(nsIMsgDBHdr *msgHdr)
04397 {
04398   PRUint32    flags = 0;
04399 #ifdef DEBUG_bienvenu
04400   NS_ASSERTION((int) m_keys.GetSize() == m_flags.GetSize() && (int) m_keys.GetSize() == m_levels.GetSize(), "view arrays out of sync!");
04401 #endif
04402 
04403   if (!GetShowingIgnored()) 
04404   {
04405     nsCOMPtr <nsIMsgThread> thread;
04406     m_db->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread));
04407     if (thread)
04408     {
04409       thread->GetFlags(&flags);
04410       if (flags & MSG_FLAG_IGNORED)
04411         return NS_OK;
04412     }
04413   }
04414 
04415   nsMsgKey msgKey, threadId;
04416   nsMsgKey threadParent;
04417   msgHdr->GetMessageKey(&msgKey);
04418   msgHdr->GetThreadId(&threadId);
04419   msgHdr->GetThreadParent(&threadParent);
04420 
04421   msgHdr->GetFlags(&flags);
04422   // ### this isn't quite right, is it? Should be checking that our thread parent key is none?
04423   if (threadParent == nsMsgKey_None) 
04424     flags |= MSG_VIEW_FLAG_ISTHREAD;
04425   nsMsgViewIndex insertIndex = GetInsertIndex(msgHdr);
04426   if (insertIndex == nsMsgViewIndex_None)
04427   {
04428     // if unreadonly, level is 0 because we must be the only msg in the thread.
04429     PRInt32 levelToAdd = 0;
04430     
04431     if (m_sortOrder == nsMsgViewSortOrder::ascending)
04432     {
04433       m_keys.Add(msgKey);
04434       m_flags.Add(flags);
04435       m_levels.Add(levelToAdd);
04436       
04437       // the call to NoteChange() has to happen after we add the key
04438       // as NoteChange() will call RowCountChanged() which will call our GetRowCount()
04439       NoteChange(GetSize() - 1, 1, nsMsgViewNotificationCode::insertOrDelete);
04440     }
04441     else
04442     {
04443       m_keys.InsertAt(0, msgKey);
04444       m_flags.InsertAt(0, flags);
04445       m_levels.InsertAt(0, levelToAdd);
04446       
04447       // the call to NoteChange() has to happen after we insert the key
04448       // as NoteChange() will call RowCountChanged() which will call our GetRowCount()
04449       NoteChange(0, 1, nsMsgViewNotificationCode::insertOrDelete);
04450     }
04451     m_sortValid = PR_FALSE;
04452   }
04453   else
04454   {
04455     m_keys.InsertAt(insertIndex, msgKey);
04456     m_flags.InsertAt(insertIndex, flags);
04457     PRInt32 level = 0; 
04458     m_levels.InsertAt(insertIndex, level);
04459     
04460     // the call to NoteChange() has to happen after we add the key
04461     // as NoteChange() will call RowCountChanged() which will call our GetRowCount()
04462     NoteChange(insertIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
04463   }
04464   OnHeaderAddedOrDeleted();
04465   return NS_OK;
04466 }
04467 
04468 PRBool nsMsgDBView::WantsThisThread(nsIMsgThread * /*threadHdr*/)
04469 {
04470   return PR_TRUE; // default is to want all threads.
04471 }
04472 
04473 nsMsgViewIndex nsMsgDBView::FindParentInThread(nsMsgKey parentKey, nsMsgViewIndex startOfThreadViewIndex)
04474 {
04475   nsCOMPtr<nsIMsgDBHdr> msgHdr;
04476   while (parentKey != nsMsgKey_None)
04477   {
04478     nsMsgViewIndex parentIndex = m_keys.FindIndex(parentKey, startOfThreadViewIndex);
04479     if (parentIndex != nsMsgViewIndex_None)
04480       return parentIndex;
04481 
04482     if (NS_FAILED(m_db->GetMsgHdrForKey(parentKey, getter_AddRefs(msgHdr))))
04483       break;
04484 
04485     msgHdr->GetThreadParent(&parentKey);
04486   }
04487 
04488   return startOfThreadViewIndex;
04489 }
04490 
04491 nsresult nsMsgDBView::ListIdsInThreadOrder(nsIMsgThread *threadHdr, nsMsgKey parentKey, PRInt32 level, nsMsgViewIndex *viewIndex, PRUint32 *pNumListed)
04492 {
04493   nsresult rv = NS_OK;
04494   nsCOMPtr <nsISimpleEnumerator> msgEnumerator;
04495   threadHdr->EnumerateMessages(parentKey, getter_AddRefs(msgEnumerator));
04496   PRUint32 numChildren;
04497   (void) threadHdr->GetNumChildren(&numChildren);
04498 
04499   // skip the first one.
04500   PRBool hasMore;
04501   nsCOMPtr <nsISupports> supports;
04502   nsCOMPtr <nsIMsgDBHdr> msgHdr;
04503   while (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv = msgEnumerator->HasMoreElements(&hasMore)) && hasMore)
04504   {
04505     rv = msgEnumerator->GetNext(getter_AddRefs(supports));
04506     if (NS_SUCCEEDED(rv) && supports)
04507     {
04508       msgHdr = do_QueryInterface(supports);
04509       nsMsgKey msgKey;
04510       PRUint32 msgFlags, newFlags;
04511       msgHdr->GetMessageKey(&msgKey);
04512       msgHdr->GetFlags(&msgFlags);
04513       AdjustReadFlag(msgHdr, &msgFlags);
04514       m_keys.InsertAt(*viewIndex, msgKey);
04515       // ### TODO - how about hasChildren flag?
04516       m_flags.InsertAt(*viewIndex, msgFlags & ~MSG_VIEW_FLAGS);
04517       // ### TODO this is going to be tricky - might use enumerators
04518       m_levels.InsertAt(*viewIndex, level); 
04519       // turn off thread or elided bit if they got turned on (maybe from new only view?)
04520       msgHdr->AndFlags(~(MSG_VIEW_FLAG_ISTHREAD | MSG_FLAG_ELIDED), &newFlags);
04521       (*pNumListed)++;
04522       (*viewIndex)++;
04523       if (*pNumListed > numChildren)
04524       {
04525         NS_ASSERTION(PR_FALSE, "thread corrupt in db");
04526         // if we've listed more messages than are in the thread, then the db
04527         // is corrupt, and we should invalidate it.
04528         // we'll use this rv to indicate there's something wrong with the db
04529         // though for now it probably won't get paid attention to.
04530         m_db->SetSummaryValid(PR_FALSE);
04531         rv = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
04532         break;
04533       }
04534       rv = ListIdsInThreadOrder(threadHdr, msgKey, level + 1, viewIndex, pNumListed);
04535     }
04536   }
04537   return rv; // we don't want to return the rv from the enumerator when it reaches the end, do we?
04538 }
04539 
04540 nsresult nsMsgDBView::ListIdsInThread(nsIMsgThread *threadHdr, nsMsgViewIndex startOfThreadViewIndex, PRUint32 *pNumListed)
04541 {
04542   NS_ENSURE_ARG(threadHdr);
04543   // these children ids should be in thread order.
04544   PRUint32 i;
04545   nsMsgViewIndex viewIndex = startOfThreadViewIndex + 1;
04546   *pNumListed = 0;
04547 
04548   // ### need to rework this when we implemented threading in group views.
04549   if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay && ! (m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
04550   {
04551     nsMsgKey parentKey = m_keys[startOfThreadViewIndex];
04552 
04553     return ListIdsInThreadOrder(threadHdr, parentKey, 1, &viewIndex, pNumListed);
04554   }
04555   // if we're not threaded, just list em out in db order
04556 
04557   PRUint32 numChildren;
04558   threadHdr->GetNumChildren(&numChildren);
04559   for (i = 1; i < numChildren; i++)
04560   {
04561     nsCOMPtr <nsIMsgDBHdr> msgHdr;
04562     threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr));
04563     if (msgHdr != nsnull)
04564     {
04565       nsMsgKey msgKey;
04566       PRUint32 msgFlags, newFlags;
04567       msgHdr->GetMessageKey(&msgKey);
04568       msgHdr->GetFlags(&msgFlags);
04569       AdjustReadFlag(msgHdr, &msgFlags);
04570       m_keys.InsertAt(viewIndex, msgKey);
04571       m_flags.InsertAt(viewIndex, msgFlags & ~MSG_VIEW_FLAGS);
04572       // here, we're either flat, or we're grouped - in either case, level is 1
04573       m_levels.InsertAt(viewIndex, 1);
04574       // turn off thread or elided bit if they got turned on (maybe from new only view?)
04575       if (i > 0)     
04576         msgHdr->AndFlags(~(MSG_VIEW_FLAG_ISTHREAD | MSG_FLAG_ELIDED), &newFlags);
04577       (*pNumListed)++;
04578       viewIndex++;
04579     }
04580   }
04581   return NS_OK;
04582 }
04583 
04584 PRInt32 nsMsgDBView::FindLevelInThread(nsIMsgDBHdr *msgHdr, nsMsgViewIndex startOfThread, nsMsgViewIndex viewIndex)
04585 {
04586   nsCOMPtr <nsIMsgDBHdr> curMsgHdr = msgHdr;
04587   nsMsgKey msgKey;
04588   msgHdr->GetMessageKey(&msgKey);
04589 
04590   // look through the ancestors of the passed in msgHdr in turn, looking for them in the view, up to the start of
04591   // the thread. If we find an ancestor, then our level is one greater than the level of the ancestor.
04592   while (curMsgHdr)
04593   {
04594     nsMsgKey parentKey;
04595     curMsgHdr->GetThreadParent(&parentKey);
04596     if (parentKey == nsMsgKey_None)
04597       break;
04598 
04599     // scan up to find view index of ancestor, if any
04600     for (nsMsgViewIndex indexToTry = viewIndex; indexToTry && indexToTry-- >= startOfThread;)
04601     {
04602       if (m_keys[indexToTry] == parentKey)
04603         return m_levels[indexToTry] + 1;
04604     }
04605 
04606     // if msgHdr's key is its parentKey, we'll loop forever, so protect
04607     // against that corruption.
04608     if (msgKey == parentKey || NS_FAILED(m_db->GetMsgHdrForKey(parentKey, getter_AddRefs(curMsgHdr))))
04609     {
04610       NS_ERROR("msgKey == parentKey, or GetMsgHdrForKey failed, this used to be an infinte loop condition");
04611       curMsgHdr = nsnull;
04612     }
04613     else
04614     {
04615       // need to update msgKey so the check for a msgHdr with matching 
04616       // key+parentKey will work after first time through loop
04617       curMsgHdr->GetMessageKey(&msgKey);
04618     }
04619   }
04620   return 1;
04621 }
04622 
04623 nsresult nsMsgDBView::ListUnreadIdsInThread(nsIMsgThread *threadHdr, nsMsgViewIndex startOfThreadViewIndex, PRUint32 *pNumListed)
04624 {
04625   NS_ENSURE_ARG(threadHdr);
04626   // these children ids should be in thread order.
04627   nsMsgViewIndex viewIndex = startOfThreadViewIndex + 1;
04628   *pNumListed = 0;
04629   nsMsgKey topLevelMsgKey = m_keys[startOfThreadViewIndex];
04630 
04631   PRUint32 numChildren;
04632   threadHdr->GetNumChildren(&numChildren);
04633   PRUint32 i;
04634   for (i = 0; i < numChildren; i++)
04635   {
04636     nsCOMPtr <nsIMsgDBHdr> msgHdr;
04637     threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr));
04638     if (msgHdr != nsnull)
04639     {
04640       nsMsgKey msgKey;
04641       PRUint32 msgFlags;
04642       msgHdr->GetMessageKey(&msgKey);
04643       msgHdr->GetFlags(&msgFlags);
04644       PRBool isRead = AdjustReadFlag(msgHdr, &msgFlags);
04645       // determining the level is going to be tricky, since we're not storing the
04646       // level in the db anymore. It will mean looking up the view for each of the
04647       // ancestors of the current msg until we find one in the view. I guess we could
04648       // check each ancestor to see if it's unread before looking for it in the view.
04649       if (!isRead)
04650       {
04651         PRUint8 levelToAdd;
04652         // just make sure flag is right in db.
04653         m_db->MarkHdrRead(msgHdr, PR_FALSE, nsnull);
04654         if (msgKey != topLevelMsgKey)
04655         {
04656           m_keys.InsertAt(viewIndex, msgKey);
04657           m_flags.InsertAt(viewIndex, msgFlags);
04658           levelToAdd = FindLevelInThread(msgHdr, startOfThreadViewIndex, viewIndex);
04659           m_levels.InsertAt(viewIndex, levelToAdd);
04660           viewIndex++;
04661           (*pNumListed)++;
04662         }
04663       }
04664     }
04665   }
04666   return NS_OK;
04667 }
04668 
04669 NS_IMETHODIMP nsMsgDBView::OnHdrChange(nsIMsgDBHdr *aHdrChanged, PRUint32 aOldFlags, 
04670                                        PRUint32 aNewFlags, nsIDBChangeListener *aInstigator)
04671 {
04672   // if we're not the instigator, update flags if this key is in our view
04673   if (aInstigator != this)
04674   {
04675     nsMsgKey msgKey;
04676     aHdrChanged->GetMessageKey(&msgKey);
04677     nsMsgViewIndex index = FindViewIndex(msgKey);
04678     if (index != nsMsgViewIndex_None)
04679     {
04680       PRUint32 viewOnlyFlags = m_flags[index] & (MSG_VIEW_FLAGS | MSG_FLAG_ELIDED);
04681 
04682       // ### what about saving the old view only flags, like IsThread and HasChildren?
04683       // I think we'll want to save those away.
04684       m_flags[index] = aNewFlags | viewOnlyFlags;
04685       // tell the view the extra flag changed, so it can
04686       // update the previous view, if any.
04687       OnExtraFlagChanged(index, aNewFlags);
04688       NoteChange(index, 1, nsMsgViewNotificationCode::changed);
04689     }
04690 
04691     PRUint32 deltaFlags = (aOldFlags ^ aNewFlags);
04692     if (deltaFlags & (MSG_FLAG_READ | MSG_FLAG_NEW))
04693     {
04694       nsMsgViewIndex threadIndex = ThreadIndexOfMsg(msgKey);
04695       // may need to fix thread counts
04696       if (threadIndex != nsMsgViewIndex_None && threadIndex != index)
04697         NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
04698     }
04699  }
04700   // don't need to propagate notifications, right?
04701   return NS_OK;
04702 }
04703 
04704 NS_IMETHODIMP nsMsgDBView::OnHdrDeleted(nsIMsgDBHdr *aHdrChanged, nsMsgKey aParentKey, PRInt32 aFlags, 
04705                             nsIDBChangeListener *aInstigator)
04706 {
04707   nsMsgViewIndex deletedIndex = FindHdr(aHdrChanged);
04708   if (deletedIndex != nsMsgViewIndex_None)
04709     RemoveByIndex(deletedIndex);
04710 
04711   return NS_OK;
04712 }
04713 
04714 NS_IMETHODIMP nsMsgDBView::OnHdrAdded(nsIMsgDBHdr *aHdrChanged, nsMsgKey aParentKey, PRInt32 aFlags, 
04715                           nsIDBChangeListener *aInstigator)
04716 {
04717   return OnNewHeader(aHdrChanged, aParentKey, PR_FALSE); 
04718   // probably also want to pass that parent key in, since we went to the trouble
04719   // of figuring out what it is.
04720 }
04721                           
04722 NS_IMETHODIMP nsMsgDBView::OnParentChanged (nsMsgKey aKeyChanged, nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeListener *aInstigator)
04723 {
04724   return NS_OK;
04725 }
04726 
04727 NS_IMETHODIMP nsMsgDBView::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator)
04728 {
04729   if (m_db)
04730   {
04731     m_db->RemoveListener(this);
04732     m_db = nsnull;
04733   }
04734 
04735   PRInt32 saveSize = GetSize();
04736   ClearHdrCache();
04737 
04738   // this is important, because the tree will ask us for our
04739   // row count, which get determine from the number of keys.
04740   m_keys.RemoveAll();
04741   // be consistent
04742   m_flags.RemoveAll();
04743   m_levels.RemoveAll();
04744 
04745   // tell the tree all the rows have gone away
04746   if (mTree) 
04747     mTree->RowCountChanged(0, -saveSize);
04748 
04749   return NS_OK;
04750 }
04751 
04752     
04753 NS_IMETHODIMP nsMsgDBView::OnReadChanged(nsIDBChangeListener *aInstigator)
04754 {
04755   return NS_OK;
04756 }
04757 
04758 NS_IMETHODIMP nsMsgDBView::OnJunkScoreChanged(nsIDBChangeListener *aInstigator)
04759 {
04760   return NS_OK;
04761 }
04762 
04763 void nsMsgDBView::ClearHdrCache()
04764 {
04765   m_cachedHdr = nsnull;
04766   m_cachedMsgKey = nsMsgKey_None;
04767 }
04768 
04769 void nsMsgDBView::EnableChangeUpdates()
04770 {
04771   mSuppressChangeNotification = PR_FALSE;
04772 }
04773 
04774 void nsMsgDBView::DisableChangeUpdates()
04775 {
04776   mSuppressChangeNotification = PR_TRUE;
04777 }
04778 
04779 void nsMsgDBView::NoteChange(nsMsgViewIndex firstLineChanged, PRInt32 numChanged, 
04780                              nsMsgViewNotificationCodeValue changeType)
04781 {
04782   if (mTree && !mSuppressChangeNotification)
04783   {
04784     switch (changeType)
04785     {
04786     case nsMsgViewNotificationCode::changed:
04787       mTree->InvalidateRange(firstLineChanged, firstLineChanged + numChanged - 1);
04788       break;
04789     case nsMsgViewNotificationCode::insertOrDelete:
04790       if (numChanged < 0)
04791         mRemovingRow = PR_TRUE;
04792       // the caller needs to have adjusted m_keys before getting here, since
04793       // RowCountChanged() will call our GetRowCount()
04794       mTree->RowCountChanged(firstLineChanged, numChanged);
04795       mRemovingRow = PR_FALSE;
04796     case nsMsgViewNotificationCode::all:
04797       ClearHdrCache();
04798       break;
04799     }
04800   }
04801 }
04802 
04803 void nsMsgDBView::NoteStartChange(nsMsgViewIndex firstlineChanged, PRInt32 numChanged, 
04804                                   nsMsgViewNotificationCodeValue changeType)
04805 {
04806 }
04807 void nsMsgDBView::NoteEndChange(nsMsgViewIndex firstlineChanged, PRInt32 numChanged, 
04808                                 nsMsgViewNotificationCodeValue changeType)
04809 {
04810   // send the notification now.
04811   NoteChange(firstlineChanged, numChanged, changeType);
04812 }
04813 
04814 NS_IMETHODIMP nsMsgDBView::GetSortOrder(nsMsgViewSortOrderValue *aSortOrder)
04815 {
04816     NS_ENSURE_ARG_POINTER(aSortOrder);
04817     *aSortOrder = m_sortOrder;
04818     return NS_OK;
04819 }
04820 
04821 NS_IMETHODIMP nsMsgDBView::GetSortType(nsMsgViewSortTypeValue *aSortType)
04822 {
04823     NS_ENSURE_ARG_POINTER(aSortType);
04824     *aSortType = m_sortType;
04825     return NS_OK;
04826 }
04827 
04828 NS_IMETHODIMP nsMsgDBView::SetSortType(nsMsgViewSortTypeValue aSortType)
04829 {
04830     m_sortType = aSortType;
04831     return NS_OK;
04832 }
04833 
04834 
04835 NS_IMETHODIMP nsMsgDBView::GetViewType(nsMsgViewTypeValue *aViewType)
04836 {
04837     NS_ASSERTION(0,"you should be overriding this\n");
04838     return NS_ERROR_UNEXPECTED;
04839 }
04840 
04841 nsresult nsMsgDBView::PersistFolderInfo(nsIDBFolderInfo **dbFolderInfo)
04842 {
04843   nsresult rv = m_db->GetDBFolderInfo(dbFolderInfo);
04844   NS_ENSURE_SUCCESS(rv, rv);
04845   // save off sort type and order, view type and flags
04846   (*dbFolderInfo)->SetSortType(m_sortType);
04847   (*dbFolderInfo)->SetSortOrder(m_sortOrder);
04848   (*dbFolderInfo)->SetViewFlags(m_viewFlags);
04849   nsMsgViewTypeValue viewType;
04850   GetViewType(&viewType);
04851   (*dbFolderInfo)->SetViewType(viewType);
04852   return rv;
04853 }
04854 
04855 
04856 NS_IMETHODIMP nsMsgDBView::GetViewFlags(nsMsgViewFlagsTypeValue *aViewFlags)
04857 {
04858     NS_ENSURE_ARG_POINTER(aViewFlags);
04859     *aViewFlags = m_viewFlags;
04860     return NS_OK;
04861 }
04862 
04863 NS_IMETHODIMP nsMsgDBView::SetViewFlags(nsMsgViewFlagsTypeValue aViewFlags)
04864 {
04865   // if we're turning off threaded display, we need to expand all so that all
04866   // messages will be displayed.
04867   if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay && ! (aViewFlags & nsMsgViewFlagsType::kThreadedDisplay))
04868   {
04869     ExpandAll();
04870     m_sortValid = PR_FALSE; // invalidate the sort so sorting will do something
04871   }
04872   m_viewFlags = aViewFlags;
04873   
04874   if (m_viewFolder)
04875   {
04876     nsCOMPtr <nsIMsgDatabase> db;
04877     nsCOMPtr <nsIDBFolderInfo> folderInfo;
04878     nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
04879     NS_ENSURE_SUCCESS(rv,rv);
04880     return folderInfo->SetViewFlags(aViewFlags);
04881   }
04882   else
04883     return NS_OK;
04884 }
04885 
04886 nsresult nsMsgDBView::MarkThreadOfMsgRead(nsMsgKey msgId, nsMsgViewIndex msgIndex, nsMsgKeyArray &idsMarkedRead, PRBool bRead)
04887 {
04888     nsCOMPtr <nsIMsgThread> threadHdr;
04889     nsresult rv = GetThreadContainingIndex(msgIndex, getter_AddRefs(threadHdr));
04890     NS_ENSURE_SUCCESS(rv, rv);
04891 
04892     nsMsgViewIndex threadIndex;
04893 
04894     NS_ASSERTION(threadHdr, "threadHdr is null");
04895     if (!threadHdr) 
04896         return NS_MSG_MESSAGE_NOT_FOUND;
04897 
04898     nsCOMPtr <nsIMsgDBHdr> firstHdr;
04899     threadHdr->GetChildAt(0, getter_AddRefs(firstHdr));
04900     nsMsgKey firstHdrId;
04901     firstHdr->GetMessageKey(&firstHdrId);
04902     if (msgId != firstHdrId)
04903         threadIndex = GetIndexOfFirstDisplayedKeyInThread(threadHdr);
04904     else
04905         threadIndex = msgIndex;
04906     return MarkThreadRead(threadHdr, threadIndex, idsMarkedRead, bRead);
04907 }
04908 
04909 nsresult nsMsgDBView::MarkThreadRead(nsIMsgThread *threadHdr, nsMsgViewIndex threadIndex, nsMsgKeyArray &idsMarkedRead, PRBool bRead)
04910 {
04911     PRBool threadElided = PR_TRUE;
04912     if (threadIndex != nsMsgViewIndex_None)
04913         threadElided = (m_flags.GetAt(threadIndex) & MSG_FLAG_ELIDED);
04914 
04915     PRUint32 numChildren;
04916     threadHdr->GetNumChildren(&numChildren);
04917     for (PRInt32 childIndex = 0; childIndex < (PRInt32) numChildren ; childIndex++)
04918     {
04919         nsCOMPtr <nsIMsgDBHdr> msgHdr;
04920         threadHdr->GetChildHdrAt(childIndex, getter_AddRefs(msgHdr));
04921         NS_ASSERTION(msgHdr, "msgHdr is null");
04922         if (!msgHdr) 
04923             continue;
04924 
04925         PRBool isRead;
04926 
04927         nsMsgKey hdrMsgId;
04928         msgHdr->GetMessageKey(&hdrMsgId);
04929         m_db->IsRead(hdrMsgId, &isRead);
04930 
04931         if (isRead != bRead) 
04932         {
04933             // MarkHdrRead will change the unread count on the thread
04934             m_db->MarkHdrRead(msgHdr, bRead, nsnull);
04935             // insert at the front.  should we insert at the end?
04936             idsMarkedRead.InsertAt(0, hdrMsgId);
04937         }
04938     }
04939 
04940     return NS_OK;
04941 }
04942 
04943 PRBool nsMsgDBView::AdjustReadFlag(nsIMsgDBHdr *msgHdr, PRUint32 *msgFlags)
04944 {
04945   PRBool isRead = PR_FALSE;
04946   nsMsgKey msgKey;
04947   msgHdr->GetMessageKey(&msgKey);
04948   m_db->IsRead(msgKey, &isRead);
04949     // just make sure flag is right in db.
04950 #ifdef DEBUG_bienvenu
04951   NS_ASSERTION(isRead == (*msgFlags & MSG_FLAG_READ != 0), "msgFlags out of sync");
04952 #endif
04953   if (isRead)
04954     *msgFlags |= MSG_FLAG_READ;
04955   else
04956     *msgFlags &= ~MSG_FLAG_READ;
04957   m_db->MarkHdrRead(msgHdr, isRead, nsnull);
04958   return isRead;
04959 }
04960 
04961 // Starting from startIndex, performs the passed in navigation, including
04962 // any marking read needed, and returns the resultId and resultIndex of the
04963 // destination of the navigation.  If no message is found in the view,
04964 // it returns a resultId of nsMsgKey_None and an resultIndex of nsMsgViewIndex_None.
04965 NS_IMETHODIMP nsMsgDBView::ViewNavigate(nsMsgNavigationTypeValue motion, nsMsgKey *pResultKey, nsMsgViewIndex *pResultIndex, nsMsgViewIndex *pThreadIndex, PRBool wrap)
04966 {
04967     NS_ENSURE_ARG_POINTER(pResultKey);
04968     NS_ENSURE_ARG_POINTER(pResultIndex);
04969     NS_ENSURE_ARG_POINTER(pThreadIndex);
04970 
04971     PRInt32 currentIndex; 
04972     nsMsgViewIndex startIndex;
04973 
04974     if (!mTreeSelection) // we must be in stand alone message mode
04975     {
04976       currentIndex = FindViewIndex(m_currentlyDisplayedMsgKey);
04977     }
04978     else
04979     {         
04980       nsresult rv = mTreeSelection->GetCurrentIndex(&currentIndex);
04981       NS_ENSURE_SUCCESS(rv, rv);
04982     }
04983     startIndex = currentIndex;
04984     return nsMsgDBView::NavigateFromPos(motion, startIndex, pResultKey, pResultIndex, pThreadIndex, wrap);
04985 }
04986 
04987 nsresult nsMsgDBView::NavigateFromPos(nsMsgNavigationTypeValue motion, nsMsgViewIndex startIndex, nsMsgKey *pResultKey, nsMsgViewIndex *pResultIndex, nsMsgViewIndex *pThreadIndex, PRBool wrap)
04988 {
04989     nsresult rv = NS_OK;
04990     nsMsgKey resultThreadKey;
04991     nsMsgViewIndex curIndex;
04992     nsMsgViewIndex lastIndex = (GetSize() > 0) ? (nsMsgViewIndex) GetSize() - 1 : nsMsgViewIndex_None;
04993     nsMsgViewIndex threadIndex = nsMsgViewIndex_None;
04994 
04995     // if there aren't any messages in the view, bail out.
04996     if (GetSize() <= 0) 
04997     {
04998       *pResultIndex = nsMsgViewIndex_None;
04999       *pResultKey = nsMsgKey_None;
05000       return NS_OK;
05001     }
05002 
05003     switch (motion) 
05004     {
05005         case nsMsgNavigationType::firstMessage:
05006             *pResultIndex = 0;
05007             *pResultKey = m_keys.GetAt(0);
05008             break;
05009         case nsMsgNavigationType::nextMessage:
05010             // return same index and id on next on last message
05011             *pResultIndex = PR_MIN(startIndex + 1, lastIndex);
05012             *pResultKey = m_keys.GetAt(*pResultIndex);
05013             break;
05014         case nsMsgNavigationType::previousMessage:
05015             *pResultIndex = (startIndex != nsMsgViewIndex_None && startIndex > 0) ? startIndex - 1 : 0;
05016             *pResultKey = m_keys.GetAt(*pResultIndex);
05017             break;
05018         case nsMsgNavigationType::lastMessage:
05019             *pResultIndex = lastIndex;
05020             *pResultKey = m_keys.GetAt(*pResultIndex);
05021             break;
05022         case nsMsgNavigationType::firstFlagged:
05023             rv = FindFirstFlagged(pResultIndex);
05024             if (IsValidIndex(*pResultIndex))
05025                 *pResultKey = m_keys.GetAt(*pResultIndex);
05026             break;
05027         case nsMsgNavigationType::nextFlagged:
05028             rv = FindNextFlagged(startIndex + 1, pResultIndex);
05029             if (IsValidIndex(*pResultIndex))
05030                 *pResultKey = m_keys.GetAt(*pResultIndex);
05031             break;
05032         case nsMsgNavigationType::previousFlagged:
05033             rv = FindPrevFlagged(startIndex, pResultIndex);
05034             if (IsValidIndex(*pResultIndex))
05035                 *pResultKey = m_keys.GetAt(*pResultIndex);
05036             break;
05037         case nsMsgNavigationType::firstNew:
05038             rv = FindFirstNew(pResultIndex);
05039             if (IsValidIndex(*pResultIndex))
05040                 *pResultKey = m_keys.GetAt(*pResultIndex);
05041             break;
05042         case nsMsgNavigationType::firstUnreadMessage:
05043             startIndex = nsMsgViewIndex_None;        // note fall thru - is this motion ever used?
05044         case nsMsgNavigationType::nextUnreadMessage:
05045             for (curIndex = (startIndex == nsMsgViewIndex_None) ? 0 : startIndex; curIndex <= lastIndex && lastIndex != nsMsgViewIndex_None; curIndex++) {
05046                 PRUint32 flags = m_flags.GetAt(curIndex);
05047 
05048                 // don't return start index since navigate should move
05049                 if (!(flags & (MSG_FLAG_READ | MSG_VIEW_FLAG_DUMMY)) && (curIndex != startIndex)) 
05050                 {
05051                     *pResultIndex = curIndex;
05052                     *pResultKey = m_keys.GetAt(*pResultIndex);
05053                     break;
05054                 }
05055                 // check for collapsed thread with new children
05056                 if ((m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) && flags & MSG_VIEW_FLAG_ISTHREAD && flags & MSG_FLAG_ELIDED) {
05057                     nsCOMPtr <nsIMsgThread> threadHdr;
05058                     GetThreadContainingIndex(curIndex, getter_AddRefs(threadHdr));
05059                     NS_ENSURE_SUCCESS(rv, rv);
05060 
05061                     NS_ASSERTION(threadHdr, "threadHdr is null");
05062                     if (!threadHdr)
05063                         continue;
05064                     PRUint32 numUnreadChildren;
05065                     threadHdr->GetNumUnreadChildren(&numUnreadChildren);
05066                     if (numUnreadChildren > 0) 
05067                     {
05068                         PRUint32 numExpanded;
05069                         ExpandByIndex(curIndex, &numExpanded);
05070                         lastIndex += numExpanded;
05071                         if (pThreadIndex)
05072                             *pThreadIndex = curIndex;
05073                     }
05074                 }
05075             }
05076             if (curIndex > lastIndex) 
05077             {
05078                 // wrap around by starting at index 0.
05079                 if (wrap) 
05080                 {
05081                     nsMsgKey startKey = GetAt(startIndex);
05082 
05083                     rv = NavigateFromPos(nsMsgNavigationType::nextUnreadMessage, nsMsgViewIndex_None, pResultKey, pResultIndex, pThreadIndex, PR_FALSE);
05084 
05085                     if (*pResultKey == startKey) 
05086                     {   
05087                         // wrapped around and found start message!
05088                         *pResultIndex = nsMsgViewIndex_None;
05089                         *pResultKey = nsMsgKey_None;
05090                     }
05091                 }
05092                 else
05093                 {
05094                     *pResultIndex = nsMsgViewIndex_None;
05095                     *pResultKey = nsMsgKey_None;
05096                 }
05097             }
05098             break;
05099         case nsMsgNavigationType::previousUnreadMessage:
05100             if (startIndex == nsMsgViewIndex_None) 
05101               break;
05102             rv = FindPrevUnread(m_keys.GetAt(startIndex), pResultKey,
05103                                 &resultThreadKey);
05104             if (NS_SUCCEEDED(rv)) 
05105             {
05106                 *pResultIndex = FindViewIndex(*pResultKey);
05107                 if (*pResultKey != resultThreadKey && (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) 
05108                 {
05109                     threadIndex  = ThreadIndexOfMsg(*pResultKey, nsMsgViewIndex_None);
05110                     if (*pResultIndex == nsMsgViewIndex_None) 
05111                     {
05112                         nsCOMPtr <nsIMsgThread> threadHdr;
05113                         nsCOMPtr <nsIMsgDBHdr> msgHdr;
05114                         rv = m_db->GetMsgHdrForKey(*pResultKey, getter_AddRefs(msgHdr));
05115                         NS_ENSURE_SUCCESS(rv, rv);
05116                         rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(threadHdr));
05117                         NS_ENSURE_SUCCESS(rv, rv);
05118 
05119                         NS_ASSERTION(threadHdr, "threadHdr is null");
05120                         if (threadHdr) 
05121                             break;
05122                         PRUint32 numUnreadChildren;
05123                         threadHdr->GetNumUnreadChildren(&numUnreadChildren);
05124                         if (numUnreadChildren > 0) 
05125                         {
05126                             PRUint32 numExpanded;
05127                             ExpandByIndex(threadIndex, &numExpanded);
05128                         }
05129                         *pResultIndex = FindViewIndex(*pResultKey);
05130                     }
05131                 }
05132                 if (pThreadIndex)
05133                     *pThreadIndex = threadIndex;
05134             }
05135             break;
05136         case nsMsgNavigationType::lastUnreadMessage:
05137             break;
05138         case nsMsgNavigationType::nextUnreadThread:
05139             if (startIndex != nsMsgViewIndex_None)
05140               ApplyCommandToIndices(nsMsgViewCommandType::markThreadRead, &startIndex, 1);
05141 
05142             return NavigateFromPos(nsMsgNavigationType::nextUnreadMessage, startIndex, pResultKey, pResultIndex, pThreadIndex, PR_TRUE);
05143         case nsMsgNavigationType::toggleThreadKilled:
05144             {
05145                 PRBool resultKilled;
05146                 nsUInt32Array selection;
05147                 GetSelectedIndices(&selection);
05148                 ToggleIgnored(selection.GetData(), selection.GetSize(), &threadIndex, &resultKilled);
05149                 if (resultKilled) 
05150                 {
05151                     return NavigateFromPos(nsMsgNavigationType::nextUnreadThread, threadIndex, pResultKey, pResultIndex, pThreadIndex, PR_TRUE);
05152                 }
05153                 else 
05154                 {
05155                     *pResultIndex = nsMsgViewIndex_None;
05156                     *pResultKey = nsMsgKey_None;
05157                     return NS_OK;
05158                 }
05159             }
05160           // check where navigate says this will take us. If we have the message in the view,
05161           // return it. Otherwise, return an error.
05162       case nsMsgNavigationType::back:
05163       case nsMsgNavigationType::forward:
05164         {
05165           nsXPIDLCString folderUri, msgUri;
05166           nsXPIDLCString viewFolderUri;
05167           nsCOMPtr<nsIMsgFolder> curFolder = m_viewFolder ? m_viewFolder : m_folder;
05168           curFolder->GetURI(getter_Copies(viewFolderUri));
05169           PRInt32 relPos = (motion == nsMsgNavigationType::forward) 
05170             ? 1 : (m_currentlyDisplayedMsgKey != nsMsgKey_None) ? -1 : 0;
05171           PRInt32 curPos;
05172           nsresult rv = mMessengerInstance->GetFolderUriAtNavigatePos(relPos, getter_Copies(folderUri));
05173           NS_ENSURE_SUCCESS(rv, rv);
05174           if (folderUri.Equals(viewFolderUri))
05175           {
05176             nsCOMPtr <nsIMsgDBHdr> msgHdr;
05177             nsresult rv = mMessengerInstance->GetMsgUriAtNavigatePos(relPos, getter_Copies(msgUri));
05178             NS_ENSURE_SUCCESS(rv, rv);
05179             mMessengerInstance->MsgHdrFromURI(msgUri.get(), getter_AddRefs(msgHdr));
05180             if (msgHdr)
05181             {
05182               mMessengerInstance->GetNavigatePos(&curPos);
05183               curPos += relPos;
05184               *pResultIndex = FindHdr(msgHdr);
05185               mMessengerInstance->SetNavigatePos(curPos);
05186               msgHdr->GetMessageKey(pResultKey);
05187               return NS_OK;
05188             }
05189           }
05190           *pResultIndex = nsMsgViewIndex_None;
05191           *pResultKey = nsMsgKey_None;
05192           break;
05193           
05194         }
05195         default:
05196             NS_ASSERTION(0, "unsupported motion");
05197             break;
05198     }
05199     return NS_OK;
05200 }
05201 
05202 NS_IMETHODIMP nsMsgDBView::NavigateStatus(nsMsgNavigationTypeValue motion, PRBool *_retval)
05203 {
05204     NS_ENSURE_ARG_POINTER(_retval);
05205 
05206     PRBool enable = PR_FALSE;
05207     nsresult rv = NS_ERROR_FAILURE;
05208     nsMsgKey resultKey = nsMsgKey_None;
05209     PRInt32 index = nsMsgKey_None;
05210     nsMsgViewIndex resultIndex = nsMsgViewIndex_None;
05211     if (mTreeSelection)
05212       (void) mTreeSelection->GetCurrentIndex(&index);
05213     else
05214       index = FindViewIndex(m_currentlyDisplayedMsgKey);
05215 
05216     // warning - we no longer validate index up front because fe passes in -1 for no
05217     // selection, so if you use index, be sure to validate it before using it
05218     // as an array index.
05219     switch (motion) 
05220     {
05221         case nsMsgNavigationType::firstMessage:
05222         case nsMsgNavigationType::lastMessage:
05223             if (GetSize() > 0)
05224                 enable = PR_TRUE;
05225             break;
05226         case nsMsgNavigationType::nextMessage:
05227             if (IsValidIndex(index) && index < GetSize() - 1)
05228                 enable = PR_TRUE;
05229             break;
05230         case nsMsgNavigationType::previousMessage:
05231             if (IsValidIndex(index) && index != 0 && GetSize() > 1)
05232                 enable = PR_TRUE;
05233             break;
05234         case nsMsgNavigationType::firstFlagged:
05235             rv = FindFirstFlagged(&resultIndex);
05236             enable = (NS_SUCCEEDED(rv) && resultIndex != nsMsgViewIndex_None);
05237             break;
05238         case nsMsgNavigationType::nextFlagged:
05239             rv = FindNextFlagged(index + 1, &resultIndex);
05240             enable = (NS_SUCCEEDED(rv) && resultIndex != nsMsgViewIndex_None);
05241             break;
05242         case nsMsgNavigationType::previousFlagged:
05243             if (IsValidIndex(index) && index != 0)
05244                 rv = FindPrevFlagged(index, &resultIndex);
05245             enable = (NS_SUCCEEDED(rv) && resultIndex != nsMsgViewIndex_None);
05246             break;
05247         case nsMsgNavigationType::firstNew:
05248             rv = FindFirstNew(&resultIndex);
05249             enable = (NS_SUCCEEDED(rv) && resultIndex != nsMsgViewIndex_None);
05250             break;
05251         case nsMsgNavigationType::readMore:
05252             enable = PR_TRUE;  // for now, always true.
05253             break;
05254         case nsMsgNavigationType::nextFolder:
05255         case nsMsgNavigationType::nextUnreadThread:
05256         case nsMsgNavigationType::nextUnreadMessage:
05257         case nsMsgNavigationType::toggleThreadKilled:
05258             enable = PR_TRUE;  // always enabled
05259             break;
05260         case nsMsgNavigationType::previousUnreadMessage:
05261             if (IsValidIndex(index)) 
05262             {
05263                 nsMsgKey threadId;
05264                 rv = FindPrevUnread(m_keys.GetAt(index), &resultKey, &threadId);
05265                 enable = (resultKey != nsMsgKey_None);
05266             }
05267             break;
05268         case nsMsgNavigationType::forward:
05269         case nsMsgNavigationType::back:
05270         {
05271           PRUint32 curPos;
05272           PRUint32 historyCount;
05273           mMessengerInstance->GetNavigateHistory(&curPos, &historyCount, nsnull);
05274           PRInt32 desiredPos = (PRInt32) curPos;
05275           if (motion == nsMsgNavigationType::forward)
05276             desiredPos++;
05277           else
05278             desiredPos--; //? operator code didn't work for me
05279           enable = (desiredPos >= 0 && desiredPos < (PRInt32) historyCount / 2);
05280         }
05281           break;
05282           
05283         default:
05284             NS_ASSERTION(0,"unexpected");
05285             break;
05286     }
05287 
05288     *_retval = enable;
05289     return NS_OK;
05290 }
05291 
05292 // Note that these routines do NOT expand collapsed threads! This mimics the old behaviour,
05293 // but it's also because we don't remember whether a thread contains a flagged message the
05294 // same way we remember if a thread contains new messages. It would be painful to dive down
05295 // into each collapsed thread to update navigate status.
05296 // We could cache this info, but it would still be expensive the first time this status needs
05297 // to get updated.
05298 nsresult nsMsgDBView::FindNextFlagged(nsMsgViewIndex startIndex, nsMsgViewIndex *pResultIndex)
05299 {
05300     nsMsgViewIndex lastIndex = (nsMsgViewIndex) GetSize() - 1;
05301     nsMsgViewIndex curIndex;
05302 
05303     *pResultIndex = nsMsgViewIndex_None;
05304 
05305     if (GetSize() > 0) 
05306     {
05307         for (curIndex = startIndex; curIndex <= lastIndex; curIndex++) 
05308         {
05309             PRUint32 flags = m_flags.GetAt(curIndex);
05310             if (flags & MSG_FLAG_MARKED) 
05311             {
05312                 *pResultIndex = curIndex;
05313                 break;
05314             }
05315         }
05316     }
05317 
05318     return NS_OK;
05319 }
05320 
05321 nsresult nsMsgDBView::FindFirstNew(nsMsgViewIndex *pResultIndex)
05322 {
05323   if (m_db) 
05324   {
05325     nsMsgKey firstNewKey = nsMsgKey_None;
05326     m_db->GetFirstNew(&firstNewKey);
05327     *pResultIndex = (firstNewKey != nsMsgKey_None)
05328         ? FindKey(firstNewKey, PR_TRUE) : nsMsgViewIndex_None;
05329   }
05330   return NS_OK;
05331 }
05332 
05333 nsresult nsMsgDBView::FindPrevUnread(nsMsgKey startKey, nsMsgKey *pResultKey,
05334                                      nsMsgKey *resultThreadId)
05335 {
05336     nsMsgViewIndex startIndex = FindViewIndex(startKey);
05337     nsMsgViewIndex curIndex = startIndex;
05338     nsresult rv = NS_MSG_MESSAGE_NOT_FOUND;
05339 
05340     if (startIndex == nsMsgViewIndex_None)
05341         return NS_MSG_MESSAGE_NOT_FOUND;
05342 
05343     *pResultKey = nsMsgKey_None;
05344     if (resultThreadId)
05345         *resultThreadId = nsMsgKey_None;
05346 
05347     for (; (int) curIndex >= 0 && (*pResultKey == nsMsgKey_None); curIndex--) 
05348     {
05349         PRUint32 flags = m_flags.GetAt(curIndex);
05350 
05351         if (curIndex != startIndex && flags & MSG_VIEW_FLAG_ISTHREAD && flags & MSG_FLAG_ELIDED) 
05352         {
05353             NS_ASSERTION(0,"fix this");
05354             //nsMsgKey threadId = m_keys.GetAt(curIndex);
05355             //rv = m_db->GetUnreadKeyInThread(threadId, pResultKey, resultThreadId);
05356             if (NS_SUCCEEDED(rv) && (*pResultKey != nsMsgKey_None))
05357                 break;
05358         }
05359         if (!(flags & (MSG_FLAG_READ | MSG_VIEW_FLAG_DUMMY)) && (curIndex != startIndex)) 
05360         {
05361             *pResultKey = m_keys.GetAt(curIndex);
05362             rv = NS_OK;
05363             break;
05364         }
05365     }
05366     // found unread message but we don't know the thread
05367     NS_ASSERTION(!(*pResultKey != nsMsgKey_None && resultThreadId && *resultThreadId == nsMsgKey_None),
05368       "fix this");
05369     return rv;
05370 }
05371 
05372 nsresult nsMsgDBView::FindFirstFlagged(nsMsgViewIndex *pResultIndex)
05373 {
05374     return FindNextFlagged(0, pResultIndex);
05375 }
05376 
05377 nsresult nsMsgDBView::FindPrevFlagged(nsMsgViewIndex startIndex, nsMsgViewIndex *pResultIndex)
05378 {
05379     nsMsgViewIndex curIndex;
05380 
05381     *pResultIndex = nsMsgViewIndex_None;
05382 
05383     if (GetSize() > 0 && IsValidIndex(startIndex)) 
05384     {
05385         curIndex = startIndex;
05386         do 
05387         {
05388             if (curIndex != 0)
05389                 curIndex--;
05390 
05391             PRUint32 flags = m_flags.GetAt(curIndex);
05392             if (flags & MSG_FLAG_MARKED) 
05393             {
05394                 *pResultIndex = curIndex;
05395                 break;
05396             }
05397         }
05398         while (curIndex != 0);
05399     }
05400     return NS_OK;
05401 }
05402 
05403 PRBool nsMsgDBView::IsValidIndex(nsMsgViewIndex index)
05404 {
05405     return ((index >=0) && (index < (nsMsgViewIndex) m_keys.GetSize()));
05406 }
05407 
05408 nsresult nsMsgDBView::OrExtraFlag(nsMsgViewIndex index, PRUint32 orflag)
05409 {
05410   PRUint32    flag;
05411   if (!IsValidIndex(index))
05412     return NS_MSG_INVALID_DBVIEW_INDEX;
05413   flag = m_flags[index];
05414   flag |= orflag;
05415   m_flags[index] = flag;
05416   OnExtraFlagChanged(index, flag);
05417   return NS_OK;
05418 }
05419 
05420 nsresult nsMsgDBView::AndExtraFlag(nsMsgViewIndex index, PRUint32 andflag)
05421 {
05422   PRUint32    flag;
05423   if (!IsValidIndex(index))
05424     return NS_MSG_INVALID_DBVIEW_INDEX;
05425   flag = m_flags[index];
05426   flag &= andflag;
05427   m_flags[index] = flag;
05428   OnExtraFlagChanged(index, flag);
05429   return NS_OK;
05430 }
05431 
05432 nsresult nsMsgDBView::SetExtraFlag(nsMsgViewIndex index, PRUint32 extraflag)
05433 {
05434   if (!IsValidIndex(index))
05435     return NS_MSG_INVALID_DBVIEW_INDEX;
05436   m_flags[index] = extraflag;
05437   OnExtraFlagChanged(index, extraflag);
05438   return NS_OK;
05439 }
05440 
05441 
05442 nsresult nsMsgDBView::ToggleIgnored(nsMsgViewIndex * indices, PRInt32 numIndices, nsMsgViewIndex *resultIndex, PRBool *resultToggleState)
05443 {
05444   nsCOMPtr <nsIMsgThread> thread;
05445 
05446   // Ignored state is toggled based on the first selected thread
05447   if (numIndices > 1)
05448     NS_QuickSort(indices, numIndices, sizeof(nsMsgViewIndex), CompareViewIndices, nsnull);
05449   nsMsgViewIndex threadIndex = GetThreadFromMsgIndex(indices[0], getter_AddRefs(thread));
05450   PRUint32 threadFlags;
05451   thread->GetFlags(&threadFlags);
05452   PRUint32 ignored = threadFlags & MSG_FLAG_IGNORED;
05453 
05454   // Process threads in reverse order
05455   // Otherwise collapsing the threads will invalidate the indices
05456   threadIndex = nsMsgViewIndex_None;
05457   while (numIndices)
05458   {
05459     numIndices--;
05460     if (indices[numIndices] < threadIndex)
05461     {
05462       threadIndex = GetThreadFromMsgIndex(indices[numIndices], getter_AddRefs(thread));
05463       thread->GetFlags(&threadFlags);
05464       if ((threadFlags & MSG_FLAG_IGNORED) == ignored)
05465         SetThreadIgnored(thread, threadIndex, !ignored);
05466     }
05467   }
05468 
05469   if (resultIndex)
05470     *resultIndex = threadIndex;
05471   if (resultToggleState)
05472     *resultToggleState = !ignored;
05473 
05474   return NS_OK;
05475 }
05476 
05477 nsMsgViewIndex       nsMsgDBView::GetThreadFromMsgIndex(nsMsgViewIndex index, 
05478                                                    nsIMsgThread **threadHdr)
05479 {
05480   nsMsgKey        msgKey = GetAt(index);
05481   nsMsgViewIndex  threadIndex;
05482   
05483   NS_ENSURE_ARG(threadHdr);
05484   nsresult rv = GetThreadContainingIndex(index, threadHdr);
05485   NS_ENSURE_SUCCESS(rv,nsMsgViewIndex_None);
05486   
05487   if (*threadHdr == nsnull)
05488     return nsMsgViewIndex_None;
05489   
05490   nsMsgKey threadKey;
05491   (*threadHdr)->GetThreadKey(&threadKey);
05492   if (msgKey !=threadKey)
05493     threadIndex = GetIndexOfFirstDisplayedKeyInThread(*threadHdr);
05494   else
05495     threadIndex = index;
05496   return threadIndex;
05497 }
05498 
05499 nsresult nsMsgDBView::ToggleWatched( nsMsgViewIndex* indices,  PRInt32 numIndices)
05500 {
05501   nsCOMPtr <nsIMsgThread> thread;
05502 
05503   // Watched state is toggled based on the first selected thread
05504   if (numIndices > 1)
05505     NS_QuickSort(indices, numIndices, sizeof(nsMsgViewIndex), CompareViewIndices, nsnull);
05506   nsMsgViewIndex threadIndex = GetThreadFromMsgIndex(indices[0], getter_AddRefs(thread));
05507   PRUint32 threadFlags;
05508   thread->GetFlags(&threadFlags);
05509   PRUint32 watched = threadFlags & MSG_FLAG_WATCHED;
05510 
05511   // Process threads in reverse order
05512   // for consistency with ToggleIgnored
05513   threadIndex = nsMsgViewIndex_None;
05514   while (numIndices)
05515   {
05516     numIndices--;
05517     if (indices[numIndices] < threadIndex)
05518     {
05519       threadIndex = GetThreadFromMsgIndex(indices[numIndices], getter_AddRefs(thread));
05520       thread->GetFlags(&threadFlags);
05521       if ((threadFlags & MSG_FLAG_WATCHED) == watched)
05522         SetThreadWatched(thread, threadIndex, !watched);
05523     }
05524   }
05525 
05526   return NS_OK;
05527 }
05528 
05529 nsresult nsMsgDBView::SetThreadIgnored(nsIMsgThread *thread, nsMsgViewIndex threadIndex, PRBool ignored)
05530 {
05531   if (!IsValidIndex(threadIndex))
05532     return NS_MSG_INVALID_DBVIEW_INDEX;
05533 
05534   NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
05535   if (ignored)
05536   {
05537     nsMsgKeyArray    idsMarkedRead;
05538     
05539     MarkThreadRead(thread, threadIndex, idsMarkedRead, PR_TRUE);
05540     CollapseByIndex(threadIndex, nsnull);
05541   }
05542   return m_db->MarkThreadIgnored(thread, m_keys[threadIndex], ignored, this);
05543 }
05544 
05545 nsresult nsMsgDBView::SetThreadWatched(nsIMsgThread *thread, nsMsgViewIndex index, PRBool watched)
05546 {
05547   if (!IsValidIndex(index))
05548     return NS_MSG_INVALID_DBVIEW_INDEX;
05549 
05550   NoteChange(index, 1, nsMsgViewNotificationCode::changed);
05551   return m_db->MarkThreadWatched(thread, m_keys[index], watched, this);
05552 }
05553 
05554 NS_IMETHODIMP nsMsgDBView::GetMsgFolder(nsIMsgFolder **aMsgFolder)
05555 {
05556   NS_ENSURE_ARG_POINTER(aMsgFolder);
05557   NS_IF_ADDREF(*aMsgFolder = m_folder);
05558   return NS_OK;
05559 }
05560 
05561 NS_IMETHODIMP nsMsgDBView::SetViewFolder(nsIMsgFolder *aMsgFolder)
05562 {
05563   m_viewFolder = aMsgFolder;
05564   return NS_OK;
05565 }
05566 
05567 NS_IMETHODIMP nsMsgDBView::GetViewFolder(nsIMsgFolder **aMsgFolder)
05568 {
05569   NS_ENSURE_ARG_POINTER(aMsgFolder);
05570   NS_IF_ADDREF(*aMsgFolder = m_viewFolder);
05571   return NS_OK;
05572 }
05573 
05574 
05575 NS_IMETHODIMP 
05576 nsMsgDBView::GetNumSelected(PRUint32 *numSelected)
05577 { 
05578   NS_ENSURE_ARG_POINTER(numSelected);
05579     
05580   if (!mTreeSelection) 
05581   {
05582     *numSelected = 0;
05583     return NS_OK;
05584   }
05585   
05586   // We call this a lot from the front end JS, so make it fast.
05587   return mTreeSelection->GetCount((PRInt32*)numSelected);
05588 }
05589 
05590 NS_IMETHODIMP 
05591 nsMsgDBView::GetMsgToSelectAfterDelete(nsMsgViewIndex *msgToSelectAfterDelete)
05592 {
05593   NS_ENSURE_ARG_POINTER(msgToSelectAfterDelete);
05594   *msgToSelectAfterDelete = nsMsgViewIndex_None;
05595   if (!mTreeSelection) 
05596   {
05597     // if we don't have an tree selection then we must be in stand alone mode.
05598     // return the index of the current message key as the first selected index.
05599     *msgToSelectAfterDelete = FindViewIndex(m_currentlyDisplayedMsgKey);
05600     return NS_OK;
05601   }
05602    
05603   PRInt32 selectionCount;
05604   PRInt32 startRange;
05605   PRInt32 endRange;
05606   nsresult rv = mTreeSelection->GetRangeCount(&selectionCount);
05607   for (PRInt32 i = 0; i < selectionCount; i++) 
05608   {
05609     rv = mTreeSelection->GetRangeAt(i, &startRange, &endRange);
05610     *msgToSelectAfterDelete = PR_MIN(*msgToSelectAfterDelete, startRange);
05611   }
05612   nsCOMPtr<nsIMsgFolder> folder;
05613   GetMsgFolder(getter_AddRefs(folder));
05614   nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder);
05615   PRBool thisIsImapFolder = (imapFolder != nsnull);
05616   if (thisIsImapFolder) //need to update the imap-delete model, can change more than once in a session.
05617     GetImapDeleteModel(nsnull);
05618 
05619   // If mail.delete_matches_sort_order is true, 
05620   // for views sorted in descending order (newest at the top), make msgToSelectAfterDelete
05621   // advance in the same direction as the sort order.
05622   PRBool deleteMatchesSort = PR_FALSE;
05623   if (m_sortOrder == nsMsgViewSortOrder::descending && *msgToSelectAfterDelete)
05624   {
05625     nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
05626     NS_ENSURE_SUCCESS(rv, rv);
05627     prefBranch->GetBoolPref("mail.delete_matches_sort_order", &deleteMatchesSort);
05628   }
05629 
05630   if (mDeleteModel == nsMsgImapDeleteModels::IMAPDelete)
05631   {
05632     if (selectionCount > 1 || (endRange-startRange) > 0)  //multiple selection either using Ctrl or Shift keys
05633       *msgToSelectAfterDelete = nsMsgViewIndex_None;
05634     else if(deleteMatchesSort)
05635       *msgToSelectAfterDelete -= 1;
05636     else
05637       *msgToSelectAfterDelete += 1;
05638   }
05639   else if (deleteMatchesSort)
05640   {
05641     *msgToSelectAfterDelete -= 1;
05642   }
05643 
05644   return NS_OK;
05645 }
05646 
05647 NS_IMETHODIMP 
05648 nsMsgDBView::GetRemoveRowOnMoveOrDelete(PRBool *aRemoveRowOnMoveOrDelete)
05649 {
05650   NS_ENSURE_ARG_POINTER(aRemoveRowOnMoveOrDelete);
05651   nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_folder);
05652   if (!imapFolder) 
05653   {
05654     *aRemoveRowOnMoveOrDelete = PR_TRUE;
05655     return NS_OK;
05656   }
05657 
05658   // need to update the imap-delete model, can change more than once in a session.
05659   GetImapDeleteModel(nsnull);
05660 
05661   // unlike the other imap delete models, "mark as deleted" does not remove rows on delete (or move)
05662   *aRemoveRowOnMoveOrDelete = (mDeleteModel != nsMsgImapDeleteModels::IMAPDelete);
05663   return NS_OK;
05664 }
05665 
05666 
05667 NS_IMETHODIMP 
05668 nsMsgDBView::GetCurrentlyDisplayedMessage(nsMsgViewIndex *currentlyDisplayedMessage)
05669 {
05670   NS_ENSURE_ARG_POINTER(currentlyDisplayedMessage);
05671   *currentlyDisplayedMessage = FindViewIndex(m_currentlyDisplayedMsgKey);
05672   return NS_OK;
05673 }
05674 
05675 // if nothing selected, return an NS_ERROR
05676 NS_IMETHODIMP
05677 nsMsgDBView::GetHdrForFirstSelectedMessage(nsIMsgDBHdr **hdr)
05678 {
05679   NS_ENSURE_ARG_POINTER(hdr);
05680 
05681   nsresult rv;
05682   nsMsgKey key;
05683   rv = GetKeyForFirstSelectedMessage(&key);
05684   // don't assert, it is legal for nothing to be selected
05685   if (NS_FAILED(rv)) return rv;
05686 
05687   if (!m_db)
05688     return NS_MSG_MESSAGE_NOT_FOUND;
05689 
05690   rv = m_db->GetMsgHdrForKey(key, hdr);
05691   NS_ENSURE_SUCCESS(rv,rv);
05692   return NS_OK;
05693 }
05694 
05695 // if nothing selected, return an NS_ERROR
05696 NS_IMETHODIMP 
05697 nsMsgDBView::GetURIForFirstSelectedMessage(char **uri)
05698 {
05699   NS_ENSURE_ARG_POINTER(uri);
05700 
05701   nsresult rv;
05702   nsMsgViewIndex viewIndex;
05703   rv = GetViewIndexForFirstSelectedMsg(&viewIndex);
05704   // don't assert, it is legal for nothing to be selected
05705   if (NS_FAILED(rv)) return rv;
05706  
05707   return GetURIForViewIndex(viewIndex, uri);
05708 }
05709 
05710 NS_IMETHODIMP
05711 nsMsgDBView::OnDeleteCompleted(PRBool aSucceeded)
05712 {
05713   if (m_deletingRows)
05714   { 
05715     if (aSucceeded)
05716     {
05717       PRUint32 numIndices = mIndicesToNoteChange.GetSize();
05718       if (numIndices) 
05719       {
05720         if (mTree)
05721         {
05722           if (numIndices > 1)
05723             mIndicesToNoteChange.QuickSort(CompareViewIndices);
05724 
05725           // the call to NoteChange() has to happen after we are done removing the keys
05726           // as NoteChange() will call RowCountChanged() which will call our GetRowCount()
05727           if (numIndices > 1)
05728             mTree->BeginUpdateBatch();
05729           for (PRUint32 i=0;i<numIndices;i++)
05730             NoteChange(mIndicesToNoteChange[i], -1, nsMsgViewNotificationCode::insertOrDelete);
05731           if (numIndices > 1)
05732             mTree->EndUpdateBatch(); 
05733         }
05734         mIndicesToNoteChange.RemoveAll();
05735       }
05736     }
05737   }
05738 
05739  m_deletingRows = PR_FALSE;
05740  return NS_OK;
05741 }
05742 
05743 NS_IMETHODIMP nsMsgDBView::GetDb(nsIMsgDatabase **aDB)
05744 {
05745   NS_ENSURE_ARG_POINTER(aDB);
05746   NS_IF_ADDREF(*aDB = m_db);
05747   return NS_OK;
05748 }
05749 
05750 PRBool nsMsgDBView::OfflineMsgSelected(nsMsgViewIndex * indices, PRInt32 numIndices)
05751 {
05752   nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder);
05753   if (localFolder)
05754     return PR_TRUE;
05755 
05756   for (nsMsgViewIndex index = 0; index < (nsMsgViewIndex) numIndices; index++)
05757   {
05758     PRUint32 flags = m_flags.GetAt(indices[index]);
05759     if ((flags & MSG_FLAG_OFFLINE))
05760       return PR_TRUE;
05761   }
05762   return PR_FALSE;
05763 }
05764 
05765 PRBool nsMsgDBView::NonDummyMsgSelected(nsMsgViewIndex * indices, PRInt32 numIndices)
05766 {
05767   for (nsMsgViewIndex index = 0; index < (nsMsgViewIndex) numIndices; index++)
05768   {
05769     PRUint32 flags = m_flags.GetAt(indices[index]);
05770     if (!(flags & MSG_VIEW_FLAG_DUMMY))
05771       return PR_TRUE;
05772   }
05773   return PR_FALSE;
05774 }
05775 
05776 NS_IMETHODIMP nsMsgDBView::GetViewIndexForFirstSelectedMsg(nsMsgViewIndex *aViewIndex)
05777 {
05778   NS_ENSURE_ARG_POINTER(aViewIndex);
05779   // if we don't have an tree selection we must be in stand alone mode....
05780   if (!mTreeSelection) 
05781   {
05782     *aViewIndex = m_currentlyDisplayedViewIndex;
05783     return NS_OK;
05784   }
05785 
05786   PRInt32 startRange;
05787   PRInt32 endRange;
05788   nsresult rv = mTreeSelection->GetRangeAt(0, &startRange, &endRange);
05789   // don't assert, it is legal for nothing to be selected
05790   if (NS_FAILED(rv))
05791     return rv;
05792 
05793   // check that the first index is valid, it may not be if nothing is selected
05794   if (startRange >= 0 && startRange < GetSize()) 
05795     *aViewIndex = startRange;
05796   else 
05797     return NS_ERROR_UNEXPECTED;
05798   return NS_OK;
05799 }
05800 
05801 NS_IMETHODIMP
05802 nsMsgDBView::GetKeyForFirstSelectedMessage(nsMsgKey *key)
05803 {
05804   NS_ENSURE_ARG_POINTER(key);
05805   // if we don't have an tree selection we must be in stand alone mode....
05806   if (!mTreeSelection) 
05807   {
05808     *key = m_currentlyDisplayedMsgKey;
05809     return NS_OK;
05810   }
05811 
05812   PRInt32 startRange;
05813   PRInt32 endRange;
05814   nsresult rv = mTreeSelection->GetRangeAt(0, &startRange, &endRange);
05815   // don't assert, it is legal for nothing to be selected
05816   if (NS_FAILED(rv))
05817     return rv;
05818 
05819   // check that the first index is valid, it may not be if nothing is selected
05820   if (startRange >= 0 && startRange < GetSize()) 
05821   {
05822     if (m_flags[startRange] & MSG_VIEW_FLAG_DUMMY)
05823       return NS_MSG_INVALID_DBVIEW_INDEX;
05824 
05825     *key = m_keys.GetAt(startRange);
05826   }
05827   else 
05828     return NS_ERROR_UNEXPECTED;
05829   return NS_OK;
05830 }
05831 
05832 nsresult nsMsgDBView::GetFolders(nsISupportsArray **aFolders)
05833 {
05834     NS_ENSURE_ARG_POINTER(aFolders);
05835     *aFolders = nsnull;
05836 
05837     return NS_OK;
05838 }
05839 
05840 nsresult nsMsgDBView::AdjustRowCount(PRInt32 rowCountBeforeSort, PRInt32 rowCountAfterSort)
05841 {
05842   PRInt32 rowChange = rowCountAfterSort - rowCountBeforeSort;
05843 
05844   if (rowChange) 
05845   {
05846     // this is not safe to use when you have a selection
05847     // RowCountChanged() will call AdjustSelection()
05848     PRUint32 numSelected = 0;
05849     GetNumSelected(&numSelected);
05850     NS_ASSERTION(numSelected == 0, "it is not save to call AdjustRowCount() when you have a selection");
05851 
05852     if (mTree)
05853       mTree->RowCountChanged(0, rowChange);
05854   }
05855   return NS_OK;
05856 }
05857 
05858 nsresult nsMsgDBView::GetImapDeleteModel(nsIMsgFolder *folder)
05859 {
05860    nsresult rv = NS_OK;
05861    nsCOMPtr <nsIMsgIncomingServer> server;
05862    if (folder) //for the search view 
05863      folder->GetServer(getter_AddRefs(server));
05864    else if (m_folder)
05865      m_folder->GetServer(getter_AddRefs(server));
05866    nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server, &rv);
05867    if (NS_SUCCEEDED(rv) && imapServer )
05868      imapServer->GetDeleteModel(&mDeleteModel);       
05869    return rv;
05870 }
05871 
05872 
05873 //
05874 // CanDrop
05875 //
05876 // Can't drop on the thread pane.
05877 //
05878 NS_IMETHODIMP nsMsgDBView::CanDrop(PRInt32 index, PRInt32 orient, PRBool *_retval)
05879 {
05880   NS_ENSURE_ARG_POINTER(_retval);
05881   *_retval = PR_FALSE;
05882   
05883   return NS_OK;
05884 }
05885 
05886 
05887 //
05888 // Drop
05889 //
05890 // Can't drop on the thread pane.
05891 //
05892 NS_IMETHODIMP nsMsgDBView::Drop(PRInt32 row, PRInt32 orient)
05893 {
05894   return NS_OK;
05895 }
05896 
05897 
05898 //
05899 // IsSorted
05900 //
05901 // ...
05902 //
05903 NS_IMETHODIMP nsMsgDBView::IsSorted(PRBool *_retval)
05904 {
05905   *_retval = PR_FALSE;
05906   return NS_OK;
05907 }
05908 
05909 NS_IMETHODIMP nsMsgDBView::SelectMsgByKey(nsMsgKey aKey)
05910 {
05911   NS_ASSERTION(aKey != nsMsgKey_None, "bad key");
05912   if (aKey == nsMsgKey_None)
05913     return NS_OK;
05914 
05915   // use SaveAndClearSelection()
05916   // and RestoreSelection() so that we'll clear the current selection
05917   // but pass in a different key array so that we'll
05918   // select (and load) the desired message
05919   
05920   nsMsgKeyArray preservedSelection;
05921   nsresult rv = SaveAndClearSelection(nsnull, &preservedSelection);
05922   NS_ENSURE_SUCCESS(rv,rv);
05923 
05924   // now, restore our desired selection
05925   nsMsgKeyArray keyArray;
05926   keyArray.Add(aKey);
05927 
05928   // if the key was not found
05929   // (this can happen with "remember last selected message")
05930   // nothing will be selected
05931   rv = RestoreSelection(aKey, &keyArray);
05932   NS_ENSURE_SUCCESS(rv,rv);
05933   return NS_OK;
05934 }
05935 
05936 NS_IMETHODIMP
05937 nsMsgDBView::CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater, nsIMsgDBView **_retval)
05938 {
05939   nsMsgDBView* newMsgDBView;
05940 
05941   NS_NEWXPCOM(newMsgDBView, nsMsgDBView);
05942   if (!newMsgDBView)
05943     return NS_ERROR_OUT_OF_MEMORY;
05944 
05945   nsresult rv = CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
05946   NS_ENSURE_SUCCESS(rv,rv);
05947 
05948   NS_IF_ADDREF(*_retval = newMsgDBView);
05949   return NS_OK;
05950 }
05951 
05952 nsresult nsMsgDBView::CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater)
05953 {
05954   NS_ENSURE_ARG_POINTER(aNewMsgDBView);
05955 
05956   aNewMsgDBView->mMsgWindow = aMsgWindow;
05957   if (aMsgWindow)
05958     aMsgWindow->SetOpenFolder(m_viewFolder? m_viewFolder : m_folder);
05959   aNewMsgDBView->mMessengerInstance = aMessengerInstance;
05960   aNewMsgDBView->mCommandUpdater = aCmdUpdater;
05961   aNewMsgDBView->m_folder = m_folder;
05962   aNewMsgDBView->m_viewFlags = m_viewFlags;
05963   aNewMsgDBView->m_sortOrder = m_sortOrder;
05964   aNewMsgDBView->m_sortType = m_sortType;
05965   aNewMsgDBView->m_sortValid = m_sortValid;
05966   aNewMsgDBView->m_db = m_db;
05967   aNewMsgDBView->mDateFormater = mDateFormater;
05968   if (m_db)
05969     aNewMsgDBView->m_db->AddListener(aNewMsgDBView);
05970   aNewMsgDBView->mIsNews = mIsNews;
05971   aNewMsgDBView->mShowSizeInLines = mShowSizeInLines;
05972   aNewMsgDBView->mHeaderParser = mHeaderParser;
05973   aNewMsgDBView->mDeleteModel = mDeleteModel;
05974   aNewMsgDBView->m_flags.CopyArray(m_flags);
05975   aNewMsgDBView->m_levels.CopyArray(m_levels);
05976   aNewMsgDBView->m_keys.CopyArray(m_keys);
05977 
05978   return NS_OK;
05979 }
05980 
05981 NS_IMETHODIMP
05982 nsMsgDBView::GetSearchSession(nsIMsgSearchSession* *aSession)
05983 {
05984   NS_ASSERTION(PR_FALSE, "should be overriden by child class");
05985   return NS_ERROR_NOT_IMPLEMENTED;
05986 }
05987 
05988 NS_IMETHODIMP
05989 nsMsgDBView::SetSearchSession(nsIMsgSearchSession *aSession)
05990 {
05991   NS_ASSERTION(PR_FALSE, "should be overriden by child class");
05992   return NS_ERROR_NOT_IMPLEMENTED;
05993 }
05994 
05995 NS_IMETHODIMP
05996 nsMsgDBView::GetSupportsThreading(PRBool *aResult)
05997 {
05998   NS_ENSURE_ARG_POINTER(aResult);
05999   *aResult = PR_FALSE;
06000   return NS_OK;
06001 }
06002 
06003 NS_IMETHODIMP
06004 nsMsgDBView::FindIndexFromKey(nsMsgKey aMsgKey, PRBool aExpand, nsMsgViewIndex *aIndex)
06005 {
06006   NS_ENSURE_ARG_POINTER(aIndex);
06007 
06008   *aIndex = FindKey(aMsgKey, aExpand);
06009   return NS_OK;
06010 }
06011  
06012 static void getDateFormatPref( nsIPrefBranch* _prefBranch, const char* _prefLocalName, nsDateFormatSelector& _format )
06013 {
06014   // read
06015   PRInt32 nFormatSetting( 0 );
06016   nsresult result = _prefBranch->GetIntPref( _prefLocalName, &nFormatSetting );
06017   if ( NS_SUCCEEDED( result ) )
06018   {
06019     // translate
06020     nsDateFormatSelector res( nFormatSetting );
06021     // transfer if valid
06022     if ( ( res >= kDateFormatNone ) && ( res <= kDateFormatWeekday ) )
06023       _format = res;
06024   }
06025 }
06026 
06027 nsresult nsMsgDBView::InitDisplayFormats()
06028 {
06029   m_dateFormatDefault   = kDateFormatShort;
06030   m_dateFormatThisWeek  = kDateFormatShort;
06031   m_dateFormatToday     = kDateFormatNone;
06032 
06033   nsresult rv = NS_OK;
06034   nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
06035   NS_ENSURE_SUCCESS(rv,rv);
06036   nsCOMPtr<nsIPrefBranch> dateFormatPrefs;
06037   rv = prefs->GetBranch("mail.ui.display.dateformat.", getter_AddRefs(dateFormatPrefs));
06038   NS_ENSURE_SUCCESS(rv,rv);
06039 
06040   getDateFormatPref( dateFormatPrefs, "default", m_dateFormatDefault );
06041   getDateFormatPref( dateFormatPrefs, "thisweek", m_dateFormatThisWeek );
06042   getDateFormatPref( dateFormatPrefs, "today", m_dateFormatToday );
06043   return rv;
06044 }
06045 
06046 void nsMsgDBView::SetMRUTimeForFolder(nsIMsgFolder *folder)
06047 {
06048   PRUint32 seconds;
06049   PRTime2Seconds(PR_Now(), &seconds);
06050   nsCAutoString nowStr;
06051   nowStr.AppendInt(seconds);
06052   folder->SetStringProperty(MRU_TIME_PROPERTY, nowStr.get());
06053 }