add threading and grouping to saved searches, r=standard8, sr=neil, 379806
authorDavid Bienvenu <bienvenu@nventure.com>
Thu, 30 Oct 2008 15:40:30 -0700
changeset 774 78360e593ef3ff950ad0253f40937a6b47153925
parent 773 0a9821e4db57bda91775e57961614f857c545e37
child 775 252720b87e3f8ff9309eee139f8e9e7eb5ccae91
push idunknown
push userunknown
push dateunknown
reviewersstandard8, neil, 379806
bugs379806
add threading and grouping to saved searches, r=standard8, sr=neil, 379806
mail/base/content/commandglue.js
mail/base/content/mail3PaneWindowCommands.js
mail/base/content/mailWindowOverlay.js
mail/base/content/searchBar.js
mailnews/base/resources/content/mail3PaneWindowCommands.js
mailnews/base/resources/content/mailWindowOverlay.js
mailnews/base/resources/content/threadPane.js
mailnews/base/src/Makefile.in
mailnews/base/src/nsMsgDBView.cpp
mailnews/base/src/nsMsgDBView.h
mailnews/base/src/nsMsgGroupThread.cpp
mailnews/base/src/nsMsgGroupThread.h
mailnews/base/src/nsMsgGroupView.cpp
mailnews/base/src/nsMsgGroupView.h
mailnews/base/src/nsMsgQuickSearchDBView.cpp
mailnews/base/src/nsMsgQuickSearchDBView.h
mailnews/base/src/nsMsgSearchDBView.cpp
mailnews/base/src/nsMsgSearchDBView.h
mailnews/base/src/nsMsgSpecialViews.cpp
mailnews/base/src/nsMsgSpecialViews.h
mailnews/base/src/nsMsgThreadedDBView.cpp
mailnews/base/src/nsMsgThreadedDBView.h
mailnews/base/src/nsMsgXFViewThread.cpp
mailnews/base/src/nsMsgXFViewThread.h
mailnews/base/src/nsMsgXFVirtualFolderDBView.cpp
mailnews/base/src/nsMsgXFVirtualFolderDBView.h
--- a/mail/base/content/commandglue.js
+++ b/mail/base/content/commandglue.js
@@ -363,67 +363,81 @@ function RerootFolder(uri, newFolder, vi
   }
 }
 
 function SwitchView(command)
 {
   // when switching thread views, we might be coming out of quick search
   // or a message view.
   // first set view picker to all
-  ViewChangeByValue(kViewItemAll);
+  if (gCurrentViewValue != kViewItemAll)
+    ViewChangeByValue(kViewItemAll);
 
   // clear the QS text, if we need to
   ClearQSIfNecessary();
   
+  var oldSortType, oldSortOrder, viewFlags, viewType, db;
   // now switch views
-  var oldSortType = gDBView ? gDBView.sortType : nsMsgViewSortType.byThread;
-  var oldSortOrder = gDBView ? gDBView.sortOrder : nsMsgViewSortOrder.ascending;
-  var viewFlags = gDBView ? gDBView.viewFlags : gCurViewFlags;
-
-  // close existing view.
-  if (gDBView) {
+  if (gDBView) 
+  {
+    oldSortType = gDBView.sortType;
+    oldSortOrder = gDBView.sortOrder;
+    viewFlags = gDBView.viewFlags;
+    viewType = gDBView.viewType;
+    db = gDBView.db;
     gDBView.close();
     gDBView = null; 
   }
-
+  else
+  {
+    oldSortType = nsMsgViewSortType.byThread;
+    oldSortOrder = nsMsgViewSortOrder.ascending;
+    viewFlags = gCurViewFlags;
+    viewType = nsMsgViewType.eShowAllThreads;
+    db = null;
+  }
   switch(command)
   {
     // "All" threads and "Unread" threads don't change threading state
     case "cmd_viewAllMsgs":
       viewFlags = viewFlags & ~nsMsgViewFlagsType.kUnreadOnly;
-      CreateDBView(msgWindow.openFolder, nsMsgViewType.eShowAllThreads, viewFlags,
-            oldSortType, oldSortOrder);
       break;
     case "cmd_viewUnreadMsgs":
       viewFlags = viewFlags | nsMsgViewFlagsType.kUnreadOnly;
-      CreateDBView(msgWindow.openFolder, nsMsgViewType.eShowAllThreads, viewFlags,
-            oldSortType, oldSortOrder );
       break;
     // "Threads with Unread" and "Watched Threads with Unread" force threading
+    case "cmd_viewWatchedThreadsWithUnread":
     case "cmd_viewThreadsWithUnread":
-      CreateDBView(msgWindow.openFolder, nsMsgViewType.eShowThreadsWithUnread, nsMsgViewFlagsType.kThreadedDisplay,
-            oldSortType, oldSortOrder);
-      break;
-    case "cmd_viewWatchedThreadsWithUnread":
-      CreateDBView(msgWindow.openFolder, nsMsgViewType.eShowWatchedThreadsWithUnread, nsMsgViewFlagsType.kThreadedDisplay,
-            oldSortType, oldSortOrder);
+      viewType = nsMsgViewType.eShowThreadsWithUnread;
+      viewFlags |= nsMsgViewFlagsType.kThreadedDisplay;
       break;
     // "Ignored Threads" toggles 'ignored' inclusion --
     //   but it also resets 'With Unread' views to 'All'
     case "cmd_viewIgnoredThreads":
       if (viewFlags & nsMsgViewFlagsType.kShowIgnored)
         viewFlags = viewFlags & ~nsMsgViewFlagsType.kShowIgnored;
       else
         viewFlags = viewFlags | nsMsgViewFlagsType.kShowIgnored;
-      CreateDBView(msgWindow.openFolder, nsMsgViewType.eShowAllThreads, viewFlags,
-            oldSortType, oldSortOrder);
       break;
   }
 
+  if (db && viewType == nsMsgViewType.eShowVirtualFolderResults)
+  {
+      db.dBFolderInfo.viewFlags = viewFlags;
+      gMsgFolderSelected = null;
+      msgWindow.openFolder = null;
+      FolderPaneSelectionChange();
+      LoadCurrentlyDisplayedMessage();
+  }
+  else
+  {
+    CreateDBView(msgWindow.openFolder, viewType, viewFlags, oldSortType,
+                 oldSortOrder);
   RerootThreadPane();
+  }
 }
 
 function SetSentFolderColumns(isSentFolder)
 {
   var tree = GetThreadTree();
 
   var lastFolderSent = tree.getAttribute("lastfoldersent") == "true";
   if (isSentFolder != lastFolderSent)
--- a/mail/base/content/mail3PaneWindowCommands.js
+++ b/mail/base/content/mail3PaneWindowCommands.js
@@ -397,28 +397,28 @@ var DefaultController =
       case "cmd_selectAll":
       case "cmd_selectFlagged":
         return gDBView != null;
       // these are enabled on when we are in threaded mode
       case "cmd_selectThread":
         if (GetNumSelectedMessages() <= 0) return false;
       case "cmd_expandAllThreads":
       case "cmd_collapseAllThreads":
-        if (!gDBView || !gDBView.supportsThreading)
-          return false;
         return (gDBView.viewFlags & nsMsgViewFlagsType.kThreadedDisplay);
       case "cmd_nextFlaggedMsg":
       case "cmd_previousFlaggedMsg":
         return IsViewNavigationItemEnabled();
       case "cmd_viewAllMsgs":
       case "cmd_viewUnreadMsgs":
+      case "cmd_viewIgnoredThreads":
+        return gDBView;
       case "cmd_viewThreadsWithUnread":
       case "cmd_viewWatchedThreadsWithUnread":
-      case "cmd_viewIgnoredThreads":
-        return gDBView;
+        return gDBView && !(GetSelectedMsgFolders()[0].flags & 
+                            MSG_FOLDER_FLAG_VIRTUAL);
       case "cmd_stop":
         return true;
       case "cmd_undo":
       case "cmd_redo":
           return SetupUndoRedoCommand(command);
       case "cmd_renameFolder":
         return IsRenameFolderEnabled();
       case "cmd_sendUnsentMsgs":
--- a/mail/base/content/mailWindowOverlay.js
+++ b/mail/base/content/mailWindowOverlay.js
@@ -249,22 +249,19 @@ function InitViewSortByMenu()
   var grouped = ((gDBView.viewFlags & nsMsgViewFlagsType.kGroupBySort) != 0);
   var threaded = ((gDBView.viewFlags & nsMsgViewFlagsType.kThreadedDisplay) != 0 && !grouped);
   var sortThreadedMenuItem = document.getElementById("sortThreaded");
   var sortUnthreadedMenuItem = document.getElementById("sortUnthreaded");
 
   sortThreadedMenuItem.setAttribute("checked", threaded);
   sortUnthreadedMenuItem.setAttribute("checked", !threaded && !grouped);
 
-  sortThreadedMenuItem.setAttribute("disabled", !gDBView.supportsThreading);
-  sortUnthreadedMenuItem.setAttribute("disabled", !gDBView.supportsThreading);
-
   var groupBySortOrderMenuItem = document.getElementById("groupBySort");
 
-  groupBySortOrderMenuItem.setAttribute("disabled", !gDBView.supportsThreading || !sortTypeSupportsGrouping);
+  groupBySortOrderMenuItem.setAttribute("disabled", !sortTypeSupportsGrouping);
   groupBySortOrderMenuItem.setAttribute("checked", grouped);
 }
 
 function InitViewMessagesMenu()
 {
   var viewFlags = (gDBView) ? gDBView.viewFlags : 0;
   var viewType = (gDBView) ? gDBView.viewType : 0;
 
--- a/mail/base/content/searchBar.js
+++ b/mail/base/content/searchBar.js
@@ -172,21 +172,17 @@ function createQuickSearchView()
       treeView.selection.clearSelection();
     gPreQuickSearchView = gDBView;
     if (gDBView.viewType == nsMsgViewType.eShowVirtualFolderResults)
     {
       // remove the view as a listener on the search results
       var saveViewSearchListener = gDBView.QueryInterface(Components.interfaces.nsIMsgSearchNotify);
       gSearchSession.unregisterListener(saveViewSearchListener);
     }
-    // if grouped by sort, turn that off, as well as threaded, since we don't
-    // group quick search results yet.
     var viewFlags = gDBView.viewFlags;
-    if (viewFlags & nsMsgViewFlagsType.kGroupBySort)
-      viewFlags &= ~(nsMsgViewFlagsType.kGroupBySort | nsMsgViewFlagsType.kThreadedDisplay);
     CreateDBView(gDBView.msgFolder, (gXFVirtualFolderTerms) ? nsMsgViewType.eShowVirtualFolderResults : nsMsgViewType.eShowQuickSearchResults, viewFlags, gDBView.sortType, gDBView.sortOrder);
   }
 }
 
 function initializeSearchBar()
 {
    createQuickSearchView();
    if (!gSearchSession)
--- a/mailnews/base/resources/content/mail3PaneWindowCommands.js
+++ b/mailnews/base/resources/content/mail3PaneWindowCommands.js
@@ -404,29 +404,29 @@ var DefaultController =
       case "cmd_selectAll":
       case "cmd_selectFlagged":
         return gDBView != null;
       // these are enabled on when we are in threaded mode
       case "cmd_selectThread":
         if (GetNumSelectedMessages() <= 0) return false;
       case "cmd_expandAllThreads":
       case "cmd_collapseAllThreads":
-        if (!gDBView || !gDBView.supportsThreading) 
-          return false;
         return (gDBView.viewFlags & nsMsgViewFlagsType.kThreadedDisplay);
         break;
       case "cmd_nextFlaggedMsg":
       case "cmd_previousFlaggedMsg":
         return IsViewNavigationItemEnabled();
       case "cmd_viewAllMsgs":
       case "cmd_viewUnreadMsgs":
+      case "cmd_viewIgnoredThreads":
+        return gDBView;
       case "cmd_viewThreadsWithUnread":
       case "cmd_viewWatchedThreadsWithUnread":
-      case "cmd_viewIgnoredThreads":
-        return gDBView;
+        return gDBView && !(GetSelectedMsgFolders()[0].flags & 
+                            MSG_FOLDER_FLAG_VIRTUAL);
       case "cmd_stop":
         return true;
       case "cmd_undo":
       case "cmd_redo":
           return SetupUndoRedoCommand(command);
       case "cmd_renameFolder":
         return IsRenameFolderEnabled();
       case "cmd_sendUnsentMsgs":
--- a/mailnews/base/resources/content/mailWindowOverlay.js
+++ b/mailnews/base/resources/content/mailWindowOverlay.js
@@ -250,22 +250,19 @@ function InitViewSortByMenu()
     var grouped = ((gDBView.viewFlags & nsMsgViewFlagsType.kGroupBySort) != 0);
     var threaded = ((gDBView.viewFlags & nsMsgViewFlagsType.kThreadedDisplay) != 0 && !grouped);
     var sortThreadedMenuItem = document.getElementById("sortThreaded");
     var sortUnthreadedMenuItem = document.getElementById("sortUnthreaded");
 
     sortThreadedMenuItem.setAttribute("checked", threaded);
     sortUnthreadedMenuItem.setAttribute("checked", !threaded && !grouped);
 
-    sortThreadedMenuItem.setAttribute("disabled", !gDBView.supportsThreading);
-    sortUnthreadedMenuItem.setAttribute("disabled", !gDBView.supportsThreading);
-
     var groupBySortOrderMenuItem = document.getElementById("groupBySort");
 
-    groupBySortOrderMenuItem.setAttribute("disabled", !gDBView.supportsThreading || !sortTypeSupportsGrouping);
+    groupBySortOrderMenuItem.setAttribute("disabled", !sortTypeSupportsGrouping);
     groupBySortOrderMenuItem.setAttribute("checked", grouped);
 }
 
 function InitViewMessagesMenu()
 {
   var viewFlags = gDBView ? gDBView.viewFlags : 0;
   var viewType = gDBView ? gDBView.viewType : 0;
 
--- a/mailnews/base/resources/content/threadPane.js
+++ b/mailnews/base/resources/content/threadPane.js
@@ -170,19 +170,16 @@ function HandleColumnClick(columnID)
   var dbview = GetDBView();
   var simpleColumns = false;
   try {
     simpleColumns = !pref.getBoolPref("mailnews.thread_pane_column_unthreads");
   }
   catch (ex) {
   }
   if (sortType == "byThread") {
-    if (!dbview.supportsThreading)
-      return;
-
     if (simpleColumns)
       MsgToggleThreaded();
     else if (dbview.viewFlags & nsMsgViewFlagsType.kThreadedDisplay)
       MsgReverseSortThreadPane();
     else
       MsgSortByThread();
   }
   else {
@@ -232,36 +229,28 @@ function ThreadPaneKeyPress(event)
 {
     if (event.keyCode == 13)
       ThreadPaneDoubleClick();
 }
 
 function MsgSortByThread()
 {
   var dbview = GetDBView();
-  if(dbview && !dbview.supportsThreading)
-    return;
   dbview.viewFlags |= nsMsgViewFlagsType.kThreadedDisplay;
   dbview.viewFlags &= ~nsMsgViewFlagsType.kGroupBySort;
   MsgSortThreadPane('byDate');
 }
 
 function MsgSortThreadPane(sortName)
 {
   var sortType = nsMsgViewSortType[sortName];
   var dbview = GetDBView();
 
-  if (dbview.viewFlags & nsMsgViewFlagsType.kGroupBySort)
-  {
-    dbview.viewFlags &= ~nsMsgViewFlagsType.kGroupBySort;
-    dbview.sortType = sortType; // save sort in current view
-    viewDebug("switching view to all msgs\n");
-    SwitchView("cmd_viewAllMsgs");
-    return;
-  }
+  // turn off grouping
+  dbview.viewFlags &= ~nsMsgViewFlagsType.kGroupBySort;
 
   dbview.sort(sortType, nsMsgViewSortOrder.ascending);
   UpdateSortIndicators(sortType, nsMsgViewSortOrder.ascending);
 }
 
 function MsgReverseSortThreadPane()
 {
   var dbview = GetDBView();
@@ -271,41 +260,34 @@ function MsgReverseSortThreadPane()
   else {
     MsgSortAscending();
   }
 }
 
 function MsgToggleThreaded()
 {
     var dbview = GetDBView();
-
-    dbview.viewFlags ^= nsMsgViewFlagsType.kThreadedDisplay;
-    if (dbview.viewFlags & nsMsgViewFlagsType.kGroupBySort)
-    {
-      dbview.viewFlags &= ~nsMsgViewFlagsType.kGroupBySort;
-      viewDebug("switching view to all msgs\n");
-      SwitchView("cmd_viewAllMsgs");
-      return;
-    }
+    var newViewFlags = dbview.viewFlags ^ nsMsgViewFlagsType.kThreadedDisplay;
+    newViewFlags &= ~nsMsgViewFlagsType.kGroupBySort;
+    dbview.viewFlags = newViewFlags;
 
     dbview.sort(dbview.sortType, dbview.sortOrder);
     UpdateSortIndicators(dbview.sortType, dbview.sortOrder);
 }
 
 function MsgSortThreaded()
 {
     var dbview = GetDBView();
     var viewFlags = dbview.viewFlags;
-
-    if (viewFlags & nsMsgViewFlagsType.kGroupBySort)
-    {
-      dbview.viewFlags &= ~nsMsgViewFlagsType.kGroupBySort;
-      viewDebug("switching view to all msgs\n");
+    let wasGrouped = viewFlags & nsMsgViewFlagsType.kGroupBySort;
+    dbview.viewFlags &= ~nsMsgViewFlagsType.kGroupBySort;
+    // if we were grouped, and not a saved search, just rebuild the view
+    if (wasGrouped && !(gMsgFolderSelected.flags & 
+                       Components.interfaces.nsMsgFolderFlags.Virtual))
       SwitchView("cmd_viewAllMsgs");
-    }
     // Toggle if not already threaded.
     else if ((viewFlags & nsMsgViewFlagsType.kThreadedDisplay) == 0)
         MsgToggleThreaded();
 }
 
 function MsgGroupBySort()
 {
   var dbview = GetDBView();
@@ -317,27 +299,34 @@ function MsgGroupBySort()
 
   var sortTypeSupportsGrouping = (sortType == nsMsgViewSortType.byAuthor 
          || sortType == nsMsgViewSortType.byDate || sortType == nsMsgViewSortType.byReceived || sortType == nsMsgViewSortType.byPriority
          || sortType == nsMsgViewSortType.bySubject || sortType == nsMsgViewSortType.byTags
          || sortType == nsMsgViewSortType.byStatus  || sortType == nsMsgViewSortType.byRecipient
          || sortType == nsMsgViewSortType.byAccount || sortType == nsMsgViewSortType.byFlagged
          || sortType == nsMsgViewSortType.byAttachments);
 
-  if (!dbview.supportsThreading || !sortTypeSupportsGrouping)
+  if (!sortTypeSupportsGrouping)
     return; // we shouldn't be trying to group something we don't support grouping for...
 
   viewFlags |= nsMsgViewFlagsType.kThreadedDisplay | nsMsgViewFlagsType.kGroupBySort;
+  if (gDBView &&
+      gMsgFolderSelected.flags & Components.interfaces.nsMsgFolderFlags.Virtual)
+  {
+    gDBView.viewFlags = viewFlags;
+    UpdateSortIndicators(sortType, nsMsgViewSortOrder.ascending);
+    return;
+  }
   // null this out, so we don't try sort.
   if (gDBView) {
     gDBView.close();
     gDBView = null;
   }
-  var dbviewContractId = "@mozilla.org/messenger/msgdbview;1?type=group";
-  gDBView = Components.classes[dbviewContractId].createInstance(Components.interfaces.nsIMsgDBView);
+  gDBView = Components.classes["@mozilla.org/messenger/msgdbview;1?type=group"]
+                                .createInstance(Components.interfaces.nsIMsgDBView);
 
   if (!gThreadPaneCommandUpdater)
     gThreadPaneCommandUpdater = new nsMsgDBViewCommandUpdater();
 
 
   gDBView.init(messenger, msgWindow, gThreadPaneCommandUpdater);
   gDBView.open(msgFolder, sortType, sortOrder, viewFlags, count);
   RerootThreadPane();
--- a/mailnews/base/src/Makefile.in
+++ b/mailnews/base/src/Makefile.in
@@ -116,16 +116,17 @@ CPPSRCS		= \
 		nsMsgPrintEngine.cpp \
 		nsStatusBarBiffManager.cpp \
 		nsMsgDBView.cpp \
 		nsMsgThreadedDBView.cpp \
 		nsMsgSpecialViews.cpp \
 		nsMsgQuickSearchDBView.cpp \
 		nsMsgSearchDBView.cpp \
 		nsMsgXFVirtualFolderDBView.cpp \
+		nsMsgXFViewThread.cpp \
 		nsMsgGroupThread.cpp \
 		nsMsgGroupView.cpp \
 		nsMsgOfflineManager.cpp \
 		nsMsgProgress.cpp \
 		nsMessengerContentHandler.cpp \
 		nsSpamSettings.cpp \
 		nsCidProtocolHandler.cpp \
 		nsMsgContentPolicy.cpp \
--- a/mailnews/base/src/nsMsgDBView.cpp
+++ b/mailnews/base/src/nsMsgDBView.cpp
@@ -1,9 +1,9 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
@@ -839,16 +839,21 @@ nsresult nsMsgDBView::RestoreSelection(n
 }
 
 nsresult nsMsgDBView::GenerateURIForMsgKey(nsMsgKey aMsgKey, nsIMsgFolder *folder, nsACString & aURI)
 {
   NS_ENSURE_ARG(folder);
   return folder->GenerateMessageURI(aMsgKey, aURI);
 }
 
+nsresult nsMsgDBView::GetMessageEnumerator(nsISimpleEnumerator **enumerator)
+{
+  return m_db->EnumerateMessages(enumerator);
+}
+
 nsresult nsMsgDBView::CycleThreadedColumn(nsIDOMElement * aElement)
 {
   nsAutoString currentView;
 
   // toggle threaded/unthreaded mode
   aElement->GetAttribute(NS_LITERAL_STRING("currentView"), currentView);
   if (currentView.EqualsLiteral("threaded"))
     aElement->SetAttribute(NS_LITERAL_STRING("currentView"), NS_LITERAL_STRING("unthreaded"));
@@ -2747,17 +2752,17 @@ nsresult nsMsgDBView::DownloadForOffline
   return rv;
 }
 
 nsresult nsMsgDBView::DownloadFlaggedForOffline(nsIMsgWindow *window)
 {
   nsresult rv = NS_OK;
   nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID));
   nsCOMPtr <nsISimpleEnumerator> enumerator;
-  rv = m_db->EnumerateMessages(getter_AddRefs(enumerator));
+  rv = GetMessageEnumerator(getter_AddRefs(enumerator));
   if (NS_SUCCEEDED(rv) && enumerator)
   {
     PRBool hasMore;
 
     while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) && (hasMore == PR_TRUE))
     {
       nsCOMPtr <nsIMsgDBHdr> pHeader;
       rv = enumerator->GetNext(getter_AddRefs(pHeader));
@@ -2809,17 +2814,17 @@ nsresult nsMsgDBView::SetReadByIndex(nsM
   nsCOMPtr <nsIMsgDatabase> dbToUse;
   rv = GetDBForViewIndex(index, getter_AddRefs(dbToUse));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = dbToUse->MarkRead(m_keys[index], read, this);
   NoteChange(index, 1, nsMsgViewNotificationCode::changed);
   if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
   {
-    nsMsgViewIndex threadIndex = ThreadIndexOfMsg(m_keys[index], index, nsnull, nsnull);
+    nsMsgViewIndex threadIndex = GetThreadIndex(index);
     if (threadIndex != index)
       NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
   }
   return rv;
 }
 
 nsresult nsMsgDBView::SetThreadOfMsgReadByIndex(nsMsgViewIndex index, nsTArray<nsMsgKey> &keysMarkedRead, PRBool /*read*/)
 {
@@ -3462,31 +3467,31 @@ nsresult nsMsgDBView::GetLongField(nsIMs
       break;
     case nsMsgViewSortType::byDate:
       // when sorting threads by date, we want the date of the newest msg
       // in the thread
       if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay
         && ! (m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
       {
         nsCOMPtr <nsIMsgThread> thread;
-        rv = m_db->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread));
+        rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread));
         NS_ENSURE_SUCCESS(rv, rv);
         thread->GetNewestMsgDate(result);
       }
       else
         rv = msgHdr->GetDateInSeconds(result);
       break;
     case nsMsgViewSortType::byReceived:
       // when sorting threads by received date, we want the received date of the newest msg
       // in the thread
       if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay
         && ! (m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
       {
         nsCOMPtr <nsIMsgThread> thread;
-        rv = m_db->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread));
+        rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread));
         NS_ENSURE_SUCCESS(rv, rv);
         thread->GetNewestMsgDate(result);
       }
       else
       {
         rv = msgHdr->GetUint32Property("dateReceived", result);  // Already in seconds...
         if (*result == 0)  // Use Date instead, we have no Received property
           rv = msgHdr->GetDateInSeconds(result);
@@ -4022,19 +4027,19 @@ nsMsgViewIndex nsMsgDBView::GetIndexOfFi
   // unread message in the thread. Sometimes, that will be wrong, however, so
   // let's skip it until we're sure it's necessary.
   //  (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly)
   //    ? threadHdr->GetFirstUnreadKey(m_db) : threadHdr->GetChildAt(0);
   PRUint32 numThreadChildren;
   threadHdr->GetNumChildren(&numThreadChildren);
   while (retIndex == nsMsgViewIndex_None && childIndex < numThreadChildren)
   {
-    nsMsgKey childKey;
-    threadHdr->GetChildKeyAt(childIndex++, &childKey);
-    retIndex = FindViewIndex(childKey);
+    nsCOMPtr<nsIMsgDBHdr> childHdr;
+    threadHdr->GetChildHdrAt(childIndex++, getter_AddRefs(childHdr));
+    retIndex = FindHdr(childHdr);
   }
   return retIndex;
 }
 
 nsresult nsMsgDBView::GetFirstMessageHdrToDisplayInThread(nsIMsgThread *threadHdr, nsIMsgDBHdr **result)
 {
   nsresult rv;
 
@@ -4051,31 +4056,51 @@ nsresult nsMsgDBView::GetFirstMessageHdr
 // then we can avoid looking for the msgKey.
 nsMsgViewIndex nsMsgDBView::ThreadIndexOfMsg(nsMsgKey msgKey,
                                             nsMsgViewIndex msgIndex /* = nsMsgViewIndex_None */,
                                             PRInt32 *pThreadCount /* = NULL */,
                                             PRUint32 *pFlags /* = NULL */)
 {
   if (! (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
     return nsMsgViewIndex_None;
-  nsCOMPtr <nsIMsgThread> threadHdr;
   nsCOMPtr <nsIMsgDBHdr> msgHdr;
   nsresult rv = m_db->GetMsgHdrForKey(msgKey, getter_AddRefs(msgHdr));
   NS_ENSURE_SUCCESS(rv, nsMsgViewIndex_None);
-  rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(threadHdr));
+  return ThreadIndexOfMsgHdr(msgHdr, msgIndex, pThreadCount, pFlags);
+}
+
+nsMsgViewIndex nsMsgDBView::GetThreadIndex(nsMsgViewIndex msgIndex)
+{
+  if (!IsValidIndex(msgIndex))
+    return NS_MSG_INVALID_DBVIEW_INDEX;
+
+  // scan up looking for level 0 message.
+  while (m_levels[msgIndex] && msgIndex)
+    --msgIndex;
+  return msgIndex;
+}
+
+nsMsgViewIndex 
+nsMsgDBView::ThreadIndexOfMsgHdr(nsIMsgDBHdr *msgHdr, 
+                                 nsMsgViewIndex msgIndex,
+                                 PRInt32 *pThreadCount,
+                                 PRUint32 *pFlags)
+{
+  nsCOMPtr<nsIMsgThread> threadHdr;
+  nsresult rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(threadHdr));
   NS_ENSURE_SUCCESS(rv, nsMsgViewIndex_None);
 
   nsMsgViewIndex retIndex = nsMsgViewIndex_None;
 
   if (threadHdr != nsnull)
   {
     if (msgIndex == nsMsgViewIndex_None)
-      msgIndex = FindViewIndex(msgKey);
-
-    if (msgIndex == nsMsgViewIndex_None)  // key is not in view, need to find by thread
+      msgIndex = FindHdr(msgHdr);
+
+    if (msgIndex == nsMsgViewIndex_None)  // hdr is not in view, need to find by thread
     {
       msgIndex = GetIndexOfFirstDisplayedKeyInThread(threadHdr);
       //nsMsgKey    threadKey = (msgIndex == nsMsgViewIndex_None) ? nsMsgKey_None : GetAt(msgIndex);
       if (pFlags)
         threadHdr->GetFlags(pFlags);
     }
     nsMsgViewIndex startOfThread = msgIndex;
     while ((PRInt32) startOfThread >= 0 && m_levels[startOfThread] != 0)
@@ -4155,21 +4180,20 @@ nsMsgViewIndex  nsMsgDBView::FindKey(nsM
           || (flags & MSG_VIEW_FLAG_DUMMY))
           retIndex = (nsMsgViewIndex) m_keys.IndexOf(key, threadIndex + 1);
       }
     }
   }
   return retIndex;
 }
 
-nsresult nsMsgDBView::GetThreadCount(nsMsgKey messageKey, PRUint32 *pThreadCount)
-{
-  nsresult rv = NS_MSG_MESSAGE_NOT_FOUND;
+nsresult nsMsgDBView::GetThreadCount(nsMsgViewIndex index, PRUint32 *pThreadCount)
+{
   nsCOMPtr <nsIMsgDBHdr> msgHdr;
-  rv = m_db->GetMsgHdrForKey(messageKey, getter_AddRefs(msgHdr));
+  nsresult rv = GetMsgHdrForViewIndex(index, getter_AddRefs(msgHdr));
   NS_ENSURE_SUCCESS(rv, rv);
   nsCOMPtr <nsIMsgThread> pThread;
   rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(pThread));
   if (NS_SUCCEEDED(rv) && pThread != nsnull)
     rv = pThread->GetNumChildren(pThreadCount);
   return rv;
 }
 
@@ -4207,17 +4231,17 @@ nsresult nsMsgDBView::ExpansionDelta(nsM
   if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
     return NS_OK;
 
   // The client can pass in the key of any message
   // in a thread and get the expansion delta for the thread.
 
   if (!(m_viewFlags & nsMsgViewFlagsType::kUnreadOnly))
   {
-    rv = GetThreadCount(m_keys[index], &numChildren);
+    rv = GetThreadCount(index, &numChildren);
     NS_ENSURE_SUCCESS(rv, rv);
   }
   else
   {
     numChildren = CountExpandedThread(index);
   }
 
   if (flags & MSG_FLAG_ELIDED)
@@ -4227,17 +4251,17 @@ nsresult nsMsgDBView::ExpansionDelta(nsM
 
   return NS_OK;
 }
 
 nsresult nsMsgDBView::ToggleExpansion(nsMsgViewIndex index, PRUint32 *numChanged)
 {
   NS_ENSURE_ARG(numChanged);
   *numChanged = 0;
-  nsMsgViewIndex threadIndex = ThreadIndexOfMsg(GetAt(index), index);
+  nsMsgViewIndex threadIndex = GetThreadIndex(index);
   if (threadIndex == nsMsgViewIndex_None)
   {
     NS_ASSERTION(PR_FALSE, "couldn't find thread");
     return NS_MSG_MESSAGE_NOT_FOUND;
   }
   PRInt32  flags = m_flags[threadIndex];
 
   // if not a thread, or doesn't have children, no expand/collapse
@@ -4271,17 +4295,19 @@ nsresult nsMsgDBView::ExpandAndSelectThr
 {
   nsresult rv;
 
   nsMsgViewIndex threadIndex;
   PRBool inThreadedMode = (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay);
 
   if (inThreadedMode)
   {
-    threadIndex = ThreadIndexOfMsg(GetAt(index), index);
+    nsCOMPtr<nsIMsgDBHdr> msgHdr;
+    GetMsgHdrForViewIndex(index, getter_AddRefs(msgHdr));
+    threadIndex = ThreadIndexOfMsgHdr(msgHdr, index);
     if (threadIndex == nsMsgViewIndex_None)
     {
       NS_ASSERTION(PR_FALSE, "couldn't find thread");
       return NS_MSG_MESSAGE_NOT_FOUND;
     }
   }
   else
   {
@@ -4352,37 +4378,30 @@ nsresult nsMsgDBView::ExpandByIndex(nsMs
   flags &= ~MSG_FLAG_ELIDED;
 
   if ((PRUint32) index > m_keys.Length())
     return NS_MSG_MESSAGE_NOT_FOUND;
 
   nsMsgKey firstIdInThread = m_keys[index];
   nsCOMPtr <nsIMsgDBHdr> msgHdr;
   nsCOMPtr <nsIMsgThread> pThread;
-  m_db->GetMsgHdrForKey(firstIdInThread, getter_AddRefs(msgHdr));
-  if (msgHdr == nsnull)
-  {
-    NS_ASSERTION(PR_FALSE, "couldn't find message to expand");
-    return NS_MSG_MESSAGE_NOT_FOUND;
-  }
-  nsresult rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(pThread));
+  nsresult rv = GetThreadContainingIndex(index, getter_AddRefs(pThread));
   NS_ENSURE_SUCCESS(rv, rv);
   m_flags[index] = flags;
   NoteChange(index, 1, nsMsgViewNotificationCode::changed);
   if (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly)
   {
     if (flags & MSG_FLAG_READ)
       m_levels.AppendElement(0);  // keep top level hdr in thread, even though read.
     rv = ListUnreadIdsInThread(pThread,  index, &numExpanded);
   }
   else
     rv = ListIdsInThread(pThread,  index, &numExpanded);
 
   NoteStartChange(index + 1, numExpanded, nsMsgViewNotificationCode::insertOrDelete);
-
   NoteEndChange(index + 1, numExpanded, nsMsgViewNotificationCode::insertOrDelete);
   if (pNumExpanded != nsnull)
     *pNumExpanded = numExpanded;
   return rv;
 }
 
 nsresult nsMsgDBView::CollapseAll()
 {
@@ -4435,20 +4454,17 @@ nsresult nsMsgDBView::OnNewHeader(nsIMsg
     if (newHdr)
   rv = AddHdr(newHdr);
     return rv;
 }
 
 nsresult nsMsgDBView::GetThreadContainingIndex(nsMsgViewIndex index, nsIMsgThread **resultThread)
 {
   nsCOMPtr <nsIMsgDBHdr> msgHdr;
-
-  NS_ENSURE_TRUE(m_db, NS_ERROR_NULL_POINTER);
-
-  nsresult rv = m_db->GetMsgHdrForKey(m_keys[index], getter_AddRefs(msgHdr));
+  nsresult rv = GetMsgHdrForViewIndex(index, getter_AddRefs(msgHdr));
   NS_ENSURE_SUCCESS(rv, rv);
   return GetThreadContainingMsgHdr(msgHdr, resultThread);
 }
 
 nsMsgViewIndex 
 nsMsgDBView::GetIndexForThread(nsIMsgDBHdr *msgHdr)
 {
   // Take advantage of the fact that we're already sorted
@@ -4685,17 +4701,17 @@ nsresult  nsMsgDBView::AddHdr(nsIMsgDBHd
 #endif
 
   if (resultIndex)
     *resultIndex = nsMsgViewIndex_None;
 
   if (!GetShowingIgnored())
   {
     nsCOMPtr <nsIMsgThread> thread;
-    m_db->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread));
+    GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread));
     if (thread)
     {
       thread->GetFlags(&flags);
       if (flags & MSG_FLAG_IGNORED)
         return NS_OK;
     }
 
     PRBool ignored;
@@ -4922,22 +4938,19 @@ nsresult nsMsgDBView::ListIdsInThread(ns
       // we'll use this rv to indicate there's something wrong with the db
       // though for now it probably won't get paid attention to.
       m_db->SetSummaryValid(PR_FALSE);
       rv = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
     }
   }
 
   // We may have added too many elements (i.e., subthreads were cut)
+  // ### fix for cross folder view case.
   if (*pNumListed < numChildren)
-  {
-    m_keys.RemoveElementsAt(viewIndex, numChildren - *pNumListed);
-    m_flags.RemoveElementsAt(viewIndex, numChildren - *pNumListed);
-    m_levels.RemoveElementsAt(viewIndex, numChildren - *pNumListed);
-  }
+    RemoveRows(viewIndex, numChildren - *pNumListed);
   return rv;
 }
 
 PRInt32 nsMsgDBView::FindLevelInThread(nsIMsgDBHdr *msgHdr, nsMsgViewIndex startOfThread, nsMsgViewIndex viewIndex)
 {
   nsCOMPtr <nsIMsgDBHdr> curMsgHdr = msgHdr;
   nsMsgKey msgKey;
   msgHdr->GetMessageKey(&msgKey);
@@ -4970,16 +4983,246 @@ PRInt32 nsMsgDBView::FindLevelInThread(n
       // need to update msgKey so the check for a msgHdr with matching
       // key+parentKey will work after first time through loop
       curMsgHdr->GetMessageKey(&msgKey);
     }
   }
   return 1;
 }
 
+// ### Can this be combined with GetIndexForThread??
+nsMsgViewIndex 
+nsMsgDBView::GetThreadRootIndex(nsIMsgDBHdr *msgHdr)
+{
+  // Take advantage of the fact that we're already sorted
+  // and find the thread root via a binary search.
+
+  nsMsgViewIndex highIndex = m_keys.Length();
+  nsMsgViewIndex lowIndex = 0;
+  IdKeyPtr EntryInfo1, EntryInfo2;
+  EntryInfo1.key = nsnull;
+  EntryInfo2.key = nsnull;
+
+  nsresult rv;
+  PRUint16 maxLen;
+  eFieldType fieldType;
+  rv = GetFieldTypeAndLenForSort(m_sortType, &maxLen, &fieldType);
+  const void *pValue1 = &EntryInfo1, *pValue2 = &EntryInfo2;
+
+  int retStatus = 0;
+  msgHdr->GetMessageKey(&EntryInfo1.id);
+  msgHdr->GetFolder(&EntryInfo1.folder);
+  EntryInfo1.folder->Release();
+  //check if a custom column handler exists. If it does then grab it and pass it in
+  //to either GetCollationKey or GetLongField
+  nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandlerFromDBInfo();
+
+  viewSortInfo comparisonContext;
+  comparisonContext.view = this;
+  comparisonContext.isSecondarySort = PR_FALSE;
+  comparisonContext.ascendingSort = (m_sortOrder == nsMsgViewSortOrder::ascending);
+  nsCOMPtr<nsIMsgDatabase> hdrDB;
+  EntryInfo1.folder->GetMsgDatabase(nsnull, getter_AddRefs(hdrDB));
+  comparisonContext.db = hdrDB.get();
+  switch (fieldType)
+  {
+    case kCollationKey:
+      rv = GetCollationKey(msgHdr, m_sortType, &EntryInfo1.key, &EntryInfo1.dword, colHandler);
+      NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key");
+      break;
+    case kU32:
+      if (m_sortType == nsMsgViewSortType::byId)
+        EntryInfo1.dword = EntryInfo1.id;
+      else
+        GetLongField(msgHdr, m_sortType, &EntryInfo1.dword, colHandler);
+      break;
+    default:
+      return highIndex;
+  }
+  while (highIndex > lowIndex)
+  {
+    nsMsgViewIndex tryIndex = (lowIndex + highIndex) / 2;
+    // need to adjust tryIndex if it's not a thread.
+    while (m_levels[tryIndex] && tryIndex)
+      tryIndex--;
+
+    if (tryIndex < lowIndex)
+    {
+      NS_ERROR("try index shouldn't be less than low index");
+      break;
+    }
+    EntryInfo2.id = m_keys[tryIndex];
+    GetFolderForViewIndex(tryIndex, &EntryInfo2.folder);
+    EntryInfo2.folder->Release();
+    
+    nsCOMPtr<nsIMsgDBHdr> tryHdr;
+    nsCOMPtr<nsIMsgDatabase> db;
+    // ### this should get the db from the folder...
+    GetDBForViewIndex(tryIndex, getter_AddRefs(db));
+    if (db)
+      rv = db->GetMsgHdrForKey(EntryInfo2.id, getter_AddRefs(tryHdr));
+    if (!tryHdr)
+      break;
+    if (tryHdr == msgHdr)
+    {
+      highIndex = tryIndex;
+      break;
+    }
+    if (fieldType == kCollationKey)
+    {
+      PR_FREEIF(EntryInfo2.key);
+      rv = GetCollationKey(tryHdr, m_sortType, &EntryInfo2.key, &EntryInfo2.dword, colHandler);
+      NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key");
+      retStatus = FnSortIdKeyPtr(&pValue1, &pValue2, &comparisonContext);
+    }
+    else if (fieldType == kU32)
+    {
+      if (m_sortType == nsMsgViewSortType::byId)
+        EntryInfo2.dword = EntryInfo2.id;
+      else
+        GetLongField(tryHdr, m_sortType, &EntryInfo2.dword, colHandler);
+      retStatus = FnSortIdUint32(&pValue1, &pValue2, &comparisonContext);
+    }
+    if (retStatus == 0)
+    {
+      highIndex = tryIndex;
+      break;
+    }
+
+    if (retStatus < 0)
+    {
+      highIndex = tryIndex;
+      // we already made sure tryIndex was at a thread at the top of the loop.
+    }
+    else
+    {
+      lowIndex = tryIndex + 1;
+      while (lowIndex < GetSize() && m_levels[lowIndex])
+        lowIndex++;
+    }
+  }
+
+  nsCOMPtr<nsIMsgDBHdr> resultHdr;
+  GetMsgHdrForViewIndex(highIndex, getter_AddRefs(resultHdr));
+
+  if (resultHdr != msgHdr)
+  {
+    NS_WARNING("didn't find hdr");
+    highIndex = FindHdr(msgHdr);
+#ifdef DEBUG_David_Bienvenu
+    if (highIndex != nsMsgViewIndex_None)
+    {
+      NS_WARNING("but find hdr did");
+      printf("level of found hdr = %d\n", m_levels[highIndex]);
+      ValidateSort();
+    }
+#endif
+    return highIndex;
+  }
+  PR_Free(EntryInfo1.key);
+  PR_Free(EntryInfo2.key);
+  return msgHdr == resultHdr ? highIndex : nsMsgViewIndex_None;
+}
+
+#ifdef DEBUG_David_Bienvenu
+
+void nsMsgDBView::InitEntryInfoForIndex(nsMsgViewIndex i, IdKeyPtr &EntryInfo)
+{
+  EntryInfo.key = nsnull;
+
+  nsresult rv;
+  PRUint16 maxLen;
+  eFieldType fieldType;
+  rv = GetFieldTypeAndLenForSort(m_sortType, &maxLen, &fieldType);
+
+  nsCOMPtr<nsIMsgDBHdr> msgHdr;
+  GetMsgHdrForViewIndex(i, getter_AddRefs(msgHdr));
+
+  msgHdr->GetMessageKey(&EntryInfo.id);
+  msgHdr->GetFolder(&EntryInfo.folder);
+  EntryInfo.folder->Release();
+  //check if a custom column handler exists. If it does then grab it and pass it in
+  //to either GetCollationKey or GetLongField
+  nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandlerFromDBInfo();
+
+  nsCOMPtr<nsIMsgDatabase> hdrDB;
+  EntryInfo.folder->GetMsgDatabase(nsnull, getter_AddRefs(hdrDB));
+  switch (fieldType)
+  {
+    case kCollationKey:
+      PR_FREEIF(EntryInfo.key);
+      rv = GetCollationKey(msgHdr, m_sortType, &EntryInfo.key, &EntryInfo.dword, colHandler);
+      NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key");
+      break;
+    case kU32:
+      if (m_sortType == nsMsgViewSortType::byId)
+        EntryInfo.dword = EntryInfo.id;
+      else
+        GetLongField(msgHdr, m_sortType, &EntryInfo.dword, colHandler);
+      break;
+    default:
+      NS_ERROR("invalid field type");
+  }
+}
+
+void nsMsgDBView::ValidateSort()
+{
+  IdKeyPtr EntryInfo1, EntryInfo2;
+  nsCOMPtr<nsIMsgDBHdr> hdr1, hdr2;
+
+  PRUint16  maxLen;
+  eFieldType fieldType;
+  GetFieldTypeAndLenForSort(m_sortType, &maxLen, &fieldType);
+
+  viewSortInfo comparisonContext;
+  comparisonContext.view = this;
+  comparisonContext.isSecondarySort = PR_FALSE;
+  comparisonContext.ascendingSort = (m_sortOrder == nsMsgViewSortOrder::ascending);
+  nsCOMPtr<nsIMsgDatabase> db;
+  GetDBForViewIndex(0, getter_AddRefs(db));
+  // this is only for comparing collation keys - it could be any db.
+  comparisonContext.db = db.get();
+
+  for (nsMsgViewIndex i = 0; i < m_keys.Length();)
+  {
+    // ignore non threads
+    if (m_levels[i])
+    {
+      i++;
+      continue;
+    }
+
+    // find next header.
+    nsMsgViewIndex j = i + 1;
+    while (j < m_keys.Length() && m_levels[j])
+      j++;
+    if (j == m_keys.Length())
+      break;
+
+    InitEntryInfoForIndex(i, EntryInfo1);
+    InitEntryInfoForIndex(j, EntryInfo2);
+    const void *pValue1 = &EntryInfo1, *pValue2 = &EntryInfo2;
+    int retStatus = 0;
+    if (fieldType == kCollationKey)
+      retStatus = FnSortIdKeyPtr(&pValue1, &pValue2, &comparisonContext);
+    else if (fieldType == kU32)
+      retStatus = FnSortIdUint32(&pValue1, &pValue2, &comparisonContext);
+
+    if (retStatus && (retStatus < 0) == (m_sortOrder == nsMsgViewSortOrder::ascending))
+    {
+      NS_ERROR("view not sorted correctly");
+      break;
+    }
+    // j is the new i.
+    i = j;
+  }
+}
+
+#endif
+
 nsresult nsMsgDBView::ListUnreadIdsInThread(nsIMsgThread *threadHdr, nsMsgViewIndex startOfThreadViewIndex, PRUint32 *pNumListed)
 {
   NS_ENSURE_ARG(threadHdr);
   // these children ids should be in thread order.
   nsMsgViewIndex viewIndex = startOfThreadViewIndex + 1;
   *pNumListed = 0;
   nsMsgKey topLevelMsgKey = m_keys[startOfThreadViewIndex];
 
@@ -5043,17 +5286,17 @@ NS_IMETHODIMP nsMsgDBView::OnHdrFlagsCha
       // update the previous view, if any.
       OnExtraFlagChanged(index, aNewFlags);
       NoteChange(index, 1, nsMsgViewNotificationCode::changed);
     }
 
     PRUint32 deltaFlags = (aOldFlags ^ aNewFlags);
     if (deltaFlags & (MSG_FLAG_READ | MSG_FLAG_NEW))
     {
-      nsMsgViewIndex threadIndex = ThreadIndexOfMsg(msgKey);
+      nsMsgViewIndex threadIndex = GetThreadIndex(index);
       // may need to fix thread counts
       if (threadIndex != nsMsgViewIndex_None && threadIndex != index)
         NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
     }
  }
   // don't need to propagate notifications, right?
   return NS_OK;
 }
@@ -5478,17 +5721,17 @@ nsresult nsMsgDBView::NavigateFromPos(ns
               break;
             rv = FindPrevUnread(m_keys[startIndex], pResultKey,
                                 &resultThreadKey);
             if (NS_SUCCEEDED(rv))
             {
                 *pResultIndex = FindViewIndex(*pResultKey);
                 if (*pResultKey != resultThreadKey && (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
                 {
-                    threadIndex  = ThreadIndexOfMsg(*pResultKey, nsMsgViewIndex_None);
+                    threadIndex  = GetThreadIndex(*pResultIndex);
                     if (*pResultIndex == nsMsgViewIndex_None)
                     {
                         nsCOMPtr <nsIMsgThread> threadHdr;
                         nsCOMPtr <nsIMsgDBHdr> msgHdr;
                         rv = m_db->GetMsgHdrForKey(*pResultKey, getter_AddRefs(msgHdr));
                         NS_ENSURE_SUCCESS(rv, rv);
                         rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(threadHdr));
                         NS_ENSURE_SUCCESS(rv, rv);
@@ -5984,17 +6227,17 @@ nsresult nsMsgDBView::SetSubthreadKilled
 
   rv = m_db->MarkHeaderKilled(header, ignored, this);
   NS_ENSURE_SUCCESS(rv, rv);
  
   if (ignored)
   {
     nsCOMPtr <nsIMsgThread> thread;
     nsresult rv;
-    rv = m_db->GetThreadContainingMsgHdr(header, getter_AddRefs(thread));
+    rv = GetThreadContainingMsgHdr(header, getter_AddRefs(thread));
     if (NS_FAILED(rv))
       return NS_OK; // So we didn't mark threads read
 
     PRUint32 children, current;
     thread->GetNumChildren(&children);
 
     nsMsgKey headKey;
     header->GetMessageKey(&headKey);
@@ -6492,17 +6735,17 @@ nsMsgDBView::SetSearchSession(nsIMsgSear
   NS_ASSERTION(PR_FALSE, "should be overriden by child class");
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 nsMsgDBView::GetSupportsThreading(PRBool *aResult)
 {
   NS_ENSURE_ARG_POINTER(aResult);
-  *aResult = PR_FALSE;
+  *aResult = PR_TRUE;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMsgDBView::FindIndexFromKey(nsMsgKey aMsgKey, PRBool aExpand, nsMsgViewIndex *aIndex)
 {
   NS_ENSURE_ARG_POINTER(aIndex);
 
@@ -6547,8 +6790,55 @@ nsresult nsMsgDBView::InitDisplayFormats
 void nsMsgDBView::SetMRUTimeForFolder(nsIMsgFolder *folder)
 {
   PRUint32 seconds;
   PRTime2Seconds(PR_Now(), &seconds);
   nsCAutoString nowStr;
   nowStr.AppendInt(seconds);
   folder->SetStringProperty(MRU_TIME_PROPERTY, nowStr);
 }
+
+
+NS_IMPL_ISUPPORTS1(nsMsgDBView::nsMsgViewHdrEnumerator, nsISimpleEnumerator)
+
+nsMsgDBView::nsMsgViewHdrEnumerator::nsMsgViewHdrEnumerator(nsMsgDBView *view)
+{
+  // we need to clone the view because the caller may clear the
+  // current view immediately. It also makes it easier to expand all
+  // if we're working on a copy.
+  nsCOMPtr<nsIMsgDBView> clonedView;
+  view->CloneDBView(nsnull, nsnull, nsnull, getter_AddRefs(clonedView));
+  m_view = static_cast<nsMsgDBView*>(clonedView.get());
+  // make sure we enumerate over collapsed threads by expanding all.
+  m_view->ExpandAll();
+  m_curHdrIndex = 0;
+}
+
+nsMsgDBView::nsMsgViewHdrEnumerator::~nsMsgViewHdrEnumerator()
+{
+}
+
+NS_IMETHODIMP nsMsgDBView::nsMsgViewHdrEnumerator::GetNext(nsISupports **aItem)
+{
+  NS_ENSURE_ARG_POINTER(aItem);
+
+  if (m_curHdrIndex >= m_view->GetSize())
+    return NS_ERROR_FAILURE;
+
+  nsCOMPtr<nsIMsgDBHdr> nextHdr;
+
+  nsresult rv = m_view->GetMsgHdrForViewIndex(m_curHdrIndex++, getter_AddRefs(nextHdr));
+  NS_IF_ADDREF(*aItem = nextHdr);
+  return rv;
+}
+
+NS_IMETHODIMP nsMsgDBView::nsMsgViewHdrEnumerator::HasMoreElements(PRBool *aResult)
+{
+  NS_ENSURE_ARG_POINTER(aResult);
+  *aResult = m_curHdrIndex < m_view->GetSize();
+  return NS_OK;
+}
+
+nsresult nsMsgDBView::GetViewEnumerator(nsISimpleEnumerator **enumerator)
+{
+  NS_IF_ADDREF(*enumerator = new nsMsgViewHdrEnumerator(this));
+  return (*enumerator) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
--- a/mailnews/base/src/nsMsgDBView.h
+++ b/mailnews/base/src/nsMsgDBView.h
@@ -215,16 +215,22 @@ protected:
   nsresult FetchSize(nsIMsgDBHdr * aHdr, nsAString & aSizeString);
   nsresult FetchPriority(nsIMsgDBHdr *aHdr, nsAString & aPriorityString);
   nsresult FetchLabel(nsIMsgDBHdr *aHdr, nsAString & aLabelString);
   nsresult FetchTags(nsIMsgDBHdr *aHdr, nsAString & aTagString);
   nsresult FetchKeywords(nsIMsgDBHdr *aHdr, nsACString & keywordString);
   nsresult FetchAccount(nsIMsgDBHdr * aHdr, nsAString& aAccount);
   nsresult CycleThreadedColumn(nsIDOMElement * aElement);
 
+  // The default enumerator is over the db, but things like
+  // quick search views will enumerate just the displayed messages.
+  virtual nsresult GetMessageEnumerator(nsISimpleEnumerator **enumerator);
+  // this is a message enumerator that enumerates based on the view contents
+  virtual nsresult GetViewEnumerator(nsISimpleEnumerator **enumerator);
+
   // Save and Restore Selection are a pair of routines you should
   // use when performing an operation which is going to change the view
   // and you want to remember the selection. (i.e. for sorting). 
   // Call SaveAndClearSelection and we'll give you an array of msg keys for
   // the current selection. We also freeze and clear the selection. 
   // When you are done changing the view, 
   // call RestoreSelection passing in the same array
   // and we'll restore the selection AND unfreeze selection in the UI.
@@ -241,41 +247,47 @@ protected:
   nsresult GenerateURIForMsgKey(nsMsgKey aMsgKey, nsIMsgFolder *folder, nsACString &aURI);
 // routines used in building up view
   virtual PRBool WantsThisThread(nsIMsgThread * thread);
   virtual nsresult AddHdr(nsIMsgDBHdr *msgHdr, nsMsgViewIndex *resultIndex = nsnull);
   PRBool GetShowingIgnored() {return (m_viewFlags & nsMsgViewFlagsType::kShowIgnored) != 0;}
   virtual nsresult OnNewHeader(nsIMsgDBHdr *aNewHdr, nsMsgKey parentKey, PRBool ensureListed);
   virtual nsMsgViewIndex GetInsertIndex(nsIMsgDBHdr *msgHdr);
   nsMsgViewIndex GetIndexForThread(nsIMsgDBHdr *hdr);
+  nsMsgViewIndex GetThreadRootIndex(nsIMsgDBHdr *msgHdr);
   virtual nsresult GetThreadContainingIndex(nsMsgViewIndex index, nsIMsgThread **thread);
   virtual nsresult GetMsgHdrForViewIndex(nsMsgViewIndex index, nsIMsgDBHdr **msgHdr);
-  nsMsgViewIndex FindIndexForThread(nsIMsgDBHdr *msgHdr, PRBool newThread);
+  // given a view index, return the index of the top-level msg in the thread.
+  nsMsgViewIndex GetThreadIndex(nsMsgViewIndex msgIndex);
 
   virtual void InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr,
                               nsMsgKey msgKey, PRUint32 flags, PRUint32 level);
   virtual void SetMsgHdrAt(nsIMsgDBHdr *hdr, nsMsgViewIndex index, 
                               nsMsgKey msgKey, PRUint32 flags, PRUint32 level);
   virtual PRBool InsertEmptyRows(nsMsgViewIndex viewIndex, PRInt32 numRows);
   virtual void RemoveRows(nsMsgViewIndex viewIndex, PRInt32 numRows);
   nsresult ToggleExpansion(nsMsgViewIndex index, PRUint32 *numChanged);
   nsresult ExpandByIndex(nsMsgViewIndex index, PRUint32 *pNumExpanded);
   nsresult CollapseByIndex(nsMsgViewIndex index, PRUint32 *pNumCollapsed);
   nsresult ExpandAll();
   nsresult CollapseAll();
   nsresult ExpandAndSelectThread();
 
   // helper routines for thread expanding and collapsing.
-  nsresult		GetThreadCount(nsMsgKey messageKey, PRUint32 *pThreadCount);
+  nsresult GetThreadCount(nsMsgViewIndex viewIndex, PRUint32 *pThreadCount);
   nsMsgViewIndex GetIndexOfFirstDisplayedKeyInThread(nsIMsgThread *threadHdr);
   virtual nsresult GetFirstMessageHdrToDisplayInThread(nsIMsgThread *threadHdr, nsIMsgDBHdr **result);
   virtual nsMsgViewIndex ThreadIndexOfMsg(nsMsgKey msgKey, 
 				  nsMsgViewIndex msgIndex = nsMsgViewIndex_None,
 				  PRInt32 *pThreadCount = nsnull,
 				  PRUint32 *pFlags = nsnull);
+  nsMsgViewIndex ThreadIndexOfMsgHdr(nsIMsgDBHdr *msgHdr, 
+                                 nsMsgViewIndex msgIndex = nsMsgViewIndex_None,
+                                 PRInt32 *pThreadCount = nsnull,
+                                 PRUint32 *pFlags = nsnull);
   virtual nsresult GetThreadContainingMsgHdr(nsIMsgDBHdr *msgHdr, nsIMsgThread **pThread);
   nsMsgKey GetKeyOfFirstMsgInThread(nsMsgKey key);
   PRInt32 CountExpandedThread(nsMsgViewIndex index);
   virtual  nsresult ExpansionDelta(nsMsgViewIndex index, PRInt32 *expansionDelta);
   void ReverseSort();
   void ReverseThreads();
   nsresult SaveSortInfo(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder);
   nsresult PersistFolderInfo(nsIDBFolderInfo **dbFolderInfo);
@@ -457,24 +469,43 @@ protected:
   
   //these hold pointers (and IDs) for the nsIMsgCustomColumnHandler object that constitutes the custom column handler
   nsCOMArray <nsIMsgCustomColumnHandler> m_customColumnHandlers;
   nsStringArray m_customColumnHandlerIDs;
   
   nsIMsgCustomColumnHandler* GetColumnHandler(const PRUnichar*);
   nsIMsgCustomColumnHandler* GetCurColumnHandlerFromDBInfo();
   void GetCurCustomColumn(nsString &colID);
+#ifdef DEBUG_David_Bienvenu
+void InitEntryInfoForIndex(nsMsgViewIndex i, IdKeyPtr &EntryInfo);
+void ValidateSort();
+#endif
 
 protected:
   static nsresult   InitDisplayFormats();
 
 private:
   static nsDateFormatSelector  m_dateFormatDefault;
   static nsDateFormatSelector  m_dateFormatThisWeek;
   static nsDateFormatSelector  m_dateFormatToday;
   PRBool ServerSupportsFilterAfterTheFact();
 
   nsresult PerformActionsOnJunkMsgs();
   nsresult DetermineActionsForJunkMsgs(PRBool* movingJunkMessages, PRBool* markingJunkMessagesRead, nsIMsgFolder** junkTargetFolder);
 
+  class nsMsgViewHdrEnumerator : public nsISimpleEnumerator 
+  {
+  public:
+    NS_DECL_ISUPPORTS
+
+    // nsISimpleEnumerator methods:
+    NS_DECL_NSISIMPLEENUMERATOR
+
+    // nsMsgThreadEnumerator methods:
+    nsMsgViewHdrEnumerator(nsMsgDBView *view);
+    ~nsMsgViewHdrEnumerator();
+
+    nsRefPtr <nsMsgDBView> m_view;
+    nsMsgViewIndex m_curHdrIndex;
+  };
 };
 
 #endif
--- a/mailnews/base/src/nsMsgGroupThread.cpp
+++ b/mailnews/base/src/nsMsgGroupThread.cpp
@@ -1,9 +1,9 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
@@ -127,52 +127,16 @@ NS_IMETHODIMP nsMsgGroupThread::GetNumUn
 
 void nsMsgGroupThread::InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr)
 {
   nsMsgKey msgKey;
   hdr->GetMessageKey(&msgKey);
   m_keys.InsertElementAt(index, msgKey);
 }
 
-#if 0
-nsresult nsMsgGroupThread::RerootThread(nsIMsgDBHdr *newParentOfOldRoot, nsIMsgDBHdr *oldRoot, nsIDBChangeAnnouncer *announcer)
-{
-  nsCOMPtr <nsIMsgDBHdr> ancestorHdr = newParentOfOldRoot;
-  nsMsgKey newRoot;
-  newParentOfOldRoot->GetMessageKey(&newRoot);
-
-  nsMsgKey newHdrAncestor;
-  nsresult rv = NS_OK;
-  // loop trying to find the oldest ancestor of this msg
-  // that is a parent of the root. The oldest ancestor will
-  // become the root of the thread.
-  do 
-  {
-    ancestorHdr->GetThreadParent(&newHdrAncestor);
-    if (newHdrAncestor != nsMsgKey_None && newHdrAncestor != m_threadRootKey && newHdrAncestor != newRoot)
-    {
-      newRoot = newHdrAncestor;
-      rv = m_db->GetMsgHdrForKey(newRoot, getter_AddRefs(ancestorHdr));
-    }
-  }
-  while (NS_SUCCEEDED(rv) && ancestorHdr && newHdrAncestor != nsMsgKey_None && newHdrAncestor != m_threadRootKey
-    && newHdrAncestor != newRoot);
-  m_threadRootKey = newRoot;
-//  ReparentNonReferenceChildrenOf(oldRoot, newRoot, announcer);
-  if (ancestorHdr)
-  {
-    // move the  root hdr to pos 0 by removing it and adding it at 0.
-    m_keys.RemoveElement(newRoot);
-    m_keys.InsertElementAt(0, newRoot);
-    ancestorHdr->SetThreadParent(nsMsgKey_None);
-  }
-  return rv;
-}
-#endif
-
 NS_IMETHODIMP nsMsgGroupThread::AddChild(nsIMsgDBHdr *child, nsIMsgDBHdr *inReplyTo, PRBool threadInThread, 
                                     nsIDBChangeAnnouncer *announcer)
 {
   NS_ASSERTION(PR_FALSE, "shouldn't call this");
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 nsMsgViewIndex nsMsgGroupThread::AddMsgHdrInDateOrder(nsIMsgDBHdr *child, nsMsgDBView *view)
@@ -235,45 +199,16 @@ nsresult nsMsgGroupThread::AddChildFromG
     ChangeUnreadChildCount(1);
 
   return AddMsgHdrInDateOrder(child, view);
 }
 
 nsresult nsMsgGroupThread::ReparentNonReferenceChildrenOf(nsIMsgDBHdr *topLevelHdr, nsMsgKey newParentKey,
                                                             nsIDBChangeAnnouncer *announcer)
 {
-#if 0
-  nsCOMPtr <nsIMsgDBHdr> curHdr;
-  PRUint32 numChildren;
-  PRUint32 childIndex = 0;
-  
-  GetNumChildren(&numChildren);
-  for (childIndex = 0; childIndex < numChildren; childIndex++)
-  {
-    nsMsgKey msgKey;
-    
-    topLevelHdr->GetMessageKey(&msgKey);
-    nsresult ret = GetChildHdrAt(childIndex, getter_AddRefs(curHdr));
-    if (NS_SUCCEEDED(ret) && curHdr)
-    {
-      nsMsgKey oldThreadParent, curHdrKey;
-      nsIMsgDBHdr *curMsgHdr = curHdr;
-      curHdr->GetThreadParent(&oldThreadParent);
-      curHdr->GetMessageKey(&curHdrKey);
-      if (oldThreadParent == msgKey && curHdrKey != newParentKey && topLevelMsgHdr->IsParentOf(curHdr))
-      {
-        curHdr->GetThreadParent(&oldThreadParent);
-        curHdr->SetThreadParent(newParentKey);
-        // OK, this is a reparenting - need to send notification
-        if (announcer)
-          announcer->NotifyParentChangedAll(curHdrKey, oldThreadParent, newParentKey, nsnull);
-      }
-    }
-  }
-#endif
   return NS_OK;
 }
 
 NS_IMETHODIMP nsMsgGroupThread::GetChildKeyAt(PRInt32 aIndex, nsMsgKey *aResult)
 {
   NS_ENSURE_ARG_POINTER(aResult);
   if (aIndex >= m_keys.Length())
     return NS_ERROR_INVALID_ARG;
--- a/mailnews/base/src/nsMsgGroupThread.h
+++ b/mailnews/base/src/nsMsgGroupThread.h
@@ -1,9 +1,9 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
--- a/mailnews/base/src/nsMsgGroupView.cpp
+++ b/mailnews/base/src/nsMsgGroupView.cpp
@@ -1,9 +1,9 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
@@ -74,16 +74,20 @@ NS_IMETHODIMP nsMsgGroupView::Open(nsIMs
   rv = m_db->EnumerateMessages(getter_AddRefs(headers));
   NS_ENSURE_SUCCESS(rv, rv);
 
   return OpenWithHdrs(headers, aSortType, aSortOrder, aViewFlags, aCount);
 }
 
 void nsMsgGroupView::InternalClose()
 {
+  // nothing to do if we're not grouped.
+  if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
+    return;
+
   PRBool rcvDate = PR_FALSE;
 
   if (m_sortType == nsMsgViewSortType::byReceived)
     rcvDate = PR_TRUE;
   if (m_db && (m_sortType == nsMsgViewSortType::byDate) || (m_sortType == nsMsgViewSortType::byReceived))
   {
     nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
     m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
@@ -111,17 +115,17 @@ void nsMsgGroupView::InternalClose()
     }
   }
   m_groupsTable.Clear();
 }
 
 NS_IMETHODIMP nsMsgGroupView::Close()
 {
   InternalClose();
-  return nsMsgThreadedDBView::Close();
+  return nsMsgDBView::Close();
 }
 
 // Set rcvDate to PR_TRUE to get the Received: date instead of the Date: date.
 nsresult nsMsgGroupView::GetAgeBucketValue(nsIMsgDBHdr *aMsgHdr, PRUint32 * aAgeBucket, PRBool rcvDate)
 {
   NS_ENSURE_ARG_POINTER(aMsgHdr);
   NS_ENSURE_ARG_POINTER(aAgeBucket);
 
@@ -284,16 +288,21 @@ nsresult nsMsgGroupView::HashHdr(nsIMsgD
     }
     default:
       NS_ASSERTION(PR_FALSE, "no hash key for this type");
       rv = NS_ERROR_FAILURE;
   }
   return rv;
 }
 
+nsMsgGroupThread *nsMsgGroupView::CreateGroupThread(nsIMsgDatabase *db)
+{
+  return new nsMsgGroupThread(db);
+}
+
 nsMsgGroupThread *nsMsgGroupView::AddHdrToThread(nsIMsgDBHdr *msgHdr, PRBool *pNewThread)
 {
   nsMsgKey msgKey;
   PRUint32 msgFlags;
   msgHdr->GetMessageKey(&msgKey);
   msgHdr->GetFlags(&msgFlags);
   nsString hashKey;
   nsresult rv = HashHdr(msgHdr, hashKey);
@@ -306,35 +315,35 @@ nsMsgGroupThread *nsMsgGroupView::AddHdr
   m_groupsTable.Get(hashKey, getter_AddRefs(msgThread));
   PRBool newThread = !msgThread;
   *pNewThread = newThread;
   nsMsgViewIndex viewIndexOfThread;
 
   nsMsgGroupThread *foundThread = static_cast<nsMsgGroupThread *>(msgThread.get());
   if (!foundThread)
   {
-    foundThread = new nsMsgGroupThread(m_db);
+    foundThread = CreateGroupThread(m_db);
     msgThread = do_QueryInterface(foundThread);
     m_groupsTable.Put(hashKey, msgThread);
     if (GroupViewUsesDummyRow())
     {
       foundThread->m_dummy = PR_TRUE;
       msgFlags |=  MSG_VIEW_FLAG_DUMMY | MSG_VIEW_FLAG_HASCHILDREN;
     }
 
     nsMsgViewIndex insertIndex = GetInsertIndex(msgHdr);
     if (insertIndex == nsMsgViewIndex_None)
       insertIndex = m_keys.Length();
-    m_keys.InsertElementAt(insertIndex, msgKey);
-    m_flags.InsertElementAt(insertIndex, msgFlags | MSG_VIEW_FLAG_ISTHREAD | MSG_FLAG_ELIDED);
-    m_levels.InsertElementAt(insertIndex, 0);
+    InsertMsgHdrAt(insertIndex, msgHdr, msgKey, 
+                  msgFlags | MSG_VIEW_FLAG_ISTHREAD | MSG_FLAG_ELIDED, 0);
     // if grouped by date, insert dummy header for "age"
     if (GroupViewUsesDummyRow())
     {
-      foundThread->m_keys.InsertElementAt(0, msgKey /* nsMsgKey_None */);
+      // this needs to do something different for xf groups
+      foundThread->InsertMsgHdrAt(0, msgHdr);
       // the previous code made it look like hashKey in this case was always an integer
       foundThread->m_threadKey = atoi(NS_LossyConvertUTF16toASCII(hashKey).get());
     }
   }
   else
     viewIndexOfThread = GetIndexOfFirstDisplayedKeyInThread(foundThread);
   if (foundThread)
     foundThread->AddChildFromGroupView(msgHdr, this);
@@ -351,16 +360,17 @@ nsMsgGroupThread *nsMsgGroupView::AddHdr
 }
 
 NS_IMETHODIMP nsMsgGroupView::OpenWithHdrs(nsISimpleEnumerator *aHeaders, nsMsgViewSortTypeValue aSortType,
                                         nsMsgViewSortOrderValue aSortOrder, nsMsgViewFlagsTypeValue aViewFlags,
                                         PRInt32 *aCount)
 {
   nsresult rv = NS_OK;
 
+  m_groupsTable.Clear();
   if (aSortType == nsMsgViewSortType::byThread || aSortType == nsMsgViewSortType::byId
     || aSortType == nsMsgViewSortType::byNone || aSortType == nsMsgViewSortType::bySize)
     return NS_ERROR_INVALID_ARG;
 
   m_sortType = aSortType;
   m_sortOrder = aSortOrder;
   m_viewFlags = aViewFlags | nsMsgViewFlagsType::kThreadedDisplay | nsMsgViewFlagsType::kGroupBySort;
 
@@ -411,21 +421,31 @@ NS_IMETHODIMP nsMsgGroupView::OpenWithHd
         }
       }
     }
   }
   *aCount = m_keys.Length();
   return rv;
 }
 
-// if the day has changed, we need to close and re-open the view.
-nsresult nsMsgGroupView::HandleDayChange()
+// we wouldn't need this if we never instantiated this directly,
+// but instead used nsMsgThreadedDBView with the grouping flag set.
+// Or, we could get rid of the nsMsgThreadedDBView impl of this method.
+NS_IMETHODIMP nsMsgGroupView::GetViewType(nsMsgViewTypeValue *aViewType)
+{
+    NS_ENSURE_ARG_POINTER(aViewType);
+    *aViewType = nsMsgViewType::eShowAllThreads; 
+    return NS_OK;
+}
+
+// E.g., if the day has changed, we need to close and re-open the view.
+nsresult nsMsgGroupView::RebuildView()
 {
   nsCOMPtr <nsISimpleEnumerator> headers;
-  if (NS_SUCCEEDED(m_db->EnumerateMessages(getter_AddRefs(headers))))
+  if (NS_SUCCEEDED(GetMessageEnumerator(getter_AddRefs(headers))))
   {
     PRInt32 count;
     m_dayChanged = PR_FALSE;
     nsAutoTArray<nsMsgKey, 1> preservedSelection;
     nsMsgKey curSelectedKey;
     SaveAndClearSelection(&curSelectedKey, preservedSelection);
     InternalClose();
     PRInt32 oldSize = GetSize();
@@ -451,34 +471,36 @@ nsresult nsMsgGroupView::HandleDayChange
     nsAutoTArray<nsMsgKey, 1> keyArray;
     keyArray.AppendElement(curSelectedKey);
 
     return RestoreSelection(curSelectedKey, keyArray);
   }
   return NS_OK;
 }
 
-nsresult nsMsgGroupView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, PRBool /*ensureListed*/)
+nsresult nsMsgGroupView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, PRBool ensureListed)
 {
+  if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
+    return nsMsgDBView::OnNewHeader(newHdr, aParentKey, ensureListed);
 
   // check if we're adding a header, and the current day has changed. If it has, we're just going to
   // close and re-open the view so things will be correctly categorized.
   if (m_dayChanged)
-    return HandleDayChange();
+    return RebuildView();
 
   PRBool newThread;
   nsMsgGroupThread *thread = AddHdrToThread(newHdr, &newThread);
   if (thread)
   {
     nsMsgKey msgKey;
     PRUint32 msgFlags;
     newHdr->GetMessageKey(&msgKey);
     newHdr->GetFlags(&msgFlags);
 
-    nsMsgViewIndex threadIndex = ThreadIndexOfMsg(msgKey);
+    nsMsgViewIndex threadIndex = ThreadIndexOfMsgHdr(newHdr);
     PRInt32 numRowsInserted = 1;
     if (newThread && GroupViewUsesDummyRow())
       numRowsInserted++;
     // may need to fix thread counts
     if (threadIndex != nsMsgViewIndex_None)
     {
       if (newThread)
         m_flags[threadIndex] &= ~MSG_FLAG_ELIDED;
@@ -506,27 +528,21 @@ nsresult nsMsgGroupView::OnNewHeader(nsI
 
             m_flags[threadIndex + msgIndexInThread] = msgFlags;
             // this will cause us to insert the old header as the first child, with
             // the right key and flags.
             msgFlags = saveOldFlags;
             msgIndexInThread++;
             msgKey = thread->m_keys[msgIndexInThread];
           }
-
-          m_keys.InsertElementAt(threadIndex + msgIndexInThread, msgKey);
-          m_flags.InsertElementAt(threadIndex + msgIndexInThread, msgFlags);
-          if (msgIndexInThread > 0)
-          {
-            m_levels.InsertElementAt(threadIndex + msgIndexInThread, 1);
-          }
-          else // insert new header at level 0, and bump old level 0 to 1
-          {
-            m_levels.InsertElementAt(threadIndex + 1, 1);
-          }
+          InsertMsgHdrAt(threadIndex + msgIndexInThread, newHdr, msgKey,
+                         msgFlags, msgIndexInThread ? 1 : 0);
+          // if we inserted new header at level 0, bump old level 0 to 1
+          if (!msgIndexInThread)
+            m_levels[threadIndex + 1] = 1;
         }
         // the call to NoteChange() has to happen after we add the key
         // as NoteChange() will call RowCountChanged() which will call our GetRowCount()
         NoteChange((insertedAtThreadRoot && GroupViewUsesDummyRow()) ? threadIndex + msgIndexInThread - 1 : threadIndex + msgIndexInThread,
                       numRowsInserted, nsMsgViewNotificationCode::insertOrDelete);
         numRowsToInvalidate = msgIndexInThread;
       }
       NoteChange(threadIndex, numRowsToInvalidate, nsMsgViewNotificationCode::changed);
@@ -534,39 +550,46 @@ nsresult nsMsgGroupView::OnNewHeader(nsI
   }
   // if thread is expanded, we need to add hdr to view...
   return NS_OK;
 }
 
 NS_IMETHODIMP nsMsgGroupView::OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, PRUint32 aOldFlags,
                                       PRUint32 aNewFlags, nsIDBChangeListener *aInstigator)
 {
+  if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
+    return nsMsgDBView::OnHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags,
+                                          aInstigator);
+
   nsCOMPtr <nsIMsgThread> thread;
 
   // check if we're adding a header, and the current day has changed. If it has, we're just going to
   // close and re-open the view so things will be correctly categorized.
   if (m_dayChanged)
-    return HandleDayChange();
+    return RebuildView();
 
   nsresult rv = GetThreadContainingMsgHdr(aHdrChanged, getter_AddRefs(thread));
   NS_ENSURE_SUCCESS(rv, rv);
   PRUint32 deltaFlags = (aOldFlags ^ aNewFlags);
   if (deltaFlags & MSG_FLAG_READ)
     thread->MarkChildRead(aNewFlags & MSG_FLAG_READ);
 
   return nsMsgDBView::OnHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags, aInstigator);
 }
 
 NS_IMETHODIMP nsMsgGroupView::OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted, nsMsgKey aParentKey, PRInt32 aFlags,
                             nsIDBChangeListener *aInstigator)
 {
+  if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
+    return nsMsgDBView::OnHdrDeleted(aHdrDeleted, aParentKey, aFlags, aInstigator);
+
   // check if we're adding a header, and the current day has changed. If it has, we're just going to
   // close and re-open the view so things will be correctly categorized.
   if (m_dayChanged)
-    return HandleDayChange();
+    return RebuildView();
 
   nsCOMPtr <nsIMsgThread> thread;
   nsMsgKey keyDeleted;
   aHdrDeleted->GetMessageKey(&keyDeleted);
 
   nsresult rv = GetThreadContainingMsgHdr(aHdrDeleted, getter_AddRefs(thread));
   NS_ENSURE_SUCCESS(rv, rv);
   nsMsgViewIndex viewIndexOfThread = GetIndexOfFirstDisplayedKeyInThread(thread);
@@ -579,23 +602,24 @@ NS_IMETHODIMP nsMsgGroupView::OnHdrDelet
   rv = nsMsgDBView::OnHdrDeleted(aHdrDeleted, aParentKey, aFlags, aInstigator);
   if (groupThread->m_dummy)
   {
     if (!groupThread->NumRealChildren())
     {
       thread->RemoveChildAt(0); // get rid of dummy
       if (viewIndexOfThread != nsMsgKey_None)
       {
-        nsMsgDBView::RemoveByIndex(viewIndexOfThread - 1);
+        RemoveByIndex(viewIndexOfThread - 1);
         if (m_deletingRows)
           mIndicesToNoteChange.AppendElement(viewIndexOfThread - 1);
       }
     }
     else if (rootDeleted && viewIndexOfThread > 0)
     {
+      // ### what about cross-folder views?
       m_keys[viewIndexOfThread - 1] = m_keys[viewIndexOfThread];
       OrExtraFlag(viewIndexOfThread - 1, MSG_VIEW_FLAG_DUMMY | MSG_VIEW_FLAG_ISTHREAD);
     }
   }
   if (!groupThread->m_keys.Length())
   {
     nsString hashKey;
     rv = HashHdr(aHdrDeleted, hashKey);
@@ -765,17 +789,16 @@ NS_IMETHODIMP nsMsgGroupView::GetCellTex
     }
     return NS_OK;
   }
   return nsMsgDBView::GetCellText(aRow, aCol, aValue);
 }
 
 NS_IMETHODIMP nsMsgGroupView::LoadMessageByViewIndex(nsMsgViewIndex aViewIndex)
 {
-
   if (m_flags[aViewIndex] & MSG_VIEW_FLAG_DUMMY)
   {
     // if we used to have one item selected, and now we have more than one, we should clear the message pane.
     nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak));
     nsCOMPtr <nsIMsgWindowCommands> windowCommands;
     if (msgWindow && NS_SUCCEEDED(msgWindow->GetWindowCommands(getter_AddRefs(windowCommands))) && windowCommands)
       windowCommands->ClearMsgPane();
     // since we are selecting a dummy row, we should also clear out m_currentlyDisplayedMsgUri
@@ -783,16 +806,19 @@ NS_IMETHODIMP nsMsgGroupView::LoadMessag
     return NS_OK;
   }
   else
     return nsMsgDBView::LoadMessageByViewIndex(aViewIndex);
 }
 
 nsresult nsMsgGroupView::GetThreadContainingMsgHdr(nsIMsgDBHdr *msgHdr, nsIMsgThread **pThread)
 {
+  if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
+    return nsMsgDBView::GetThreadContainingMsgHdr(msgHdr, pThread);
+
   nsString hashKey;
   nsresult rv = HashHdr(msgHdr, hashKey);
   *pThread = nsnull;
   if (NS_SUCCEEDED(rv))
   {
     nsCOMPtr<nsIMsgThread> thread;
     m_groupsTable.Get(hashKey, getter_AddRefs(thread));
     thread.swap(*pThread);
--- a/mailnews/base/src/nsMsgGroupView.h
+++ b/mailnews/base/src/nsMsgGroupView.h
@@ -1,9 +1,9 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
@@ -31,32 +31,39 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
-#include "nsMsgThreadedDBView.h"
+#ifndef _nsMsgGroupView_H_
+#define _nsMsgGroupView_H_
+
+#include "nsMsgDBView.h"
 #include "nsInterfaceHashtable.h"
 
 class nsIMsgThread;
 class nsMsgGroupThread;
 
-class nsMsgGroupView : public nsMsgThreadedDBView
+// Please note that if you override a method of nsMsgDBView,
+// you will most likely want to check the m_viewFlags to see if
+// we're grouping, and if not, call the base class implementation.
+class nsMsgGroupView : public nsMsgDBView
 {
 public:
   nsMsgGroupView();
   virtual ~nsMsgGroupView();
 
   NS_IMETHOD Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, nsMsgViewFlagsTypeValue viewFlags, PRInt32 *pCount);
   NS_IMETHOD OpenWithHdrs(nsISimpleEnumerator *aHeaders, nsMsgViewSortTypeValue aSortType, 
                                         nsMsgViewSortOrderValue aSortOrder, nsMsgViewFlagsTypeValue aViewFlags, 
                                         PRInt32 *aCount);
+  NS_IMETHOD GetViewType(nsMsgViewTypeValue *aViewType);
   NS_IMETHOD Close();
   NS_IMETHOD OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted, nsMsgKey aParentKey, PRInt32 aFlags, 
                             nsIDBChangeListener *aInstigator);
   NS_IMETHOD OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, PRUint32 aOldFlags, 
                                       PRUint32 aNewFlags, nsIDBChangeListener *aInstigator);
 
   NS_IMETHOD LoadMessageByViewIndex(nsMsgViewIndex aViewIndex);
   NS_IMETHOD GetCellProperties(PRInt32 aRow, nsITreeColumn *aCol, nsISupportsArray *aProperties);
@@ -72,22 +79,25 @@ protected:
   virtual nsresult GetThreadContainingMsgHdr(nsIMsgDBHdr *msgHdr, nsIMsgThread **pThread);
   virtual PRInt32 FindLevelInThread(nsIMsgDBHdr *msgHdr, nsMsgViewIndex startOfThread, nsMsgViewIndex viewIndex);
   nsMsgViewIndex ThreadIndexOfMsg(nsMsgKey msgKey, 
                                             nsMsgViewIndex msgIndex = nsMsgViewIndex_None,
                                             PRInt32 *pThreadCount = NULL,
                                             PRUint32 *pFlags = NULL);
 
   PRBool GroupViewUsesDummyRow(); // returns true if we are grouped by a sort attribute that uses a dummy row
-  nsresult HandleDayChange();
+  virtual nsresult RebuildView();
+  virtual nsMsgGroupThread *CreateGroupThread(nsIMsgDatabase *db);
 
   nsInterfaceHashtable <nsStringHashKey, nsIMsgThread> m_groupsTable;
   PRExplodedTime m_lastCurExplodedTime;
   PRBool m_dayChanged;
 
 private:
   nsString m_kTodayString;
   nsString m_kYesterdayString;
   nsString m_kLastWeekString;
   nsString m_kTwoWeeksAgoString;
   nsString m_kOldMailString;
 };
 
+#endif
+
--- a/mailnews/base/src/nsMsgQuickSearchDBView.cpp
+++ b/mailnews/base/src/nsMsgQuickSearchDBView.cpp
@@ -1,9 +1,9 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
@@ -40,16 +40,17 @@
 #include "nsMsgQuickSearchDBView.h"
 #include "nsMsgFolderFlags.h"
 #include "nsIMsgHdr.h"
 #include "nsMsgBaseCID.h"
 #include "nsIMsgImapMailFolder.h"
 #include "nsImapCore.h"
 #include "nsIMsgHdr.h"
 #include "nsIDBFolderInfo.h"
+#include "nsArrayEnumerator.h"
 
 nsMsgQuickSearchDBView::nsMsgQuickSearchDBView()
 {
   m_usingCachedHits = PR_FALSE;
   m_cacheEmpty = PR_TRUE;
 }
 
 nsMsgQuickSearchDBView::~nsMsgQuickSearchDBView()
@@ -115,16 +116,23 @@ NS_IMETHODIMP nsMsgQuickSearchDBView::Do
 
 NS_IMETHODIMP nsMsgQuickSearchDBView::GetViewType(nsMsgViewTypeValue *aViewType)
 {
     NS_ENSURE_ARG_POINTER(aViewType);
     *aViewType = nsMsgViewType::eShowQuickSearchResults; 
     return NS_OK;
 }
 
+nsresult nsMsgQuickSearchDBView::AddHdr(nsIMsgDBHdr *msgHdr, nsMsgViewIndex *resultIndex)
+{
+  return (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+          ? nsMsgGroupView::OnNewHeader(msgHdr, nsMsgKey_None, PR_TRUE)
+          : nsMsgDBView::AddHdr(msgHdr, resultIndex);
+}
+
 nsresult nsMsgQuickSearchDBView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, PRBool ensureListed)
 {
   if (newHdr)
   {
     PRBool match=PR_FALSE;
     nsCOMPtr <nsIMsgSearchSession> searchSession = do_QueryReferent(m_searchSession);
     if (searchSession)
       searchSession->MatchHdr(newHdr, m_db, &match);
@@ -141,17 +149,17 @@ nsresult nsMsgQuickSearchDBView::OnNewHe
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP nsMsgQuickSearchDBView::OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, PRUint32 aOldFlags, 
                                        PRUint32 aNewFlags, nsIDBChangeListener *aInstigator)
 {
-  nsresult rv = nsMsgDBView::OnHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags, aInstigator);
+  nsresult rv = nsMsgGroupView::OnHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags, aInstigator);
 
   if (m_viewFolder && (aOldFlags & MSG_FLAG_READ) != (aNewFlags & MSG_FLAG_READ))
   {
     // if we're displaying a single folder virtual folder for an imap folder,
     // the search criteria might be on message body, and we might not have the
     // message body offline, in which case we can't tell if the message 
     // matched or not. But if the unread flag changed, we need to update the
     // unread counts. Normally, VirtualFolderChangeListener::OnHdrFlagsChanged will
@@ -258,20 +266,25 @@ nsMsgQuickSearchDBView::OnSearchHit(nsIM
   NS_ENSURE_ARG(aMsgHdr);
   if (!m_db)
     return NS_ERROR_NULL_POINTER;
   // remember search hit and when search is done, reconcile cache
   // with new hits;
   m_hdrHits.AppendObject(aMsgHdr);
   nsMsgKey key;
   aMsgHdr->GetMessageKey(&key);
+  // put the new header in m_origKeys, so that expanding a thread will
+  // show the newly added header.
+  nsMsgViewIndex insertIndex = GetInsertIndexHelper(aMsgHdr, m_origKeys, nsnull,
+                  nsMsgViewSortOrder::ascending, nsMsgViewSortType::byId);
+  m_origKeys.InsertElementAt(insertIndex, key);
   // is FindKey going to be expensive here? A lot of hits could make
   // it a little bit slow to search through the view for every hit.
   if (m_cacheEmpty || FindKey(key, PR_FALSE) == nsMsgViewIndex_None)
-  return AddHdr(aMsgHdr); 
+    return AddHdr(aMsgHdr); 
   else
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMsgQuickSearchDBView::OnSearchDone(nsresult status)
 {
   if (m_viewFolder)
@@ -307,17 +320,16 @@ nsMsgQuickSearchDBView::OnSearchDone(nsr
   if (m_sortType != nsMsgViewSortType::byThread)//we do not find levels for the results.
   {
     m_sortValid = PR_FALSE;       //sort the results 
     Sort(m_sortType, m_sortOrder);
   }
   if (m_viewFolder)
     SetMRUTimeForFolder(m_viewFolder);
 
-  m_hdrHits.Clear();
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsMsgQuickSearchDBView::OnNewSearch()
 {
   PRInt32 oldSize = GetSize();
@@ -430,20 +442,19 @@ nsresult nsMsgQuickSearchDBView::GetFirs
     }
   }
   NS_IF_ADDREF(*result = retHdr);
   return NS_OK; 
 }
 
 nsresult nsMsgQuickSearchDBView::SortThreads(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder)
 {
-  // we don't handle grouping in quick search views yet.
+  // don't need to sort by threads for group view.
   if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
     return NS_OK;
-
   // iterate over the messages in the view, getting the thread id's
   // sort m_keys so we can quickly find if a key is in the view. 
   m_keys.Sort();
   // array of the threads' root hdr keys.
   nsTArray<nsMsgKey> threadRootIds;
   nsCOMPtr <nsIMsgDBHdr> rootHdr;
   nsCOMPtr <nsIMsgDBHdr> msgHdr;
   nsCOMPtr <nsIMsgThread> threadHdr;
@@ -519,46 +530,47 @@ nsresult  nsMsgQuickSearchDBView::ListId
   PRUint32 i;
   PRUint32 viewIndex = startOfThreadViewIndex + 1;
   nsCOMPtr <nsIMsgDBHdr> rootHdr;
   nsMsgKey rootKey;
   PRUint32 rootFlags = m_flags[startOfThreadViewIndex];
   *pNumListed = 0;
   GetMsgHdrForViewIndex(startOfThreadViewIndex, getter_AddRefs(rootHdr));
   rootHdr->GetMessageKey(&rootKey);
+  // group threads can have the root key twice, one for the dummy row.
+  PRBool rootKeySkipped = PR_FALSE;
   for (i = 0; i < numChildren; i++)
   {
     nsCOMPtr <nsIMsgDBHdr> msgHdr;
     threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr));
     if (msgHdr != nsnull)
     {
       nsMsgKey msgKey;
       msgHdr->GetMessageKey(&msgKey);
-      if (msgKey != rootKey)
+      if (msgKey != rootKey || (GroupViewUsesDummyRow() && rootKeySkipped))
       {
         nsMsgViewIndex threadRootIndex = m_origKeys.BinaryIndexOf(msgKey);
         // if this hdr is in the original view, add it to new view.
         if (threadRootIndex != kNotFound)
         {
           PRUint32 childFlags;
           msgHdr->GetFlags(&childFlags);
-          PRUint8 levelToAdd;
-          m_keys.InsertElementAt(viewIndex, msgKey);
-          m_flags.InsertElementAt(viewIndex, childFlags);
+          InsertMsgHdrAt(viewIndex, msgHdr, msgKey, childFlags, 
+                        FindLevelInThread(msgHdr, startOfThreadViewIndex, viewIndex));
           if (! (rootFlags & MSG_VIEW_FLAG_HASCHILDREN))
-          {
-            rootFlags |= MSG_VIEW_FLAG_HASCHILDREN;
-            m_flags[startOfThreadViewIndex] = rootFlags;
-          }
-          levelToAdd = FindLevelInThread(msgHdr, startOfThreadViewIndex, viewIndex);
-          m_levels.InsertElementAt(viewIndex, levelToAdd);
+            m_flags[startOfThreadViewIndex] = rootFlags | MSG_VIEW_FLAG_HASCHILDREN;
+
           viewIndex++;
           (*pNumListed)++;
         }
       }
+      else
+      {
+        rootKeySkipped = PR_TRUE;
+      }
     }
   }
   return NS_OK;
 }
 
 nsresult nsMsgQuickSearchDBView::ExpansionDelta(nsMsgViewIndex index, PRInt32 *expansionDelta)
 {
   *expansionDelta = 0;
@@ -574,8 +586,60 @@ nsresult nsMsgQuickSearchDBView::Expansi
   // in a thread and get the expansion delta for the thread.
 
   PRInt32 numChildren = CountExpandedThread(index);
 
   *expansionDelta = (flags & MSG_FLAG_ELIDED) ? 
                     numChildren - 1 : - (PRInt32) (numChildren - 1);
   return NS_OK;
 }
+
+NS_IMETHODIMP 
+nsMsgQuickSearchDBView::OpenWithHdrs(nsISimpleEnumerator *aHeaders, 
+                                     nsMsgViewSortTypeValue aSortType,
+                                     nsMsgViewSortOrderValue aSortOrder, 
+                                     nsMsgViewFlagsTypeValue aViewFlags,
+                                     PRInt32 *aCount)
+{
+  if (aViewFlags & nsMsgViewFlagsType::kGroupBySort)
+    return nsMsgGroupView::OpenWithHdrs(aHeaders, aSortType, aSortOrder, 
+                                        aViewFlags, aCount);
+
+  m_sortType = aSortType;
+  m_sortOrder = aSortOrder;
+  m_viewFlags = aViewFlags;
+
+  PRBool hasMore;
+  nsCOMPtr<nsISupports> supports;
+  nsCOMPtr<nsIMsgDBHdr> msgHdr;
+  nsresult rv = NS_OK;
+  while (NS_SUCCEEDED(rv = aHeaders->HasMoreElements(&hasMore)) && hasMore)
+  {
+    rv = aHeaders->GetNext(getter_AddRefs(supports));
+    if (NS_SUCCEEDED(rv) && supports)
+    {
+      msgHdr = do_QueryInterface(supports);
+      AddHdr(msgHdr); 
+    }
+    else
+      break;
+  }
+  *aCount = m_keys.Length();
+  return rv;
+}
+
+NS_IMETHODIMP nsMsgQuickSearchDBView::SetViewFlags(nsMsgViewFlagsTypeValue aViewFlags)
+{
+  nsMsgViewFlagsTypeValue saveViewFlags = m_viewFlags;
+  nsresult rv = nsMsgDBView::SetViewFlags(aViewFlags);
+  // if the grouping has changed, rebuild the view
+  if (saveViewFlags & nsMsgViewFlagsType::kGroupBySort ^
+      (aViewFlags & nsMsgViewFlagsType::kGroupBySort))
+    RebuildView();
+
+  return rv;
+}
+
+nsresult 
+nsMsgQuickSearchDBView::GetMessageEnumerator(nsISimpleEnumerator **enumerator)
+{
+  return GetViewEnumerator(enumerator);
+}
--- a/mailnews/base/src/nsMsgQuickSearchDBView.h
+++ b/mailnews/base/src/nsMsgQuickSearchDBView.h
@@ -31,17 +31,17 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
-#ifndef _nsMsgQuickSearchDBViewsH_
+#ifndef _nsMsgQuickSearchDBView_H_
 #define _nsMsgQuickSearchDBView_H_
 
 #include "nsMsgThreadedDBView.h"
 #include "nsIMsgSearchNotify.h"
 #include "nsIMsgSearchSession.h"
 #include "nsCOMArray.h"
 #include "nsIMsgHdr.h"
 
@@ -54,35 +54,43 @@ public:
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIMSGSEARCHNOTIFY
 
   virtual const char * GetViewName(void) {return "QuickSearchView"; }
   NS_IMETHOD Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, 
                   nsMsgViewSortOrderValue sortOrder, 
                   nsMsgViewFlagsTypeValue viewFlags, PRInt32 *pCount);
+  NS_IMETHOD OpenWithHdrs(nsISimpleEnumerator *aHeaders, 
+                          nsMsgViewSortTypeValue aSortType, 
+                          nsMsgViewSortOrderValue aSortOrder, 
+                          nsMsgViewFlagsTypeValue aViewFlags, 
+                          PRInt32 *aCount);
   NS_IMETHOD DoCommand(nsMsgViewCommandTypeValue aCommand);
   NS_IMETHOD GetViewType(nsMsgViewTypeValue *aViewType);
+  NS_IMETHOD SetViewFlags(nsMsgViewFlagsTypeValue aViewFlags);
   NS_IMETHOD SetSearchSession(nsIMsgSearchSession *aSearchSession);
   NS_IMETHOD GetSearchSession(nsIMsgSearchSession* *aSearchSession);
   NS_IMETHOD OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, PRUint32 aOldFlags, 
                          PRUint32 aNewFlags, nsIDBChangeListener *aInstigator);
   NS_IMETHOD OnHdrPropertyChanged(nsIMsgDBHdr *aHdrToChange, PRBool aPreChange, PRUint32 *aStatus, 
                                  nsIDBChangeListener * aInstigator);
 
 protected:
   nsWeakPtr m_searchSession;
   nsTArray<nsMsgKey> m_origKeys;
   PRBool    m_usingCachedHits;
   PRBool    m_cacheEmpty;
   nsCOMArray <nsIMsgDBHdr> m_hdrHits;
+  virtual nsresult AddHdr(nsIMsgDBHdr *msgHdr, nsMsgViewIndex *resultIndex = nsnull);
   virtual nsresult OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, PRBool ensureListed);
   virtual nsresult DeleteMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, PRInt32 numIndices, PRBool deleteStorage);
   virtual nsresult SortThreads(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder);
   virtual nsresult GetFirstMessageHdrToDisplayInThread(nsIMsgThread *threadHdr, nsIMsgDBHdr **result);
   virtual nsresult ExpansionDelta(nsMsgViewIndex index, PRInt32 *expansionDelta);
   virtual nsresult ListIdsInThread(nsIMsgThread *threadHdr, nsMsgViewIndex startOfThreadViewIndex, PRUint32 *pNumListed);
+  virtual nsresult GetMessageEnumerator(nsISimpleEnumerator **enumerator);
   void      SavePreSearchInfo();
   void      ClearPreSearchInfo();
 
 };
 
 #endif
--- a/mailnews/base/src/nsMsgSearchDBView.cpp
+++ b/mailnews/base/src/nsMsgSearchDBView.cpp
@@ -1,9 +1,9 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
@@ -45,45 +45,79 @@
 #include "nsMsgBaseCID.h"
 #include "nsIMsgCopyService.h"
 #include "nsICopyMsgStreamListener.h"
 #include "nsMsgUtils.h"
 #include "nsITreeColumns.h"
 #include "nsIMsgMessageService.h"
 #include "nsAutoPtr.h"
 #include "nsIMutableArray.h"
+#include "nsMsgGroupThread.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+
+static PRBool gReferenceOnlyThreading;
 
 nsMsgSearchDBView::nsMsgSearchDBView()
 {
   // don't try to display messages for the search pane.
   mSuppressMsgDisplay = PR_TRUE;
+  m_threadsTable.Init();
+  m_hdrsTable.Init();
 }
 
 nsMsgSearchDBView::~nsMsgSearchDBView()
 {	
 }
 
-NS_IMPL_ISUPPORTS_INHERITED3(nsMsgSearchDBView, nsMsgDBView, nsIMsgDBView, nsIMsgCopyServiceListener, nsIMsgSearchNotify)
+NS_IMPL_ISUPPORTS_INHERITED3(nsMsgSearchDBView, nsMsgDBView, nsIMsgDBView, 
+                             nsIMsgCopyServiceListener, nsIMsgSearchNotify)
 
-NS_IMETHODIMP nsMsgSearchDBView::Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, nsMsgViewFlagsTypeValue viewFlags, PRInt32 *pCount)
+NS_IMETHODIMP nsMsgSearchDBView::Open(nsIMsgFolder *folder, 
+                                      nsMsgViewSortTypeValue sortType, 
+                                      nsMsgViewSortOrderValue sortOrder, 
+                                      nsMsgViewFlagsTypeValue viewFlags, 
+                                      PRInt32 *pCount)
 {
-    nsresult rv = nsMsgDBView::Open(folder, sortType, sortOrder, viewFlags, pCount);
+  nsresult rv = nsMsgDBView::Open(folder, sortType, sortOrder, 
+                                    viewFlags, pCount);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
     NS_ENSURE_SUCCESS(rv, rv);
+  prefBranch->GetBoolPref("mail.strict_threading", &gReferenceOnlyThreading);
 
     if (pCount)
       *pCount = 0;
     m_folder = nsnull;
     return rv;
 }
 
+
+PLDHashOperator
+nsMsgSearchDBView::ThreadTableCloner(const nsAString &aKey, nsIMsgThread* aThread, void* aArg)
+{
+  nsMsgSearchDBView* view = static_cast<nsMsgSearchDBView*>(aArg);
+  nsresult rv = view->m_threadsTable.Put(aKey, aThread);
+  return NS_SUCCEEDED(rv) ? PL_DHASH_NEXT : PL_DHASH_STOP;
+}
+
+PLDHashOperator
+nsMsgSearchDBView::MsgHdrTableCloner(const nsAString &aKey, nsIMsgDBHdr* aMsgHdr, void* aArg)
+{
+  nsMsgSearchDBView* view = static_cast<nsMsgSearchDBView*>(aArg);
+  nsresult rv = view->m_hdrsTable.Put(aKey, aMsgHdr);
+  return NS_SUCCEEDED(rv) ? PL_DHASH_NEXT : PL_DHASH_STOP;
+}
+
 NS_IMETHODIMP
 nsMsgSearchDBView::CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance, 
                                        nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater)
 {
-  nsMsgDBView::CopyDBView(aNewMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
+  nsMsgGroupView::CopyDBView(aNewMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
   nsMsgSearchDBView* newMsgDBView = (nsMsgSearchDBView *) aNewMsgDBView;
 
   // now copy all of our private member data
   newMsgDBView->mDestFolder = mDestFolder;
   newMsgDBView->mCommand = mCommand;
   newMsgDBView->mTotalIndices = mTotalIndices;
   newMsgDBView->mCurIndex = mCurIndex; 
   newMsgDBView->m_folders.InsertObjectsAt(m_folders, 0);
@@ -98,56 +132,173 @@ nsMsgSearchDBView::CopyDBView(nsMsgDBVie
 
   PRInt32 count = m_dbToUseList.Count(); 
   for(PRInt32 i = 0; i < count; i++)
   {
     newMsgDBView->m_dbToUseList.AppendObject(m_dbToUseList[i]);
     // register the new view with the database so it gets notifications
     m_dbToUseList[i]->AddListener(newMsgDBView);
   }
-
+  if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+  {
+    // We need to clone the thread and msg hdr hash tables.
+    m_threadsTable.EnumerateRead(ThreadTableCloner, newMsgDBView);
+    m_hdrsTable.EnumerateRead(MsgHdrTableCloner, newMsgDBView);
+  }
   return NS_OK;
 }
 
 NS_IMETHODIMP nsMsgSearchDBView::Close()
 {
   PRInt32 count = m_dbToUseList.Count();
   
   for(PRInt32 i = 0; i < count; i++)
     m_dbToUseList[i]->RemoveListener(this);
 
   m_dbToUseList.Clear();
 
-  return nsMsgDBView::Close();
+  return nsMsgGroupView::Close();
 }
 
 NS_IMETHODIMP nsMsgSearchDBView::GetCellText(PRInt32 aRow, nsITreeColumn* aCol, nsAString& aValue)
 {
   const PRUnichar* colID;
   aCol->GetIdConst(&colID);
-  if (colID[0] == 'l' && colID[1] == 'o') // location, need to check for "lo" not just "l" to avoid "label" column
+  // location, need to check for "lo" not just "l" to avoid "label" column
+  if (colID[0] == 'l' && colID[1] == 'o') 
     return FetchLocation(aRow, aValue);
   else
-    return nsMsgDBView::GetCellText(aRow, aCol, aValue);
+    return nsMsgGroupView::GetCellText(aRow, aCol, aValue);
 }
 
 nsresult nsMsgSearchDBView::FetchLocation(PRInt32 aRow, nsAString& aLocationString)
 {
   nsCOMPtr <nsIMsgFolder> folder;
   nsresult rv = GetFolderForViewIndex(aRow, getter_AddRefs(folder));
   NS_ENSURE_SUCCESS(rv,rv);
   return folder->GetPrettiestName(aLocationString);
 }
 
-nsresult nsMsgSearchDBView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, PRBool /*ensureListed*/)
+nsresult nsMsgSearchDBView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey,
+                                        PRBool /*ensureListed*/)
 {
    return NS_OK;
 }
 
-nsresult nsMsgSearchDBView::GetMsgHdrForViewIndex(nsMsgViewIndex index, nsIMsgDBHdr **msgHdr)
+NS_IMETHODIMP 
+nsMsgSearchDBView::OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted, nsMsgKey aParentKey, 
+                                PRInt32 aFlags, nsIDBChangeListener *aInstigator)
+{
+  if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+    return nsMsgGroupView::OnHdrDeleted(aHdrDeleted, aParentKey, 
+                                        aFlags, aInstigator);
+  if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+  {
+    nsMsgViewIndex deletedIndex = FindHdr(aHdrDeleted);
+    PRUint32 savedFlags = 0;
+    if (deletedIndex != nsMsgViewIndex_None)
+    {
+      savedFlags = m_flags[deletedIndex];
+      RemoveByIndex(deletedIndex);
+    }
+
+    nsCOMPtr<nsIMsgThread> thread;
+    GetXFThreadFromMsgHdr(aHdrDeleted, getter_AddRefs(thread));
+    if (thread)
+    {
+      nsMsgXFViewThread *viewThread = static_cast<nsMsgXFViewThread*>(thread.get());
+      viewThread->RemoveChildHdr(aHdrDeleted, nsnull);
+      if (deletedIndex == nsMsgViewIndex_None && viewThread->MsgCount() == 1)
+      {
+        // remove the last child of a collapsed thread. Need to find the root,
+        // and remove the thread flags on it.
+        nsCOMPtr<nsIMsgDBHdr> rootHdr;
+        thread->GetRootHdr(nsnull, getter_AddRefs(rootHdr));
+        if (rootHdr)
+        {
+          nsMsgViewIndex threadIndex = GetThreadRootIndex(rootHdr);
+          if (threadIndex != nsMsgViewIndex_None)
+            AndExtraFlag(threadIndex, ~(MSG_VIEW_FLAG_ISTHREAD | 
+                                        MSG_FLAG_ELIDED | 
+                                        MSG_VIEW_FLAG_HASCHILDREN));
+        }
+      }
+      else if (savedFlags & MSG_VIEW_FLAG_HASCHILDREN)
+{
+        if (savedFlags & MSG_FLAG_ELIDED)
+        {
+          nsCOMPtr<nsIMsgDBHdr> rootHdr;
+          nsresult rv = thread->GetRootHdr(nsnull, getter_AddRefs(rootHdr));
+          NS_ENSURE_SUCCESS(rv, rv);
+          nsMsgKey msgKey;
+          PRUint32 msgFlags;
+          rootHdr->GetMessageKey(&msgKey);
+          rootHdr->GetFlags(&msgFlags);
+          // promote the new thread root
+          if (viewThread->MsgCount() > 1)
+            msgFlags |= MSG_VIEW_FLAG_ISTHREAD | MSG_FLAG_ELIDED | 
+                        MSG_VIEW_FLAG_HASCHILDREN;
+          InsertMsgHdrAt(deletedIndex, rootHdr, msgKey, msgFlags, 0);
+          NoteChange(deletedIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
+        }
+        else if (viewThread->MsgCount() > 1)
+        {
+          OrExtraFlag(deletedIndex, MSG_VIEW_FLAG_ISTHREAD |
+                                    MSG_VIEW_FLAG_HASCHILDREN);
+        }
+      }
+    }
+  }
+  else
+  {
+    return nsMsgDBView::OnHdrDeleted(aHdrDeleted, aParentKey, 
+                                        aFlags, aInstigator);
+  }
+   return NS_OK;
+}
+
+void nsMsgSearchDBView::InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr,
+                              nsMsgKey msgKey, PRUint32 flags, PRUint32 level)
+{
+  m_keys.InsertElementAt(index, msgKey);
+  m_flags.InsertElementAt(index, flags);
+  m_levels.InsertElementAt(index, level);
+  nsCOMPtr<nsIMsgFolder> folder;
+  hdr->GetFolder(getter_AddRefs(folder));
+  m_folders.InsertObjectAt(folder, index);
+}
+
+void nsMsgSearchDBView::SetMsgHdrAt(nsIMsgDBHdr *hdr, nsMsgViewIndex index, 
+                              nsMsgKey msgKey, PRUint32 flags, PRUint32 level)
+{
+  m_keys[index] = msgKey;
+  m_flags[index] = flags;
+  m_levels[index] = level;
+  nsCOMPtr<nsIMsgFolder> folder;
+  hdr->GetFolder(getter_AddRefs(folder));
+  m_folders.ReplaceObjectAt(folder, index);
+}
+
+PRBool nsMsgSearchDBView::InsertEmptyRows(nsMsgViewIndex viewIndex, PRInt32 numRows)
+{
+  for (PRInt32 i = 0; i < numRows; i++)
+    if (!m_folders.InsertObjectAt(nsnull, viewIndex + i))
+      return PR_FALSE;
+  return nsMsgDBView::InsertEmptyRows(viewIndex, numRows);
+}
+
+void nsMsgSearchDBView::RemoveRows(nsMsgViewIndex viewIndex, PRInt32 numRows)
+{
+  nsMsgDBView::RemoveRows(viewIndex, numRows);
+  for (PRInt32 i = 0; i < numRows; i++)
+    m_folders.RemoveObjectAt(viewIndex);
+}
+
+nsresult nsMsgSearchDBView::GetMsgHdrForViewIndex(nsMsgViewIndex index, 
+                                                  nsIMsgDBHdr **msgHdr)
 {
   nsresult rv = NS_MSG_INVALID_DBVIEW_INDEX;
   if (index == nsMsgViewIndex_None || index > (PRUint32) m_folders.Count())
     return rv;
   nsIMsgFolder *folder = m_folders[index];
   if (folder)
   {
     nsCOMPtr <nsIMsgDatabase> db;
@@ -174,35 +325,237 @@ nsresult nsMsgSearchDBView::GetDBForView
   nsCOMPtr <nsIMsgFolder> aFolder;
   nsresult rv = GetFolderForViewIndex(index, getter_AddRefs(aFolder));
   NS_ENSURE_SUCCESS(rv, rv);
   return aFolder->GetMsgDatabase(nsnull, db);
 }
 
 nsresult nsMsgSearchDBView::AddHdrFromFolder(nsIMsgDBHdr *msgHdr, nsIMsgFolder *folder)
 {
-  m_folders.AppendObject(folder);
+  if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+    return nsMsgGroupView::OnNewHeader(msgHdr, nsMsgKey_None, PR_TRUE);
   nsMsgKey msgKey;
   PRUint32 msgFlags;
   msgHdr->GetMessageKey(&msgKey);
+  msgHdr->GetFlags(&msgFlags);
+
+  if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+  {
+    nsCOMPtr<nsIMsgThread> thread;
+    nsCOMPtr<nsIMsgDBHdr> threadRoot;
+    // if we find an xf thread in the hash table corresponding to the new msg's
+    // message id, a previous header must be a reference child of the new 
+    // message, which means we need to reparent later.
+    PRBool msgIsReferredTo;
+    GetXFThreadFromMsgHdr(msgHdr, getter_AddRefs(thread), &msgIsReferredTo);
+    PRBool newThread = !thread;
+    nsMsgXFViewThread *viewThread;
+    if (!thread)
+    {
+      viewThread = new nsMsgXFViewThread(this);
+      if (!viewThread)
+        return NS_ERROR_OUT_OF_MEMORY;
+      thread = do_QueryInterface(viewThread);
+    }
+    else
+    {
+      viewThread = static_cast<nsMsgXFViewThread*>(thread.get());
+      thread->GetChildAt(0, getter_AddRefs(threadRoot));
+    }
+
+    AddMsgToHashTables(msgHdr, thread);
+    nsCOMPtr<nsIMsgDBHdr> parent;
+    PRUint32 posInThread;
+    // We need to move threads in order to keep ourselves sorted
+    // correctly.  We want the index of the original thread...we can do this by
+    // getting the root header before we add the new header, and finding that.
+    if (newThread || !viewThread->MsgCount())
+    {
+      viewThread->AddHdr(msgHdr, PR_FALSE, posInThread,
+                         getter_AddRefs(parent));
+      nsMsgViewIndex insertIndex = GetIndexForThread(msgHdr);
+      NS_ASSERTION(insertIndex == m_levels.Length() || !m_levels[insertIndex],
+                    "inserting into middle of thread");
+      if (insertIndex == nsMsgViewIndex_None)
+        return NS_ERROR_FAILURE;
+      if (!(m_viewFlags & nsMsgViewFlagsType::kExpandAll))
+        msgFlags |= MSG_FLAG_ELIDED;
+      InsertMsgHdrAt(insertIndex, msgHdr, msgKey, msgFlags, 0);
+      NoteChange(insertIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
+    }
+    else
+    {
+      // get the thread root index before we add the header, because adding
+      // the header can change the sort position.
+      nsMsgViewIndex threadIndex = GetThreadRootIndex(threadRoot);
+      NS_ASSERTION(!m_levels[threadIndex], "threadRoot incorrect, or level incorrect");
+      viewThread->AddHdr(msgHdr, msgIsReferredTo, posInThread,
+                         getter_AddRefs(parent));
+
+      PRBool moveThread = PR_FALSE;
+      if (m_sortType == nsMsgViewSortType::byDate)
+      {
+        PRUint32 newestMsgInThread = 0, msgDate = 0;
+        viewThread->GetNewestMsgDate(&newestMsgInThread);
+        msgHdr->GetDateInSeconds(&msgDate);
+        moveThread = (msgDate == newestMsgInThread);
+      }
+      OrExtraFlag(threadIndex, MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_ISTHREAD);
+      if (!(m_flags[threadIndex] & MSG_FLAG_ELIDED))
+      {
+        if (parent)
+        {
+          // since we know posInThread, we just want to insert the new hdr
+          // at threadIndex + posInThread, and then rebuild the view until we
+          // get to a sibling of the new hdr.
+          PRUint8 newMsgLevel = viewThread->ChildLevelAt(posInThread);
+          InsertMsgHdrAt(threadIndex + posInThread, msgHdr, msgKey, msgFlags,
+                         newMsgLevel);
+
+          NoteChange(threadIndex + posInThread, 1, nsMsgViewNotificationCode::insertOrDelete);
+          for (nsMsgViewIndex viewIndex = threadIndex + ++posInThread;
+               posInThread < viewThread->MsgCount() && 
+               viewThread->ChildLevelAt(posInThread) > newMsgLevel; viewIndex++)
+          {
+            m_levels[viewIndex] = viewThread->ChildLevelAt(posInThread++);
+          }
+
+        }
+        else // The new header is the root, so we need to adjust 
+             // all the children.
+        {
+          InsertMsgHdrAt(threadIndex, msgHdr, msgKey, msgFlags, 0);
+
+          NoteChange(threadIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
+          nsMsgViewIndex i;
+          for (i = threadIndex + 1; 
+               i < m_keys.Length() && (i == threadIndex + 1 || m_levels[i]); i++)
+            m_levels[i] = m_levels[i] + 1;
+          // turn off thread flags on old root.
+          AndExtraFlag(threadIndex + 1, ~(MSG_VIEW_FLAG_ISTHREAD | 
+                                          MSG_FLAG_ELIDED | 
+                                          MSG_VIEW_FLAG_HASCHILDREN));
+
+          NoteChange(threadIndex + 1, i - threadIndex + 1, 
+                     nsMsgViewNotificationCode::changed);
+        }
+      }
+      else if (!parent)
+      {
+        // new parent came into collapsed thread
+        nsCOMPtr<nsIMsgFolder> msgFolder;
+        msgHdr->GetFolder(getter_AddRefs(msgFolder));
+        m_keys[threadIndex] = msgKey;
+        m_folders.ReplaceObjectAt(msgFolder, threadIndex);
+        m_flags[threadIndex] = msgFlags | MSG_VIEW_FLAG_ISTHREAD | 
+                                          MSG_FLAG_ELIDED | 
+                                          MSG_VIEW_FLAG_HASCHILDREN;
+        NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
+
+      }
+      if (moveThread)
+        MoveThreadAt(threadIndex);
+    }
+  }
+  else
+  {
+    m_folders.AppendObject(folder);
   // nsMsgKey_None means it's not a valid hdr.
   if (msgKey != nsMsgKey_None)
   {
     msgHdr->GetFlags(&msgFlags);
     m_keys.AppendElement(msgKey);
     m_levels.AppendElement(0);
     m_flags.AppendElement(msgFlags);
-    
-    // this needs to be called after we add the key, since RowCountChanged() will call our GetRowCount()
-    if (mTree)
-      mTree->RowCountChanged(GetSize() - 1, 1);
+      NoteChange(GetSize() - 1, 1, nsMsgViewNotificationCode::insertOrDelete);
+    }
   }
   return NS_OK;
   }
 
+// This method removes the thread at threadIndex from the view 
+// and puts it back in its new position, determined by the sort order.
+// And, if the selection is affected, save and restore the selection.
+void nsMsgSearchDBView::MoveThreadAt(nsMsgViewIndex threadIndex)
+{
+  PRBool updatesSuppressed = mSuppressChangeNotification;
+  // Turn off tree notifications so that we don't reload the current message.
+  if (!updatesSuppressed)
+    DisableChangeUpdates();
+
+  nsCOMPtr<nsIMsgDBHdr> threadHdr;
+  GetMsgHdrForViewIndex(threadIndex, getter_AddRefs(threadHdr));
+
+  PRUint32 saveFlags = m_flags[threadIndex];
+  PRBool threadIsExpanded = !(saveFlags & MSG_FLAG_ELIDED);
+  PRInt32 childCount = 0;
+  nsMsgKey preservedKey;
+  nsAutoTArray<nsMsgKey, 1> preservedSelection;
+  PRInt32 selCount;
+
+  SaveAndClearSelection(&preservedKey, preservedSelection);
+  if (threadIsExpanded)
+  {
+    ExpansionDelta(threadIndex, &childCount);
+    childCount = -childCount;
+  }
+  nsTArray<nsMsgKey> threadKeys;
+  nsTArray<PRUint32> threadFlags;
+  nsTArray<PRUint8> threadLevels;
+  nsCOMArray<nsIMsgFolder> threadFolders;
+
+  if (threadIsExpanded)
+  {
+    threadKeys.SetCapacity(childCount);
+    threadFlags.SetCapacity(childCount);
+    threadLevels.SetCapacity(childCount);
+    threadFolders.SetCapacity(childCount);
+    for (nsMsgViewIndex index = threadIndex + 1; 
+        index < (nsMsgViewIndex) GetSize() && m_levels[index]; index++)
+    {
+      threadKeys.AppendElement(m_keys[index]);
+      threadFlags.AppendElement(m_flags[index]);
+      threadLevels.AppendElement(m_levels[index]);
+      threadFolders.AppendObject(m_folders[index]);
+    }
+    PRUint32 collapseCount;
+    CollapseByIndex(threadIndex, &collapseCount);
+  }
+  nsMsgDBView::RemoveByIndex(threadIndex);
+  m_folders.RemoveObjectAt(threadIndex);
+  nsMsgViewIndex newIndex = GetIndexForThread(threadHdr);
+  NS_ASSERTION(newIndex == m_levels.Length() || !m_levels[newIndex],
+                "inserting into middle of thread");
+  if (newIndex == nsMsgViewIndex_None)
+    newIndex = 0;
+  nsMsgKey msgKey;
+  PRUint32 msgFlags;
+  threadHdr->GetMessageKey(&msgKey);
+  threadHdr->GetFlags(&msgFlags);
+  InsertMsgHdrAt(newIndex, threadHdr, msgKey, msgFlags, 0);
+
+  if (threadIsExpanded)
+  {
+    m_keys.InsertElementsAt(newIndex + 1, threadKeys);
+    m_flags.InsertElementsAt(newIndex + 1, threadFlags);
+    m_levels.InsertElementsAt(newIndex + 1, threadLevels);
+    m_folders.InsertObjectsAt(threadFolders, newIndex + 1);
+  }
+  m_flags[newIndex] = saveFlags;
+  // unfreeze selection.
+  RestoreSelection(preservedKey, preservedSelection);
+
+  if (!updatesSuppressed)
+    EnableChangeUpdates();
+  nsMsgViewIndex lowIndex = threadIndex < newIndex ? threadIndex : newIndex;
+  nsMsgViewIndex highIndex = lowIndex == threadIndex ? newIndex : threadIndex;
+  NoteChange(lowIndex, highIndex - lowIndex + childCount, 
+              nsMsgViewNotificationCode::changed);
+}
+
 NS_IMETHODIMP
 nsMsgSearchDBView::OnSearchHit(nsIMsgDBHdr* aMsgHdr, nsIMsgFolder *folder)
 {
   NS_ENSURE_ARG(aMsgHdr);
   NS_ENSURE_ARG(folder);
 
   if (m_folders.IndexOf(folder) < 0 ) //do this just for new folder
   {
@@ -289,25 +642,27 @@ nsMsgSearchDBView::GetCommandStatus(nsMs
   return NS_OK;
 }
 
 NS_IMETHODIMP 
 nsMsgSearchDBView::DoCommandWithFolder(nsMsgViewCommandTypeValue command, nsIMsgFolder *destFolder)
 {
     mCommand = command;
     mDestFolder = destFolder;
-
     return nsMsgDBView::DoCommandWithFolder(command, destFolder);
 }
 
 NS_IMETHODIMP nsMsgSearchDBView::DoCommand(nsMsgViewCommandTypeValue command)
 {
   mCommand = command;
-  if (command == nsMsgViewCommandType::deleteMsg || command == nsMsgViewCommandType::deleteNoTrash
-    || command == nsMsgViewCommandType::selectAll)
+  if (command == nsMsgViewCommandType::deleteMsg || 
+      command == nsMsgViewCommandType::deleteNoTrash ||
+      command == nsMsgViewCommandType::selectAll || 
+      command ==nsMsgViewCommandType::expandAll ||
+      command == nsMsgViewCommandType::collapseAll)
     return nsMsgDBView::DoCommand(command);
   nsresult rv = NS_OK;
   nsMsgViewIndexArray selection;
   GetSelectedIndices(selection);
 
   nsMsgViewIndex *indices = selection.Elements();
   PRInt32 numIndices = selection.Length();
 
@@ -323,25 +678,65 @@ NS_IMETHODIMP nsMsgSearchDBView::DoComma
   {
     rv = ApplyCommandToIndices(command, indexArrays[folderIndex].Elements(), indexArrays[folderIndex].Length());
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return rv;
 }
 
-// This method just removes the specified line from the view. It does
-// NOT delete it from the database.
+// This method removes the specified line from the view, and adjusts the
+// various flags and levels of affected messages.
 nsresult nsMsgSearchDBView::RemoveByIndex(nsMsgViewIndex index)
 {
     if (!IsValidIndex(index))
         return NS_MSG_INVALID_DBVIEW_INDEX;
 
-    m_folders.RemoveObjectAt(index);
+  if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+  {
+    nsCOMPtr<nsIMsgDBHdr> msgHdr;
+    nsCOMPtr<nsIMsgThread> thread;
+    nsresult rv = GetMsgHdrForViewIndex(index, getter_AddRefs(msgHdr));
+    NS_ENSURE_SUCCESS(rv, rv);
     
+    GetXFThreadFromMsgHdr(msgHdr, getter_AddRefs(thread));
+    if (thread)
+    {
+      nsMsgXFViewThread *viewThread = static_cast<nsMsgXFViewThread*>(thread.get());
+      if (viewThread->MsgCount() == 2)
+      {
+        // if we removed the next to last message in the thread,
+        // we need to adjust the flags on the first message in the thread.
+        nsMsgViewIndex threadIndex = m_levels[index] ? index -1 : index;
+        if (threadIndex != nsMsgViewIndex_None)
+        {
+          AndExtraFlag(threadIndex, ~(MSG_VIEW_FLAG_ISTHREAD | MSG_FLAG_ELIDED |
+                                      MSG_VIEW_FLAG_HASCHILDREN));
+          m_levels[threadIndex] = 0;
+          NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
+        }
+      }
+      // Bump up the level of all the descendents of the message
+      // that was removed, if the thread was expanded.
+      PRUint8 removedLevel = m_levels[index];
+      nsMsgViewIndex i = index + 1;
+      if (i < m_levels.Length() && m_levels[i] > removedLevel)
+      {
+        // promote the child of the removed message.
+        PRUint8 promotedLevel = m_levels[i];
+        m_levels[i] = promotedLevel - 1;
+        i++;
+        // now promote all the children of the promoted message.
+        for (; i < m_levels.Length() && 
+              m_levels[i] > promotedLevel; i++)
+          m_levels[i] = m_levels[i] - 1;
+      }
+    }
+  }
+  m_folders.RemoveObjectAt(index);
     return nsMsgDBView::RemoveByIndex(index);
 }
 
 nsresult nsMsgSearchDBView::DeleteMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, PRInt32 numIndices, PRBool deleteStorage)
 {
    nsresult rv;
    GetFoldersAndHdrsForSelection(indices, numIndices);
    if (mDeleteModel != nsMsgImapDeleteModels::MoveToTrash)
@@ -362,22 +757,18 @@ nsresult nsMsgSearchDBView::DeleteMessag
     else
       rv = ProcessRequestsInAllFolders(window);
     return rv;
 }
 
 nsresult 
 nsMsgSearchDBView::CopyMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, PRInt32 numIndices, PRBool isMove, nsIMsgFolder *destFolder)
 {
-    nsresult rv;
     GetFoldersAndHdrsForSelection(indices, numIndices);
-
-    rv = ProcessRequestsInOneFolder(window);
-
-    return rv;
+    return ProcessRequestsInOneFolder(window);
 }
 
 nsresult
 nsMsgSearchDBView::PartitionSelectionByFolder(nsMsgViewIndex *indices, PRInt32 numIndices, nsTArray<PRUint32> **indexArrays, PRInt32 *numArrays)
 {
   nsMsgViewIndex i;
   PRInt32 folderIndex;
   nsCOMArray<nsIMsgFolder> uniqueFoldersSelected;
@@ -564,42 +955,49 @@ nsresult nsMsgSearchDBView::ProcessReque
 
     curFolder->DeleteMessages(messageArray, window, PR_TRUE /* delete storage */, PR_FALSE /* is move*/, nsnull/*copyServListener*/, PR_FALSE /*allowUndo*/ );
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP nsMsgSearchDBView::Sort(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder)
 {
-    nsresult rv;
     PRInt32 rowCountBeforeSort = GetSize();
 
     if (!rowCountBeforeSort)
         return NS_OK;
 
+    if (m_viewFlags & (nsMsgViewFlagsType::kThreadedDisplay |
+                      nsMsgViewFlagsType::kGroupBySort))
+    {
+      // ### This forgets which threads were expanded, and is sub-optimal
+      // since it rebuilds the thread objects.  
+      m_sortType = sortType;
+      m_sortOrder = sortOrder;
+      return RebuildView();
+    }
+
     nsMsgKey preservedKey;
     nsAutoTArray<nsMsgKey, 1> preservedSelection;
     SaveAndClearSelection(&preservedKey, preservedSelection);
 
-    rv = nsMsgDBView::Sort(sortType,sortOrder);
-
+    nsresult rv = nsMsgDBView::Sort(sortType,sortOrder);
     // the sort may have changed the number of rows
     // before we restore the selection, tell the tree
     // do this before we call restore selection
     // this is safe when there is no selection. 
     rv = AdjustRowCount(rowCountBeforeSort, GetSize());
 
     RestoreSelection(preservedKey, preservedSelection);
     if (mTree) mTree->Invalidate();
 
     NS_ENSURE_SUCCESS(rv,rv);
     return rv;
 }
 
-
 // if nothing selected, return an NS_ERROR
 NS_IMETHODIMP
 nsMsgSearchDBView::GetHdrForFirstSelectedMessage(nsIMsgDBHdr **hdr)
 {
   NS_ENSURE_ARG_POINTER(hdr);
   PRInt32 index;
   if (!mTreeSelection)
     return NS_ERROR_NULL_POINTER;
@@ -618,8 +1016,290 @@ nsMsgSearchDBView::GetFolderFromMsgURI(c
   
   nsCOMPtr <nsIMsgDBHdr> msgHdr;
   rv = msgMessageService->MessageURIToMsgHdr(aMsgURI, getter_AddRefs(msgHdr));
   NS_ENSURE_SUCCESS(rv,rv);
   
   return msgHdr->GetFolder(aFolder);
 }
 
+nsMsgViewIndex nsMsgSearchDBView::FindHdr(nsIMsgDBHdr *msgHdr)
+{
+  nsCOMPtr<nsIMsgDBHdr> curHdr;
+  PRInt32 index;
+  // it would be nice to take advantage of sorted views when possible.
+  for (index = 0; index < GetSize(); index++)
+  {
+    GetMsgHdrForViewIndex(index, getter_AddRefs(curHdr));
+    if (curHdr == msgHdr && (!(m_flags[index] & MSG_VIEW_FLAG_DUMMY) ||
+        (m_flags[index] & MSG_FLAG_ELIDED)))
+      break;
+  }
+  return index < GetSize() ? index : nsMsgViewIndex_None;
+}
+
+// This method looks for the XF thread that corresponds to this message hdr,
+// first by looking up the message id, then references, and finally, if subject
+// threading is turned on, the subject.
+nsresult nsMsgSearchDBView::GetXFThreadFromMsgHdr(nsIMsgDBHdr *msgHdr, 
+                                                  nsIMsgThread **pThread,
+                                                  PRBool *foundByMessageId)
+{
+  nsAutoString hashKey;
+  nsCAutoString messageId;
+  msgHdr->GetMessageId(getter_Copies(messageId));
+  CopyASCIItoUTF16(messageId, hashKey);
+  *pThread = nsnull;
+  m_threadsTable.Get(hashKey, pThread);
+  // The caller may want to know if we found the thread by the msgHdr's
+  // messageId
+  if (foundByMessageId)
+    *foundByMessageId = *pThread != nsnull;
+  if (!*pThread)
+  {
+    PRUint16 numReferences = 0;
+    msgHdr->GetNumReferences(&numReferences);
+    for (PRInt32 i = numReferences - 1; i >= 0  && !*pThread; i--)
+    {
+      nsCAutoString reference;
+      
+      msgHdr->GetStringReference(i, reference);
+      if (reference.IsEmpty())
+        break;
+
+      CopyASCIItoUTF16(reference, hashKey);
+      m_threadsTable.Get(hashKey, pThread);
+    }
+  }
+  // if we're threading by subject, and we couldn't find the thread by ref,
+  // just treat subject as an other ref.
+  if (!*pThread && !gReferenceOnlyThreading)
+  {
+    nsCString subject;
+    msgHdr->GetSubject(getter_Copies(subject));
+    // this is the raw rfc822 subject header, so this is OK
+    CopyASCIItoUTF16(subject, hashKey);
+    m_threadsTable.Get(hashKey, pThread);
+  }
+  return (*pThread) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult nsMsgSearchDBView::GetMsgHdrFromHash(nsCString &reference, nsIMsgDBHdr **hdr)
+{
+  nsString hashKey;
+  CopyASCIItoUTF16(reference, hashKey);
+  return m_hdrsTable.Get(hashKey, hdr);
+}
+
+nsresult nsMsgSearchDBView::GetThreadFromHash(nsCString &reference, 
+                                              nsIMsgThread **thread)
+{
+  nsString hashKey;
+  CopyASCIItoUTF16(reference, hashKey);
+  return m_threadsTable.Get(hashKey, thread);
+}
+
+nsresult nsMsgSearchDBView::AddRefToHash(nsCString &reference, 
+                                         nsIMsgThread *thread)
+{
+  nsString hashKey;
+  CopyASCIItoUTF16(reference, hashKey);
+  // Check if this reference is already is associated with a thread;
+  // If so, don't overwrite that association.
+  nsCOMPtr<nsIMsgThread> oldThread;
+  m_threadsTable.Get(hashKey, getter_AddRefs(oldThread));
+  if (oldThread)
+    return NS_OK;
+
+  return m_threadsTable.Put(hashKey, thread);
+}
+
+nsresult nsMsgSearchDBView::AddMsgToHashTables(nsIMsgDBHdr *msgHdr,
+                                               nsIMsgThread *thread)
+{
+  PRUint16 numReferences = 0;
+  nsresult rv;
+
+  msgHdr->GetNumReferences(&numReferences);
+  for (PRInt32 i = 0; i < numReferences; i++)
+  {
+    nsCAutoString reference;
+    
+    msgHdr->GetStringReference(i, reference);
+    if (reference.IsEmpty())
+      break;
+
+    rv = AddRefToHash(reference, thread);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  nsCString messageId;
+  msgHdr->GetMessageId(getter_Copies(messageId));
+  nsString hashKey;
+  CopyASCIItoUTF16(messageId, hashKey);
+  m_hdrsTable.Put(hashKey, msgHdr);
+  if (!gReferenceOnlyThreading)
+  {
+    nsCString subject;
+    msgHdr->GetSubject(getter_Copies(subject));
+    // if we're threading by subject, just treat subject as an other ref.
+    AddRefToHash(subject, thread);
+  }
+  return AddRefToHash(messageId, thread);
+}
+
+nsresult nsMsgSearchDBView::RemoveRefFromHash(nsCString &reference)
+{
+  nsString hashKey;
+  CopyASCIItoUTF16(reference, hashKey);
+  m_threadsTable.Remove(hashKey);
+  return NS_OK;
+}
+
+nsresult nsMsgSearchDBView::RemoveMsgFromHashTables(nsIMsgDBHdr *msgHdr)
+{
+  PRUint16 numReferences = 0;
+  nsresult rv = NS_OK;
+
+  msgHdr->GetNumReferences(&numReferences);
+
+  for (PRInt32 i = 0; i < numReferences; i++)
+  {
+    nsCAutoString reference;
+    msgHdr->GetStringReference(i, reference);
+    if (reference.IsEmpty())
+      break;
+
+    rv = RemoveRefFromHash(reference);
+    if (NS_FAILED(rv))
+      break;
+  }
+  nsCString messageId;
+  msgHdr->GetMessageId(getter_Copies(messageId));
+  nsString hashKey;
+  CopyASCIItoUTF16(messageId, hashKey);
+  m_hdrsTable.Remove(hashKey);
+  RemoveRefFromHash(messageId);
+  if (!gReferenceOnlyThreading)
+  {
+    nsCString subject;
+    msgHdr->GetSubject(getter_Copies(subject));
+    // if we're threading by subject, just treat subject as an other ref.
+    RemoveRefFromHash(subject);
+  }
+  return rv;
+}
+
+nsMsgGroupThread *nsMsgSearchDBView::CreateGroupThread(nsIMsgDatabase * /* db */)
+{
+  return new nsMsgXFGroupThread();
+}
+
+nsresult nsMsgSearchDBView::GetThreadContainingMsgHdr(nsIMsgDBHdr *msgHdr, 
+                                                      nsIMsgThread **pThread)
+{
+  if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+    return nsMsgGroupView::GetThreadContainingMsgHdr(msgHdr, pThread);
+  else if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+    return GetXFThreadFromMsgHdr(msgHdr, pThread);
+
+  // if not threaded, use the real thread. 
+  nsCOMPtr<nsIMsgFolder> folder;
+  nsresult rv = msgHdr->GetFolder(getter_AddRefs(folder));
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsIMsgDatabase> msgDB;
+  rv = folder->GetMsgDatabase(nsnull, getter_AddRefs(msgDB));
+  NS_ENSURE_SUCCESS(rv, rv);
+  return msgDB->GetThreadContainingMsgHdr(msgHdr, pThread);
+}
+
+nsresult 
+nsMsgSearchDBView::ListIdsInThread(nsIMsgThread *threadHdr, 
+                                   nsMsgViewIndex startOfThreadViewIndex, 
+                                   PRUint32 *pNumListed)
+{
+  NS_ENSURE_ARG(threadHdr);
+  // these children ids should be in thread order.
+  PRUint32 i;
+  nsMsgViewIndex viewIndex = startOfThreadViewIndex + 1;
+  *pNumListed = 0;
+
+  PRUint32 numChildren;
+  threadHdr->GetNumChildren(&numChildren);
+  NS_ASSERTION(numChildren, "Empty thread in view/db");
+  if (!numChildren)
+    return NS_OK;
+
+  numChildren--; // account for the existing thread root
+  if (!InsertEmptyRows(viewIndex, numChildren))
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  PRBool threadedView = m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay &&
+    !(m_viewFlags & nsMsgViewFlagsType::kGroupBySort);
+  nsMsgXFViewThread *viewThread;
+  if (threadedView)
+    viewThread = static_cast<nsMsgXFViewThread*>(threadHdr);
+
+  for (i = 1; i <= numChildren; i++)
+  {
+    nsCOMPtr<nsIMsgDBHdr> msgHdr;
+    threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr));
+
+    if (msgHdr)
+    {
+      nsMsgKey msgKey;
+      PRUint32 msgFlags;
+      msgHdr->GetMessageKey(&msgKey);
+      msgHdr->GetFlags(&msgFlags);
+      PRUint8 level = (threadedView) ? viewThread->ChildLevelAt(i) : 1;
+      SetMsgHdrAt(msgHdr, viewIndex, msgKey, msgFlags & ~MSG_VIEW_FLAGS, 
+                  level);
+      (*pNumListed)++;
+      viewIndex++;
+    }
+  }
+  return NS_OK;
+}
+
+nsresult nsMsgSearchDBView::RebuildView()
+{
+  if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+    return nsMsgGroupView::RebuildView();
+
+  nsCOMPtr<nsISimpleEnumerator> headers;
+  if (NS_SUCCEEDED(GetMessageEnumerator(getter_AddRefs(headers))))
+  {
+    PRInt32 count;
+    // ### we need to be remembering headers, not keys.
+    nsAutoTArray<nsMsgKey, 1> preservedSelection;
+    nsMsgKey curSelectedKey;
+    SaveAndClearSelection(&curSelectedKey, preservedSelection);
+    InternalClose();
+    PRInt32 oldSize = GetSize();
+    // this is important, because the tree will ask us for our
+    // row count, which get determine from the number of keys.
+    m_keys.Clear();
+    // be consistent
+    m_flags.Clear();
+    m_levels.Clear();
+    m_folders.Clear();
+    m_threadsTable.Clear();
+    m_hdrsTable.Clear();
+
+    // this needs to happen after we remove all the keys, since RowCountChanged() will call our GetRowCount()
+    if (mTree)
+      mTree->RowCountChanged(0, -oldSize);
+    DisableChangeUpdates();
+    nsresult rv = OpenWithHdrs(headers, m_sortType, m_sortOrder, m_viewFlags, &count);
+    EnableChangeUpdates();
+    if (mTree)
+      mTree->RowCountChanged(0, GetSize());
+
+    NS_ENSURE_SUCCESS(rv,rv);
+
+    // now, restore our desired selection
+    nsAutoTArray<nsMsgKey, 1> keyArray;
+    keyArray.AppendElement(curSelectedKey);
+
+    return RestoreSelection(curSelectedKey, keyArray);
+  }
+  return NS_OK;
+}
--- a/mailnews/base/src/nsMsgSearchDBView.h
+++ b/mailnews/base/src/nsMsgSearchDBView.h
@@ -1,9 +1,9 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
@@ -33,75 +33,125 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef _nsMsgSearchDBViewsH_
 #define _nsMsgSearchDBView_H_
 
-#include "nsMsgDBView.h"
+#include "nsMsgGroupView.h"
 #include "nsIMsgCopyServiceListener.h"
 #include "nsIMsgSearchNotify.h"
+#include "nsMsgXFViewThread.h"
 #include "nsCOMArray.h"
 
-class nsMsgSearchDBView : public nsMsgDBView, public nsIMsgCopyServiceListener, public nsIMsgSearchNotify
+class nsMsgSearchDBView : public nsMsgGroupView, public nsIMsgCopyServiceListener, public nsIMsgSearchNotify
 {
 public:
   nsMsgSearchDBView();
   virtual ~nsMsgSearchDBView();
 
+  // these are tied together pretty intimately
+  friend class nsMsgXFViewThread;
+
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIMSGSEARCHNOTIFY
   NS_DECL_NSIMSGCOPYSERVICELISTENER
 
   virtual const char * GetViewName(void) {return "SearchView"; }
   NS_IMETHOD Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, 
         nsMsgViewFlagsTypeValue viewFlags, PRInt32 *pCount);
   NS_IMETHOD CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance, 
                         nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater);
   NS_IMETHOD Close();
   NS_IMETHOD GetViewType(nsMsgViewTypeValue *aViewType);
-  NS_IMETHOD Sort(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder);
-  NS_IMETHOD GetCommandStatus(nsMsgViewCommandTypeValue command, PRBool *selectable_p, nsMsgViewCommandCheckStateValue *selected_p);
+  NS_IMETHOD Sort(nsMsgViewSortTypeValue sortType, 
+                  nsMsgViewSortOrderValue sortOrder);
+  NS_IMETHOD GetCommandStatus(nsMsgViewCommandTypeValue command,
+                              PRBool *selectable_p, 
+                              nsMsgViewCommandCheckStateValue *selected_p);
   NS_IMETHOD DoCommand(nsMsgViewCommandTypeValue command);
   NS_IMETHOD DoCommandWithFolder(nsMsgViewCommandTypeValue command, nsIMsgFolder *destFolder);
   NS_IMETHOD GetHdrForFirstSelectedMessage(nsIMsgDBHdr **hdr);
+  NS_IMETHOD OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted, nsMsgKey aParentKey, 
+                          PRInt32 aFlags, nsIDBChangeListener *aInstigator);
   // override to get location
   NS_IMETHOD GetCellText(PRInt32 aRow, nsITreeColumn* aCol, nsAString& aValue);
   virtual nsresult GetMsgHdrForViewIndex(nsMsgViewIndex index, nsIMsgDBHdr **msgHdr);
   virtual nsresult OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey parentKey, PRBool ensureListed);
   NS_IMETHOD GetFolderForViewIndex(nsMsgViewIndex index, nsIMsgFolder **folder);
+  virtual nsresult RebuildView();
 
   NS_IMETHOD OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator);
 
   virtual nsCOMArray<nsIMsgFolder>* GetFolders();
   virtual nsresult GetFolderFromMsgURI(const char *aMsgURI, nsIMsgFolder **aFolder);
 
 protected:
+  virtual nsresult ListIdsInThread(nsIMsgThread *threadHdr, 
+                                   nsMsgViewIndex startOfThreadViewIndex, 
+                                   PRUint32 *pNumListed);
   nsresult FetchLocation(PRInt32 aRow, nsAString& aLocationString);
   virtual nsresult AddHdrFromFolder(nsIMsgDBHdr *msgHdr, nsIMsgFolder *folder);
   virtual nsresult GetDBForViewIndex(nsMsgViewIndex index, nsIMsgDatabase **db);
   virtual nsresult RemoveByIndex(nsMsgViewIndex index);
   virtual nsresult CopyMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, PRInt32 numIndices, PRBool isMove, nsIMsgFolder *destFolder);
   virtual nsresult DeleteMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, PRInt32 numIndices, PRBool deleteStorage);
+  virtual void InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr,
+                              nsMsgKey msgKey, PRUint32 flags, PRUint32 level);
+  virtual void SetMsgHdrAt(nsIMsgDBHdr *hdr, nsMsgViewIndex index, 
+                              nsMsgKey msgKey, PRUint32 flags, PRUint32 level);
+  virtual PRBool InsertEmptyRows(nsMsgViewIndex viewIndex, PRInt32 numRows);
+  virtual void RemoveRows(nsMsgViewIndex viewIndex, PRInt32 numRows);
+  virtual nsMsgViewIndex FindHdr(nsIMsgDBHdr *msgHdr);
+  virtual nsresult GetThreadContainingMsgHdr(nsIMsgDBHdr *msgHdr, nsIMsgThread **pThread);
   nsresult GetFoldersAndHdrsForSelection(nsMsgViewIndex *indices, PRInt32 numIndices);
   nsresult GroupSearchResultsByFolder();
   nsresult PartitionSelectionByFolder(nsMsgViewIndex *indices, PRInt32 numIndices, nsTArray<PRUint32> **indexArrays, PRInt32 *numArrays);
   virtual nsresult ApplyCommandToIndicesWithFolder(nsMsgViewCommandTypeValue command, nsMsgViewIndex* indices,
                     PRInt32 numIndices, nsIMsgFolder *destFolder);
+  void MoveThreadAt(nsMsgViewIndex threadIndex);
   
   nsCOMArray<nsIMsgFolder> m_folders;
   nsCOMPtr <nsISupportsArray> m_hdrsForEachFolder;
   nsCOMPtr <nsISupportsArray> m_copyListenerList;
   nsCOMArray<nsIMsgFolder> m_uniqueFoldersSelected;
-  PRInt32 mCurIndex;
+  PRUint32 mCurIndex;
 
   nsMsgViewIndex* mIndicesForChainedDeleteAndFile;
   PRInt32 mTotalIndices;
   nsCOMArray<nsIMsgDatabase> m_dbToUseList;
   nsMsgViewCommandTypeValue mCommand;
   nsCOMPtr <nsIMsgFolder> mDestFolder;
   nsresult ProcessRequestsInOneFolder(nsIMsgWindow *window);
   nsresult ProcessRequestsInAllFolders(nsIMsgWindow *window);
+  // these are for doing threading of the search hits
+
+
+  // this maps message-ids and reference message ids to
+  // the corresponding nsMsgXFViewThread object. If we're 
+  // doing subject threading, we would throw subjects
+  // into the same table.
+  nsInterfaceHashtable <nsStringHashKey, nsIMsgThread> m_threadsTable;
+
+  // map message-ids to msg hdrs in the view, used for threading.
+  nsInterfaceHashtable <nsStringHashKey, nsIMsgDBHdr> m_hdrsTable;
+
+  PR_STATIC_CALLBACK(PLDHashOperator) ThreadTableCloner(const nsAString &aKey, 
+                                                        nsIMsgThread* aThread, 
+                                                        void* aArg);
+  PR_STATIC_CALLBACK(PLDHashOperator) MsgHdrTableCloner(const nsAString &aKey, 
+                                                        nsIMsgDBHdr* aMsgHdr, 
+                                                        void* aArg);
+  virtual nsMsgGroupThread *CreateGroupThread(nsIMsgDatabase *db);
+  nsresult GetXFThreadFromMsgHdr(nsIMsgDBHdr *msgHdr, nsIMsgThread **pThread,
+                                 PRBool *foundByMessageId = nsnull);
+  nsresult GetThreadFromHash(nsCString &reference, nsIMsgThread **thread);
+  nsresult GetMsgHdrFromHash(nsCString &reference, nsIMsgDBHdr **hdr);
+  nsresult AddRefToHash(nsCString &reference, nsIMsgThread *thread);
+  nsresult AddMsgToHashTables(nsIMsgDBHdr *msgHdr, nsIMsgThread *thread);
+  nsresult RemoveRefFromHash(nsCString &reference);
+  nsresult RemoveMsgFromHashTables(nsIMsgDBHdr *msgHdr);
+  nsresult InitRefHash();
 };
 
 #endif
--- a/mailnews/base/src/nsMsgSpecialViews.cpp
+++ b/mailnews/base/src/nsMsgSpecialViews.cpp
@@ -1,9 +1,9 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
--- a/mailnews/base/src/nsMsgSpecialViews.h
+++ b/mailnews/base/src/nsMsgSpecialViews.h
@@ -1,9 +1,9 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
--- a/mailnews/base/src/nsMsgThreadedDBView.cpp
+++ b/mailnews/base/src/nsMsgThreadedDBView.cpp
@@ -1,9 +1,9 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
@@ -550,16 +550,19 @@ void nsMsgThreadedDBView::ClearPrevIdArr
   m_prevKeys.Clear();
   m_prevLevels.Clear();
   m_prevFlags.Clear();
   m_havePrevView = PR_FALSE;
 }
 
 nsresult nsMsgThreadedDBView::InitSort(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder)
 {
+  if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+    return NS_OK; // nothing to do.
+
   if (sortType == nsMsgViewSortType::byThread)
   {
     nsMsgDBView::Sort(nsMsgViewSortType::byId, sortOrder); // sort top level threads by id.
     m_sortType = nsMsgViewSortType::byThread;
     m_viewFlags |= nsMsgViewFlagsType::kThreadedDisplay;
     m_viewFlags &= ~nsMsgViewFlagsType::kGroupBySort;
     SetViewFlags(m_viewFlags); // persist the view flags.
     //		m_db->SetSortInfo(m_sortType, sortOrder);
@@ -577,16 +580,19 @@ nsresult nsMsgThreadedDBView::InitSort(n
   Sort(sortType, sortOrder);
   if (sortType != nsMsgViewSortType::byThread)	// forget prev view, since it has everything expanded.
     ClearPrevIdArray();
   return NS_OK;
 }
 
 nsresult nsMsgThreadedDBView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, PRBool ensureListed)
 {
+  if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+    return nsMsgGroupView::OnNewHeader(newHdr, aParentKey, ensureListed);
+
   nsresult rv = NS_OK;
   nsMsgKey newKey;
   newHdr->GetMessageKey(&newKey);
 
   // views can override this behaviour, which is to append to view.
   // This is the mail behaviour, but threaded views want
   // to insert in order...
   if (newHdr)
@@ -946,16 +952,8 @@ nsMsgThreadedDBView::CloneDBView(nsIMess
     return NS_ERROR_OUT_OF_MEMORY;
 
   nsresult rv = CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
   NS_ENSURE_SUCCESS(rv,rv);
 
   NS_IF_ADDREF(*_retval = newMsgDBView);
   return NS_OK;
 }
-
-NS_IMETHODIMP
-nsMsgThreadedDBView::GetSupportsThreading(PRBool *aResult)
-{
-  NS_ENSURE_ARG_POINTER(aResult);
-  *aResult = PR_TRUE;
-  return NS_OK;
-}
--- a/mailnews/base/src/nsMsgThreadedDBView.h
+++ b/mailnews/base/src/nsMsgThreadedDBView.h
@@ -1,9 +1,9 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
@@ -33,35 +33,30 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef _nsMsgThreadedDBView_H_
 #define _nsMsgThreadedDBView_H_
 
-#include "nsMsgDBView.h"
+#include "nsMsgGroupView.h"
 
-// this class should probably inherit from the class that
-// implements the tree. Since I don't know what that is yet,
-// I'll just make it inherit from nsMsgDBView for now.
-class nsMsgThreadedDBView : public nsMsgDBView
+class nsMsgThreadedDBView : public nsMsgGroupView
 {
 public:
   nsMsgThreadedDBView();
   virtual ~nsMsgThreadedDBView();
 
   NS_IMETHOD Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, nsMsgViewFlagsTypeValue viewFlags, PRInt32 *pCount);
   NS_IMETHOD CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCommandUpdater, nsIMsgDBView **_retval);
   NS_IMETHOD Close();
   virtual nsresult AddKeys(nsMsgKey *pKeys, PRInt32 *pFlags, const char *pLevels, nsMsgViewSortTypeValue sortType, PRInt32 numKeysToAdd);
   NS_IMETHOD Sort(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder);
   NS_IMETHOD GetViewType(nsMsgViewTypeValue *aViewType);
-  NS_IMETHOD GetSupportsThreading(PRBool *aResult);
-
   NS_IMETHOD OnParentChanged (nsMsgKey aKeyChanged, nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeListener *aInstigator);
 
 protected:
   virtual const char * GetViewName(void) {return "ThreadedDBView"; }
   nsresult InitThreadedView(PRInt32 *pCount);
   virtual nsresult OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, PRBool ensureListed);
   virtual nsresult AddMsgToThreadNotInView(nsIMsgThread *threadHdr, nsIMsgDBHdr *msgHdr, PRBool ensureListed);
   nsresult ListThreadIds(nsMsgKey *startMsg, PRBool unreadOnly, nsMsgKey *pOutput, PRInt32 *pFlags, char *pLevels, 
new file mode 100644
--- /dev/null
+++ b/mailnews/base/src/nsMsgXFViewThread.cpp
@@ -0,0 +1,511 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * David Bienvenu.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   David Bienvenu <bienvenu@nventure.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "msgCore.h"
+#include "nsMsgXFViewThread.h"
+#include "nsMsgSearchDBView.h"
+
+NS_IMPL_ISUPPORTS1(nsMsgXFViewThread, nsIMsgThread)
+
+nsMsgXFViewThread::nsMsgXFViewThread(nsMsgSearchDBView *view)
+{
+  m_numUnreadChildren = 0;
+  m_numChildren = 0;
+  m_flags = 0;
+  m_newestMsgDate = 0;
+  m_view = view;
+}
+
+nsMsgXFViewThread::~nsMsgXFViewThread()
+{
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::SetThreadKey(nsMsgKey threadKey)
+{
+  NS_ERROR("shouldn't call this");
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::GetThreadKey(nsMsgKey *aResult)
+{
+  NS_ERROR("shouldn't call this");
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::GetFlags(PRUint32 *aFlags)
+{
+  NS_ENSURE_ARG_POINTER(aFlags);
+  *aFlags = m_flags;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::SetFlags(PRUint32 aFlags)
+{
+  m_flags = aFlags;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::SetSubject(const nsACString& aSubject)
+{
+  NS_ASSERTION(PR_FALSE, "shouldn't call this");
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::GetSubject(nsACString& result)
+{
+  NS_ASSERTION(PR_FALSE, "shouldn't call this");
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::GetNumChildren(PRUint32 *aNumChildren)
+{
+  NS_ENSURE_ARG_POINTER(aNumChildren);
+  *aNumChildren = m_keys.Length();
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::GetNumUnreadChildren (PRUint32 *aNumUnreadChildren)
+{
+  NS_ENSURE_ARG_POINTER(aNumUnreadChildren);
+  *aNumUnreadChildren = m_numUnreadChildren;
+  return NS_OK;
+}
+
+NS_IMETHODIMP 
+nsMsgXFViewThread::AddChild(nsIMsgDBHdr *aNewHdr, nsIMsgDBHdr *aInReplyTo, 
+                            PRBool aThreadInThread, nsIDBChangeAnnouncer *aAnnouncer)
+{
+  PRUint32 whereInserted;
+  return AddHdr(aNewHdr, PR_FALSE, whereInserted, nsnull);
+}
+
+// Returns the parent of the newly added header. If reparentChildren
+// is true, we believe that the new header is a parent of an existing
+// header, and we should find it, and reparent it.
+nsresult nsMsgXFViewThread::AddHdr(nsIMsgDBHdr *newHdr,
+                                   PRBool reparentChildren,
+                                   PRUint32 &whereInserted,
+                                   nsIMsgDBHdr **outParent)
+{
+  nsCOMPtr<nsIMsgFolder> newHdrFolder;
+  newHdr->GetFolder(getter_AddRefs(newHdrFolder));
+
+  PRUint32 newHdrFlags = 0;
+  PRUint32 msgDate;
+  nsMsgKey newHdrKey = 0;
+
+  newHdr->GetMessageKey(&newHdrKey);
+  newHdr->GetDateInSeconds(&msgDate);
+  newHdr->GetFlags(&newHdrFlags);
+  if (msgDate > m_newestMsgDate)
+    SetNewestMsgDate(msgDate);
+
+  if (newHdrFlags & MSG_FLAG_WATCHED)
+    SetFlags(m_flags | MSG_FLAG_WATCHED);
+
+  ChangeChildCount(1);
+  if (! (newHdrFlags & MSG_FLAG_READ))
+    ChangeUnreadChildCount(1);
+
+  if (m_numChildren == 1)
+  {
+    m_keys.InsertElementAt(0, newHdrKey);
+    m_levels.InsertElementAt(0, 0);
+    m_folders.InsertObjectAt(newHdrFolder, 0);
+    if (outParent)
+      *outParent = nsnull;
+    whereInserted = 0;
+    return NS_OK;
+  }
+
+  // Find our parent, if any, in the thread. Starting at the newest
+  // reference, and working our way back, see if we've mapped that reference
+  // to this thread.
+  PRUint16 numReferences;
+  newHdr->GetNumReferences(&numReferences);
+  nsCOMPtr<nsIMsgDBHdr> parent;
+  PRInt32 parentIndex;
+
+  for (PRInt32 i = numReferences - 1; i >= 0;  i--)
+  {
+    nsCAutoString reference;
+    newHdr->GetStringReference(i, reference);
+    if (reference.IsEmpty())
+      break;
+
+    // I could look for the thread from the reference, but getting
+    // the header directly should be fine. If it's not, that means
+    // that the parent isn't in this thread, though it should be.
+    m_view->GetMsgHdrFromHash(reference, getter_AddRefs(parent));
+    if (parent)
+    {
+      parentIndex = HdrIndex(parent);
+      if (parentIndex == -1)
+      {
+        NS_ERROR("how did we get in the wrong thread?");
+        parent = nsnull;
+      }
+      break;
+    }
+  }
+  if (parent)
+  {
+    if (outParent)
+      NS_ADDREF(*outParent = parent);
+    PRUint8 parentLevel = m_levels[parentIndex];
+    nsMsgKey parentKey;
+    parent->GetMessageKey(&parentKey);
+    nsCOMPtr<nsIMsgFolder> parentFolder;
+    parent->GetFolder(getter_AddRefs(parentFolder));
+    // iterate over our parents' children until we find one we're older than,
+    // and insert ourselves before it, or as the last child. In other words,
+    // insert, sorted by date.
+    PRUint32 msgDate, childDate;
+    newHdr->GetDateInSeconds(&msgDate);
+    nsCOMPtr<nsIMsgDBHdr> child;
+    nsMsgViewIndex i;
+    PRInt32 insertIndex = m_keys.Length();
+    for (i = parentIndex; 
+         i < m_keys.Length() && (i == parentIndex ||  m_levels[i] > parentLevel); i++)
+    {
+      if (m_levels[i] == parentLevel + 1) // possible sibling
+      {
+        GetChildHdrAt(i, getter_AddRefs(child));
+        if (child)
+        {
+          if (reparentChildren && IsHdrParentOf(newHdr, child))
+          {
+            insertIndex = i;
+            // bump all the children of the current child.
+            nsMsgViewIndex i = insertIndex; 
+            do 
+            {
+              m_levels[i] = m_levels[i] + 1;
+              i++;
+            }
+            while (i < m_keys.Length() && m_levels[i] > parentLevel + 1);
+            break;
+          }
+          else
+          {
+            child->GetDateInSeconds(&childDate);
+            if (msgDate < childDate)
+            {
+              // if we think we need to reparent, remember this
+              // insert index, but keep looking for children.
+              insertIndex = i;
+              if (!reparentChildren)
+                break;
+            }
+          }
+        }
+      }
+    }
+    m_keys.InsertElementAt(insertIndex, newHdrKey);
+    m_levels.InsertElementAt(insertIndex, m_levels[parentIndex] + 1);
+    m_folders.InsertObjectAt(newHdrFolder, insertIndex);
+    whereInserted = insertIndex;
+  }
+  else
+  {
+    if (outParent)
+      *outParent = nsnull;
+    nsCOMPtr<nsIMsgDBHdr> rootHdr;
+    GetChildHdrAt(0, getter_AddRefs(rootHdr));
+    // If the new header is a parent of the root then it should be promoted. 
+    if (rootHdr && IsHdrParentOf(newHdr, rootHdr))
+    {
+      m_keys.InsertElementAt(0, newHdrKey);
+      m_levels.InsertElementAt(0, 0);
+      m_folders.InsertObjectAt(newHdrFolder, 0);
+      whereInserted = 0;
+      // Adjust level of root hdr. We still have to reparent children of root,
+      // and adjust levels if if neccessary.
+      m_levels[1] = 1;
+    }
+    else
+    {
+      m_keys.AppendElement(newHdrKey);
+      m_levels.AppendElement(1);
+      m_folders.AppendObject(newHdrFolder);
+      if (outParent)
+        NS_ADDREF(*outParent = rootHdr);
+      whereInserted = m_keys.Length() -1;
+    }
+  }
+
+  // ### TODO handle the case where the root header starts 
+  // with Re, and the new one doesn't, and is earlier. In that
+  // case, we want to promote the new header to root.
+
+//  PRTime newHdrDate;
+//  newHdr->GetDate(&newHdrDate);
+
+//  if (numChildren > 0 && !(newHdrFlags & MSG_FLAG_HAS_RE))
+//  {
+//    PRTime topLevelHdrDate;
+
+//    nsCOMPtr<nsIMsgDBHdr> topLevelHdr;
+//    rv = GetRootHdr(nsnull, getter_AddRefs(topLevelHdr));
+//    if (NS_SUCCEEDED(rv) && topLevelHdr)
+//    {
+//      topLevelHdr->GetDate(&topLevelHdrDate);
+//      if (LL_CMP(newHdrDate, <, topLevelHdrDate))
+      
+//    }
+//  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::GetChildAt(PRInt32 aIndex, nsIMsgDBHdr **aResult)
+{
+  if (aIndex >= (PRInt32) m_keys.Length())
+    return NS_MSG_MESSAGE_NOT_FOUND;
+  nsCOMPtr<nsIMsgDatabase> db;
+  nsresult rv = m_folders[aIndex]->GetMsgDatabase(nsnull, getter_AddRefs(db));
+  NS_ENSURE_SUCCESS(rv, rv);
+  return db->GetMsgHdrForKey(m_keys[aIndex], aResult);
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::GetChildHdrAt(PRInt32 aIndex, nsIMsgDBHdr **aResult)
+{
+  return GetChildAt(aIndex, aResult);
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::RemoveChildAt(PRInt32 aIndex)
+{
+  m_keys.RemoveElementAt(aIndex);
+  m_levels.RemoveElementAt(aIndex);
+  m_folders.RemoveObjectAt(aIndex);
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::RemoveChildHdr(nsIMsgDBHdr *child, nsIDBChangeAnnouncer *announcer)
+{
+  NS_ENSURE_ARG_POINTER(child);
+  nsMsgKey msgKey;
+  PRUint32 msgFlags;
+  child->GetMessageKey(&msgKey);
+  child->GetFlags(&msgFlags);
+  nsCOMPtr<nsIMsgFolder> msgFolder;
+  child->GetFolder(getter_AddRefs(msgFolder));
+  // if this was the newest msg, clear the newest msg date so we'll recalc.
+  PRUint32 date;
+  child->GetDateInSeconds(&date);
+  if (date == m_newestMsgDate)
+    SetNewestMsgDate(0);
+
+  for (PRUint32 childIndex = 0; childIndex < m_keys.Length(); childIndex++)
+  {
+    if (m_keys[childIndex] == msgKey && m_folders[childIndex] == msgFolder)
+    {
+      PRUint8 levelRemoved = m_keys[childIndex];
+      // Adjust the levels of all the children of this header
+      nsMsgViewIndex i;
+      for (i = childIndex + 1; 
+               i < m_keys.Length() && m_levels[i] > levelRemoved; i++)
+            m_levels[i] = m_levels[i] - 1;
+
+      m_view->NoteChange(childIndex + 1, i - childIndex + 1,
+                         nsMsgViewNotificationCode::changed);
+      m_keys.RemoveElementAt(childIndex);
+      m_levels.RemoveElementAt(childIndex);
+      m_folders.RemoveObjectAt(childIndex);
+      if (!(msgFlags & MSG_FLAG_READ))
+        ChangeUnreadChildCount(-1);
+      ChangeChildCount(-1);
+      return NS_OK;
+    }
+  }
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::GetRootHdr(PRInt32 *aResultIndex, nsIMsgDBHdr **aResult)
+{
+  NS_ENSURE_ARG_POINTER(aResult);
+  if (aResultIndex)
+    *aResultIndex = 0;
+  return GetChildHdrAt(0, aResult);
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::GetChildKeyAt(PRInt32 aIndex, nsMsgKey *aResult)
+{
+  NS_ASSERTION(PR_FALSE, "shouldn't call this");
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::GetChild(nsMsgKey msgKey, nsIMsgDBHdr **aResult)
+{
+  NS_ASSERTION(PR_FALSE, "shouldn't call this");
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+PRUint32 nsMsgXFViewThread::HdrIndex(nsIMsgDBHdr *hdr)
+{
+  nsMsgKey msgKey;
+  nsCOMPtr<nsIMsgFolder> folder;
+  hdr->GetMessageKey(&msgKey);
+  hdr->GetFolder(getter_AddRefs(folder));
+  for (PRUint32 i = 0; i < m_keys.Length(); i++)
+  {
+    if (m_keys[i] == msgKey && m_folders[i] == folder)
+      return i;
+  }
+  return -1;
+}
+
+void nsMsgXFViewThread::ChangeUnreadChildCount(PRInt32 delta)
+{
+  m_numUnreadChildren += delta;
+}
+
+void nsMsgXFViewThread::ChangeChildCount(PRInt32 delta)
+{
+  m_numChildren += delta;
+}
+
+PRBool nsMsgXFViewThread::IsHdrParentOf(nsIMsgDBHdr *possibleParent, 
+                                        nsIMsgDBHdr *possibleChild)
+{
+  PRUint16 referenceToCheck = 0;
+  possibleChild->GetNumReferences(&referenceToCheck);
+  nsCAutoString reference;
+
+  nsCString messageId;
+  possibleParent->GetMessageId(getter_Copies(messageId));
+
+  while (referenceToCheck > 0)
+  {
+    possibleChild->GetStringReference(referenceToCheck - 1, reference);
+
+    if (reference.Equals(messageId))
+      return PR_TRUE;
+    // if reference didn't match, check if this ref is for a non-existent
+    // header. If it is, continue looking at ancestors.
+    nsCOMPtr<nsIMsgDBHdr> refHdr;
+    m_view->GetMsgHdrFromHash(reference, getter_AddRefs(refHdr));
+    if (refHdr)
+      break;
+    referenceToCheck--;
+  }
+  return PR_FALSE;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::GetNewestMsgDate(PRUint32 *aResult) 
+{
+  // if this hasn't been set, figure it out by enumerating the msgs in the thread.
+  if (!m_newestMsgDate)
+  {
+    PRUint32 numChildren;
+    nsresult rv = NS_OK;
+  
+    GetNumChildren(&numChildren);
+  
+    if ((PRInt32) numChildren < 0)
+      numChildren = 0;
+  
+    for (PRUint32 childIndex = 0; childIndex < numChildren; childIndex++)
+    {
+      nsCOMPtr<nsIMsgDBHdr> child;
+      rv = GetChildHdrAt(childIndex, getter_AddRefs(child));
+      if (NS_SUCCEEDED(rv) && child)
+      {
+        PRUint32 msgDate;
+        child->GetDateInSeconds(&msgDate);
+        if (msgDate > m_newestMsgDate)
+          m_newestMsgDate = msgDate;
+      }
+    }
+  }
+  *aResult = m_newestMsgDate;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::SetNewestMsgDate(PRUint32 aNewestMsgDate) 
+{
+  m_newestMsgDate = aNewestMsgDate;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::MarkChildRead(PRBool aRead)
+{
+  ChangeUnreadChildCount(aRead ? -1 : 1);
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::GetFirstUnreadChild(nsIMsgDBHdr **aResult)
+{
+  NS_ENSURE_ARG(aResult);
+  PRUint32 numChildren;
+  nsresult rv = NS_OK;
+  
+  GetNumChildren(&numChildren);
+  
+  if ((PRInt32) numChildren < 0)
+    numChildren = 0;
+  
+  for (PRUint32 childIndex = 0; childIndex < numChildren; childIndex++)
+  {
+    nsCOMPtr<nsIMsgDBHdr> child;
+    rv = GetChildHdrAt(childIndex, getter_AddRefs(child));
+    if (NS_SUCCEEDED(rv) && child)
+    {
+      nsMsgKey msgKey;
+      child->GetMessageKey(&msgKey);
+      
+      PRBool isRead;
+      nsCOMPtr<nsIMsgDatabase> db;
+      nsresult rv = m_folders[childIndex]->GetMsgDatabase(nsnull, getter_AddRefs(db));
+      if (NS_SUCCEEDED(rv))
+        rv = db->IsRead(msgKey, &isRead);
+      if (NS_SUCCEEDED(rv) && !isRead)
+      {
+        NS_ADDREF(*aResult = child);
+        break;
+      }
+    }
+  }
+  return rv;
+}
+NS_IMETHODIMP nsMsgXFViewThread::EnumerateMessages(PRUint32 aParentKey, 
+                                                   nsISimpleEnumerator **aResult)
+{
+  NS_ERROR("shouldn't call this");
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
new file mode 100644
--- /dev/null
+++ b/mailnews/base/src/nsMsgXFViewThread.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * David Bienvenu.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   David Bienvenu <bienvenu@nventure.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+#ifndef nsMsgXFViewThread_h__
+#define nsMsgXFViewThread_h__
+
+#include "msgCore.h"
+#include "nsCOMArray.h"
+#include "nsIMsgThread.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgHdr.h"
+#include "nsMsgDBView.h"
+
+class nsMsgSearchDBView;
+
+class nsMsgXFViewThread : public nsIMsgThread
+{
+public:
+
+  nsMsgXFViewThread(nsMsgSearchDBView *view);
+  virtual ~nsMsgXFViewThread();
+
+  NS_DECL_NSIMSGTHREAD
+  NS_DECL_ISUPPORTS
+
+  PRBool    IsHdrParentOf(nsIMsgDBHdr *possibleParent,
+                          nsIMsgDBHdr *possibleChild);
+
+  void      ChangeUnreadChildCount(PRInt32 delta);
+  void      ChangeChildCount(PRInt32 delta);
+
+  nsresult  AddHdr(nsIMsgDBHdr *newHdr, PRBool reparentChildren, 
+                   PRUint32 &whereInserted, nsIMsgDBHdr **outParent);
+  PRUint32  HdrIndex(nsIMsgDBHdr *hdr);
+  PRUint32  ChildLevelAt(PRUint32 msgIndex) {return m_levels[msgIndex];}
+  PRUint32  MsgCount() {return m_numChildren;};
+
+protected:
+  nsMsgSearchDBView *m_view;
+  PRUint32        m_numUnreadChildren;	
+  PRUint32        m_numChildren;
+  PRUint32        m_flags;
+  PRUint32        m_newestMsgDate;
+  nsTArray<nsMsgKey> m_keys;
+  nsCOMArray<nsIMsgFolder> m_folders;
+  nsTArray<PRUint8> m_levels;
+};
+
+#endif
--- a/mailnews/base/src/nsMsgXFVirtualFolderDBView.cpp
+++ b/mailnews/base/src/nsMsgXFVirtualFolderDBView.cpp
@@ -1,9 +1,9 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
@@ -55,17 +55,21 @@ nsMsgXFVirtualFolderDBView::nsMsgXFVirtu
   mSuppressMsgDisplay = PR_FALSE;
   m_doingSearch = PR_FALSE;
 }
 
 nsMsgXFVirtualFolderDBView::~nsMsgXFVirtualFolderDBView()
 {
 }
 
-NS_IMETHODIMP nsMsgXFVirtualFolderDBView::Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, nsMsgViewFlagsTypeValue viewFlags, PRInt32 *pCount)
+NS_IMETHODIMP nsMsgXFVirtualFolderDBView::Open(nsIMsgFolder *folder, 
+                                               nsMsgViewSortTypeValue sortType, 
+                                               nsMsgViewSortOrderValue sortOrder, 
+                                               nsMsgViewFlagsTypeValue viewFlags, 
+                                               PRInt32 *pCount)
 {
   m_viewFolder = folder;
   return nsMsgSearchDBView::Open(folder, sortType, sortOrder, viewFlags, pCount);
 }
 
 void nsMsgXFVirtualFolderDBView::RemovePendingDBListeners()
 {
   nsresult rv;
@@ -107,17 +111,16 @@ nsMsgXFVirtualFolderDBView::CopyDBView(n
 
   nsMsgXFVirtualFolderDBView* newMsgDBView = (nsMsgXFVirtualFolderDBView *) aNewMsgDBView;
 
   newMsgDBView->m_viewFolder = m_viewFolder;
   newMsgDBView->m_searchSession = m_searchSession;
   return NS_OK;
 }
 
-
 NS_IMETHODIMP nsMsgXFVirtualFolderDBView::GetViewType(nsMsgViewTypeValue *aViewType)
 {
     NS_ENSURE_ARG_POINTER(aViewType);
     *aViewType = nsMsgViewType::eShowVirtualFolderResults;
     return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -200,20 +203,17 @@ nsresult nsMsgXFVirtualFolderDBView::Ins
   nsMsgViewIndex insertIndex = GetInsertIndex(msgHdr);
   if (insertIndex == nsMsgViewIndex_None)
     return AddHdrFromFolder(msgHdr, folder);
 
   nsMsgKey msgKey;
   PRUint32 msgFlags;
   msgHdr->GetMessageKey(&msgKey);
   msgHdr->GetFlags(&msgFlags);
-  m_keys.InsertElementAt(insertIndex, msgKey);
-  m_flags.InsertElementAt(insertIndex, msgFlags);
-  m_folders.InsertObjectAt(folder, insertIndex);
-  m_levels.InsertElementAt(insertIndex, 0);
+  InsertMsgHdrAt(insertIndex, msgHdr, msgKey, msgFlags, 0);
 
   // the call to NoteChange() has to happen after we add the key
   // as NoteChange() will call RowCountChanged() which will call our GetRowCount()
   NoteChange(insertIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
   return NS_OK;
 }
 
 void nsMsgXFVirtualFolderDBView::UpdateCacheAndViewForFolder(nsIMsgFolder *folder, nsMsgKey *newHits, PRUint32 numNewHits)
@@ -225,22 +225,26 @@ void nsMsgXFVirtualFolderDBView::UpdateC
     nsCString searchUri;
     m_viewFolder->GetURI(searchUri);
     PRUint32 numBadHits;
     nsMsgKey *badHits;
     rv = db->RefreshCache(searchUri.get(), numNewHits, newHits,
                      &numBadHits, &badHits);
     if (NS_SUCCEEDED(rv))
     {
+      nsCOMPtr<nsIMsgDBHdr> badHdr;
       for (PRUint32 badHitIndex = 0; badHitIndex < numBadHits; badHitIndex++)
       {
-        // of course, this isn't quite right
-        nsMsgViewIndex staleHitIndex = FindKey(badHits[badHitIndex], PR_TRUE);
-        if (staleHitIndex != nsMsgViewIndex_None)
-          RemoveByIndex(staleHitIndex);
+        // ### of course, this isn't quite right, since we should be 
+        // using FindHdr, and we shouldn't be expanding the threads.
+        db->GetMsgHdrForKey(badHits[badHitIndex], getter_AddRefs(badHdr));
+        // let nsMsgSearchDBView decide what to do about this header
+        // getting removed.
+        if (badHdr)
+          OnHdrDeleted(badHdr, nsMsgKey_None, 0, this);
       }
       delete [] badHits;
     }
   }
 }
 
 void nsMsgXFVirtualFolderDBView::UpdateCacheAndViewForPrevSearchedFolders(nsIMsgFolder *curSearchFolder)
 {
@@ -298,17 +302,19 @@ nsMsgXFVirtualFolderDBView::OnSearchHit(
     m_curFolderStartKeyIndex = m_keys.Length();
   }
   PRBool hdrInCache = PR_FALSE;
   nsCString searchUri;
   m_viewFolder->GetURI(searchUri);
   dbToUse->HdrIsInCache(searchUri.get(), aMsgHdr, &hdrInCache);
   if (!m_doingSearch || !m_curFolderHasCachedHits || !hdrInCache)
   {
-    if (m_sortValid)
+    if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+      nsMsgGroupView::OnNewHeader(aMsgHdr, nsMsgKey_None, PR_TRUE);
+    else if (m_sortValid)
       InsertHdrFromFolder(aMsgHdr, aFolder);
     else
       AddHdrFromFolder(aMsgHdr, aFolder);
   }
   m_hdrHits.AppendObject(aMsgHdr);
 
   return NS_OK;
 }
@@ -319,36 +325,55 @@ nsMsgXFVirtualFolderDBView::OnSearchDone
   // handle any non verified hits we haven't handled yet.
   UpdateCacheAndViewForPrevSearchedFolders(nsnull);
 
   m_doingSearch = PR_FALSE;
   //we want to set imap delete model once the search is over because setting next
   //message after deletion will happen before deleting the message and search scope
   //can change with every search.
   mDeleteModel = nsMsgImapDeleteModels::MoveToTrash;  //set to default in case it is non-imap folder
-  nsCOMPtr <nsIMsgFolder> curFolder = m_folders.SafeObjectAt(0);
+  nsIMsgFolder *curFolder = m_folders.SafeObjectAt(0);
   if (curFolder)
     GetImapDeleteModel(curFolder);
 
   nsCOMPtr <nsIMsgDatabase> virtDatabase;
   nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
   nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase));
   NS_ENSURE_SUCCESS(rv, rv);
   // count up the number of unread and total messages from the view, and set those in the
   // folder - easier than trying to keep the count up to date in the face of
   // search hits coming in while the user is reading/deleting messages.
-  PRInt32 numUnread = 0;
+  PRUint32 numUnread = 0;
+  PRUint32 numTotal = 0;
   for (PRUint32 i = 0; i < m_flags.Length(); i++)
+    if (m_flags[i] & MSG_FLAG_ELIDED)
+    {
+      nsCOMPtr<nsIMsgThread> thread;
+      GetThreadContainingIndex(i, getter_AddRefs(thread));
+      if (thread)
+      {
+        PRUint32 totalInThread, unreadInThread;
+        thread->GetNumUnreadChildren(&unreadInThread);
+        thread->GetNumChildren(&totalInThread);
+        numTotal += totalInThread;
+        numUnread += unreadInThread;
+      }
+    }
+    else
+    {
+      numTotal++;
     if (!(m_flags[i] & MSG_FLAG_READ))
       numUnread++;
+    }
   dbFolderInfo->SetNumUnreadMessages(numUnread);
-  dbFolderInfo->SetNumMessages(GetSize());
+  dbFolderInfo->SetNumMessages(numTotal);
   m_viewFolder->UpdateSummaryTotals(true); // force update from db.
   virtDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
-  if (!m_sortValid && m_sortType != nsMsgViewSortType::byThread)
+  if (!m_sortValid && m_sortType != nsMsgViewSortType::byThread && 
+      !(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
   {
     m_sortValid = PR_FALSE;       //sort the results
     Sort(m_sortType, m_sortOrder);
   }
   m_foldersSearchingOver.Clear();
   m_curFolderGettingHits = nsnull;
   return rv;
 }
@@ -436,23 +461,24 @@ nsMsgXFVirtualFolderDBView::OnNewSearch(
 
   m_curFolderStartKeyIndex = 0;
   m_curFolderGettingHits = nsnull;
   m_curFolderHasCachedHits = PR_FALSE;
 
   // if we have cached hits, sort them.
   if (GetSize() > 0)
   {
-    if (m_sortType != nsMsgViewSortType::byThread)
+    // currently, we keep threaded views sorted while we build them.
+    if (m_sortType != nsMsgViewSortType::byThread &&
+      !(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
     {
       m_sortValid = PR_FALSE;       //sort the results
       Sort(m_sortType, m_sortOrder);
     }
   }
-//    mSearchResults->Clear();
     return NS_OK;
 }
 
 
 NS_IMETHODIMP nsMsgXFVirtualFolderDBView::DoCommand(nsMsgViewCommandTypeValue command)
 {
     return nsMsgSearchDBView::DoCommand(command);
 }
@@ -460,8 +486,60 @@ NS_IMETHODIMP nsMsgXFVirtualFolderDBView
 
 
 NS_IMETHODIMP nsMsgXFVirtualFolderDBView::GetMsgFolder(nsIMsgFolder **aMsgFolder)
 {
   NS_ENSURE_ARG_POINTER(aMsgFolder);
   NS_IF_ADDREF(*aMsgFolder = m_viewFolder);
   return NS_OK;
 }
+
+NS_IMETHODIMP nsMsgXFVirtualFolderDBView::SetViewFlags(nsMsgViewFlagsTypeValue aViewFlags)
+{
+  nsMsgViewFlagsTypeValue saveViewFlags = m_viewFlags;
+  nsresult rv =  nsMsgDBView::SetViewFlags(aViewFlags);
+  // if the grouping/threading has changed, rebuild the view
+  if ((saveViewFlags & (nsMsgViewFlagsType::kGroupBySort |
+                      nsMsgViewFlagsType::kThreadedDisplay)) !=
+      (aViewFlags & (nsMsgViewFlagsType::kGroupBySort |
+                     nsMsgViewFlagsType::kThreadedDisplay)))
+    RebuildView();
+
+  return rv;
+}
+
+NS_IMETHODIMP nsMsgXFVirtualFolderDBView::OpenWithHdrs(nsISimpleEnumerator *aHeaders, nsMsgViewSortTypeValue aSortType,
+                                        nsMsgViewSortOrderValue aSortOrder, nsMsgViewFlagsTypeValue aViewFlags,
+                                        PRInt32 *aCount)
+{
+  if (aViewFlags & nsMsgViewFlagsType::kGroupBySort)
+    return nsMsgGroupView::OpenWithHdrs(aHeaders, aSortType, aSortOrder, 
+                                        aViewFlags, aCount);
+
+  m_sortType = aSortType;
+  m_sortOrder = aSortOrder;
+  m_viewFlags = aViewFlags;
+
+  PRBool hasMore;
+  nsCOMPtr<nsISupports> supports;
+  nsCOMPtr<nsIMsgDBHdr> msgHdr;
+  nsCOMPtr<nsIMsgFolder> folder;
+  nsresult rv = NS_OK;
+  while (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv = aHeaders->HasMoreElements(&hasMore)) && hasMore)
+  {
+    rv = aHeaders->GetNext(getter_AddRefs(supports));
+    if (NS_SUCCEEDED(rv) && supports)
+    {
+      msgHdr = do_QueryInterface(supports);
+      msgHdr->GetFolder(getter_AddRefs(folder));
+      AddHdrFromFolder(msgHdr, folder); 
+    }
+  }
+  *aCount = m_keys.Length();
+  return rv;
+}
+
+
+nsresult 
+nsMsgXFVirtualFolderDBView::GetMessageEnumerator(nsISimpleEnumerator **enumerator)
+{
+  return GetViewEnumerator(enumerator);
+}
--- a/mailnews/base/src/nsMsgXFVirtualFolderDBView.h
+++ b/mailnews/base/src/nsMsgXFVirtualFolderDBView.h
@@ -1,9 +1,9 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
@@ -39,47 +39,58 @@
 #ifndef _nsMsgXFVirtualFolderDBView_H_
 #define _nsMsgXFVirtualFolderDBView_H_
 
 #include "nsMsgSearchDBView.h"
 #include "nsIMsgCopyServiceListener.h"
 #include "nsIMsgSearchNotify.h"
 #include "nsCOMArray.h"
 
+class nsMsgGroupThread;
+
 class nsMsgXFVirtualFolderDBView : public nsMsgSearchDBView
 {
 public:
   nsMsgXFVirtualFolderDBView();
   virtual ~nsMsgXFVirtualFolderDBView();
 
   NS_IMETHOD SetSearchSession(nsIMsgSearchSession *aSearchSession);
   // we override all the methods, currently. Might change...
   NS_DECL_NSIMSGSEARCHNOTIFY
 
   virtual const char * GetViewName(void) {return "XFVirtualFolderView"; }
   NS_IMETHOD Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, 
         nsMsgViewFlagsTypeValue viewFlags, PRInt32 *pCount);
+  NS_IMETHOD OpenWithHdrs(nsISimpleEnumerator *aHeaders, 
+                          nsMsgViewSortTypeValue aSortType,
+                          nsMsgViewSortOrderValue aSortOrder, 
+                          nsMsgViewFlagsTypeValue aViewFlags,
+                          PRInt32 *aCount);
   NS_IMETHOD CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, 
                          nsIMsgDBViewCommandUpdater *aCmdUpdater, nsIMsgDBView **_retval);
   NS_IMETHOD CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance, 
                         nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater);
   NS_IMETHOD Close();
   NS_IMETHOD GetViewType(nsMsgViewTypeValue *aViewType);
   NS_IMETHOD DoCommand(nsMsgViewCommandTypeValue command);
-  virtual nsresult OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey parentKey, PRBool ensureListed);
+  NS_IMETHOD SetViewFlags(nsMsgViewFlagsTypeValue aViewFlags);
   NS_IMETHOD OnHdrPropertyChanged(nsIMsgDBHdr *aHdrToChange, PRBool aPreChange, PRUint32 *aStatus, 
                                  nsIDBChangeListener * aInstigator);
+  NS_IMETHOD GetMsgFolder(nsIMsgFolder **aMsgFolder);
+
+  virtual nsresult OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey parentKey, PRBool ensureListed);
   virtual nsresult InsertHdrFromFolder(nsIMsgDBHdr *msgHdr, nsIMsgFolder *folder);
-  NS_IMETHOD GetMsgFolder(nsIMsgFolder **aMsgFolder);
   void UpdateCacheAndViewForPrevSearchedFolders(nsIMsgFolder *curSearchFolder);
   void UpdateCacheAndViewForFolder(nsIMsgFolder *folder, nsMsgKey *newHits, PRUint32 numNewHits);
   void RemovePendingDBListeners();
 
 protected:
 
+  virtual nsresult GetMessageEnumerator(nsISimpleEnumerator **enumerator);
+
   PRUint32 m_cachedFolderArrayIndex; // array index of next folder with cached hits to deal with.
   nsCOMArray<nsIMsgFolder> m_foldersSearchingOver;
   nsCOMArray<nsIMsgDBHdr> m_hdrHits;
   nsCOMPtr <nsIMsgFolder> m_curFolderGettingHits;
   PRUint32 m_curFolderStartKeyIndex; // keeps track of the index of the first hit from the cur folder
   PRBool m_curFolderHasCachedHits;
   PRBool m_doingSearch;
   nsWeakPtr m_searchSession;