Bug 654864 - Suite changes from |Bug 422845 - Replace rdf-driven addressbook directory tree with js one|. r=Mnyromyr sr=Neil a=IanN
authorJens Hatlak <jh@junetz.de>
Sun, 08 May 2011 10:20:00 +0200
changeset 8305 1aaea7153818aadf96207636cae23ca19f294d72
parent 8304 40707d58c3658609615b6d57ed00bba7f547b2b9
child 8306 1e274c1f9e3bd7631cbeb6ffca1876995f773a58
push id84
push userbugzilla@standard8.plus.com
push dateTue, 16 Aug 2011 21:25:04 +0000
treeherdercomm-beta@6970c86be3cd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMnyromyr, Neil, IanN
bugs654864, 422845
Bug 654864 - Suite changes from |Bug 422845 - Replace rdf-driven addressbook directory tree with js one|. r=Mnyromyr sr=Neil a=IanN
suite/mailnews/addrbook/abCommon.js
suite/mailnews/addrbook/abTrees.js
suite/mailnews/addrbook/addressbook.js
suite/mailnews/addrbook/addressbook.xul
suite/mailnews/jar.mn
--- a/suite/mailnews/addrbook/abCommon.js
+++ b/suite/mailnews/addrbook/abCommon.js
@@ -36,18 +36,18 @@
  * 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 ***** */
 
 Components.utils.import("resource:///modules/mailServices.js");
 
-var dirTree = 0;
-var abList = 0;
+var gDirTree = 0;
+var abList = null;
 var gAbResultsTree = null;
 var gAbView = null;
 var gAddressBookBundle;
 
 var gPrefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
 var gHeaderParser = Components.classes["@mozilla.org/messenger/headerparser;1"].getService(Components.interfaces.nsIMsgHeaderParser);
 
 const kDefaultSortColumn = "GeneratedName";
@@ -77,17 +77,17 @@ var DirPaneController =
   },
 
   isCommandEnabled: function(command)
   {
     var selectedDir;
 
     switch (command) {
       case "cmd_selectAll":
-        // the dirTree pane
+        // the gDirTree pane
         // only handles single selection
         // so we forward select all to the results pane
         // but if there is no gAbView
         // don't bother sending to the results pane
         return (gAbView != null);
       case "cmd_delete":
       case "button_delete":
         selectedDir = GetSelectedDirectory();
@@ -143,17 +143,17 @@ var DirPaneController =
   doCommand: function(command)
   {
     switch (command) {
       case "cmd_selectAll":
         SendCommandToResultsPane(command);
         break;
       case "cmd_delete":
       case "button_delete":
-        if (dirTree)
+        if (gDirTree)
           AbDeleteSelectedDirectory();
         break;
       case "button_edit":
         AbEditSelectedDirectory();
         break;
       case "cmd_newlist":
         AbNewList();
         break;
@@ -190,17 +190,17 @@ function AbNewAddressBook()
   window.openDialog("chrome://messenger/content/addressbook/abAddressBookNameDialog.xul",
                     "",
                     "chrome,modal,resizable=no,centerscreen",
                     null);
 }
 
 function AbEditSelectedDirectory()
 {
-  if (dirTree.view.selection.count == 1) {
+  if (gDirectoryTreeView.selection.count == 1) {
     var selecteduri = GetSelectedDirectory();
     var directory = GetDirectoryFromURI(selecteduri);
     if (directory.isMailList) {
       goEditListDialog(null, selecteduri);
     }
     else {
       window.openDialog(directory.propertiesChromeURI,
                         "",
@@ -264,17 +264,17 @@ function AbDeleteDirectory(aURI)
     gPrefs.setCharPref("mail.collect_addressbook", kPersonalAddressbookURI);
   }
 
   MailServices.ab.deleteAddressBook(aURI);
 }
 
 function InitCommonJS()
 {
-  dirTree = document.getElementById("dirTree");
+  gDirTree = document.getElementById("dirTree");
   abList = document.getElementById("addressbookList");
   gAddressBookBundle = document.getElementById("bundle_addressBook");
 }
 
 function UpgradeAddressBookResultsPaneUI(prefName)
 {
   // placeholder in case any new columns get added to the address book
   // var resultsPaneUIVersion = gPrefs.getIntPref(prefName);
@@ -395,19 +395,18 @@ function goToggleSplitter( id, elementID
 // Generate a list of cards from the selected mailing list 
 // and get a comma separated list of card addresses. If the
 // item selected in the directory pane is not a mailing list,
 // an empty string is returned. 
 function GetSelectedAddressesFromDirTree() 
 {
   var addresses = "";
 
-  if (dirTree.currentIndex >= 0) {
-    var selectedResource = dirTree.builderView.getResourceAtIndex(dirTree.currentIndex);
-    var directory = GetDirectoryFromURI(selectedResource.Value);
+  if (gDirTree.currentIndex >= 0) {
+    var directory = gDirectoryTreeView.getDirectoryAtIndex(gDirTree.currentIndex);
     if (directory.isMailList) {
       var listCardsCount = directory.addressLists.length;
       var cards = new Array(listCardsCount);
       for (var i = 0; i < listCardsCount; ++i)
         cards[i] = directory.addressLists
                             .queryElementAt(i, Components.interfaces.nsIAbCard);
       addresses = GetAddressesForCards(cards);
     }
@@ -437,17 +436,17 @@ function GetAddressesForCards(cards)
   }
 
   return addresses;
 }
 
 
 function SelectFirstAddressBook()
 {
-  dirTree.view.selection.select(0);
+  gDirectoryTreeView.selection.select(0);
 
   ChangeDirectoryByURI(GetSelectedDirectory());
   gAbResultsTree.focus();
 }
 
 function DirPaneClick(event)
 {
   // we only care about left button events
@@ -471,30 +470,32 @@ function DirPaneClick(event)
 }
 
 function DirPaneDoubleClick(event)
 {
   // we only care about left button events
   if (event.button != 0)
     return;
 
-  var row = dirTree.treeBoxObject.getRowAt(event.clientX, event.clientY);
-  if (row == -1 || row > dirTree.view.rowCount-1) {
+  var row = gDirTree.treeBoxObject.getRowAt(event.clientX, event.clientY);
+  if (row == -1 || row >= gDirectoryTreeView.rowCount) {
     // double clicking on a non valid row should not open the dir properties dialog
     return;
   }
 
-  if (dirTree && dirTree.view.selection && dirTree.view.selection.count == 1)
+  if (gDirectoryTreeView.selection &&
+      gDirectoryTreeView.selection.count == 1)
     AbEditSelectedDirectory();
 }
 
 function DirPaneSelectionChange()
 {
-  if (dirTree && dirTree.view.selection && dirTree.view.selection.count == 1) {
-    gPreviousDirTreeIndex = dirTree.currentIndex;
+  if (gDirectoryTreeView.selection &&
+      gDirectoryTreeView.selection.count == 1) {
+    gPreviousDirTreeIndex = gDirTree.currentIndex;
     ChangeDirectoryByURI(GetSelectedDirectory());
   }
 }
 
 function ChangeDirectoryByURI(uri)
 {
   if (!uri)
     uri = kPersonalAddressbookURI;
@@ -613,29 +614,28 @@ function GetParentDirectoryFromMailingLi
   }
 
   return null;
 } 
 
 function DirPaneHasFocus()
 {
   // Returns true if directory pane has the focus. Returns false, otherwise.
-  return (top.document.commandDispatcher.focusedElement == dirTree)
+  return top.document.commandDispatcher.focusedElement == gDirTree;
 }
 
 function GetSelectedDirectory()
 {
   if (abList)
     return abList.value;
-  else {
-    if (dirTree.currentIndex < 0)
-      return null;
-    var selected = dirTree.builderView.getResourceAtIndex(dirTree.currentIndex)
-    return selected.Value;
-  }
+
+  if (gDirTree.currentIndex < 0)
+    return null;
+
+  return gDirectoryTreeView.getDirectoryAtIndex(gDirTree.currentIndex).URI;
 }
 
 /**
  * Returns an nsIFile of the directory in which contact photos are stored.
  * This will create the directory if it does not yet exist.
  */
 function getPhotosDir() {
   var file = Components.classes["@mozilla.org/file/directory_service;1"]
new file mode 100644
--- /dev/null
+++ b/suite/mailnews/addrbook/abTrees.js
@@ -0,0 +1,342 @@
+/* ***** 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 Mail Addressbook code.
+ *
+ * The Initial Developer of the Original Code is
+ *   Joey Minta <jminta@gmail.com>
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either 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 ***** */
+
+/**
+ * This file contains our implementation for various addressbook trees.  It
+ * depends on jsTreeView.js being loaded before this script is loaded.
+ */
+
+Components.utils.import("resource:///modules/mailServices.js");
+
+/**
+ * Each abDirTreeItem corresponds to one row in the tree view.
+ */
+function abDirTreeItem(aDirectory)
+{
+  this._directory = aDirectory;
+}
+
+abDirTreeItem.prototype =
+{
+  getText: function atv_getText()
+  {
+    return this._directory.dirName;
+  },
+
+  get id()
+  {
+    return this._directory.URI;
+  },
+
+  _open: false,
+  get open()
+  {
+    return this._open;
+  },
+
+  _level: 0,
+  get level()
+  {
+    return this._level;
+  },
+
+  _children: null,
+  get children()
+  {
+    if (!this._children)
+    {
+      this._children = [];
+      var myEnum = this._directory.childNodes;
+      while (myEnum.hasMoreElements())
+      {
+        var abItem = new abDirTreeItem(
+          myEnum.getNext().QueryInterface(Components.interfaces.nsIAbDirectory));
+        abItem._level = this._level + 1;
+        abItem._parent = this;
+        this._children.push(abItem);
+      }
+
+      // We sort children based on their names.
+      function nameSort(a, b)
+      {
+        return a._directory.dirName.localeCompare(b._directory.dirName);
+      }
+      this._children.sort(nameSort);
+    }
+    return this._children;
+  },
+
+  getProperties: function atv_getProps(aProps)
+  {
+    var atomSvc = Components.classes["@mozilla.org/atom-service;1"]
+                            .getService(Components.interfaces.nsIAtomService);
+    if (this._directory.isMailList)
+      aProps.AppendElement(atomSvc.getAtom("IsMailList-true"));
+    if (this._directory.isRemote)
+      aProps.AppendElement(atomSvc.getAtom("IsRemote-true"));
+    if (this._directory.isSecure)
+      aProps.AppendElement(atomSvc.getAtom("IsSecure-true"));
+  }
+};
+
+/**
+ * Our actual implementation of nsITreeView.
+ */
+function directoryTreeView() {}
+directoryTreeView.prototype =
+{
+  __proto__: new PROTO_TREE_VIEW(),
+
+  init: function dtv_init(aTree, aJSONFile)
+  {
+    if (aJSONFile) {
+      // Parse our persistent-open-state json file
+      let file = Components.classes["@mozilla.org/file/directory_service;1"]
+                           .getService(Components.interfaces.nsIProperties)
+                           .get("ProfD", Components.interfaces.nsIFile);
+      file.append(aJSONFile);
+
+      if (file.exists())
+      {
+        let data = "";
+        let fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
+                                .createInstance(Components.interfaces.nsIFileInputStream);
+        let sstream = Components.classes["@mozilla.org/scriptableinputstream;1"]
+                                .createInstance(Components.interfaces.nsIScriptableInputStream);
+        fstream.init(file, -1, 0, 0);
+        sstream.init(fstream);
+
+        while (sstream.available())
+          data += sstream.read(4096);
+
+        sstream.close();
+        fstream.close();
+        this._persistOpenMap = JSON.parse(data);
+      }
+    }
+
+    this._rebuild();
+    aTree.view = this;
+  },
+
+  shutdown: function dtv_shutdown(aJSONFile)
+  {
+    // Write out the persistOpenMap to our JSON file.
+    if (aJSONFile)
+    {
+      // Write out our json file...
+      let data = JSON.stringify(this._persistOpenMap);
+      let file = Components.classes["@mozilla.org/file/directory_service;1"]
+                           .getService(Components.interfaces.nsIProperties)
+                           .get("ProfD", Components.interfaces.nsIFile);
+      file.append(aJSONFile);
+      let foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+                               .createInstance(Components.interfaces.nsIFileOutputStream);
+
+      foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0);
+      foStream.write(data, data.length);
+      foStream.close();
+    }
+  },
+
+  // Override the dnd methods for those functions in abDragDrop.js
+  canDrop: function dtv_canDrop(aIndex, aOrientation)
+  {
+    return abDirTreeObserver.canDrop(aIndex, aOrientation);
+  },
+
+  drop: function dtv_drop(aRow, aOrientation)
+  {
+    abDirTreeObserver.onDrop(aRow, aOrientation);
+  },
+
+  getDirectoryAtIndex: function dtv_getDirForIndex(aIndex)
+  {
+    return this._rowMap[aIndex]._directory;
+  },
+
+  getIndexOfDirectory: function dtv_getIndexOfDir(aItem)
+  {
+    for (var i in this._rowMap)
+      if (this._rowMap[i]._directory == aItem)
+        return i;
+
+    return -1;
+  },
+
+  // Override jsTreeView's isContainer, since we want to be able
+  // to react to drag-drop events for all items in the directory
+  // tree.
+  isContainer: function dtv_isContainer(aIndex)
+  {
+    return true;
+  },
+
+  /**
+   * NOTE: This function will result in indeterminate rows being selected.
+   *       Callers should take care to re-select a desired row after calling
+   *       this function.
+   */
+  _rebuild: function dtv__rebuild() {
+    var oldCount = this._rowMap.length;
+    this._rowMap = [];
+
+    var dirEnum = MailServices.ab.directories;
+    while (dirEnum.hasMoreElements())
+      this._rowMap.push(new abDirTreeItem(
+        dirEnum.getNext().QueryInterface(Components.interfaces.nsIAbDirectory)));
+
+    // Sort our addressbooks now.
+
+    const AB_ORDER = ["pab", "mork", "ldap", "mapi+other", "cab"];
+
+    function getDirectoryValue(aDir, aKey)
+    {
+      if (aKey == "ab_type")
+      {
+        if (aDir._directory.URI == kPersonalAddressbookURI)
+          return "pab";
+        if (aDir._directory.URI == kCollectedAddressbookURI)
+          return "cab";
+        if (aDir._directory instanceof Components.interfaces.nsIAbMDBDirectory)
+          return "mork";
+        if (aDir._directory instanceof Components.interfaces.nsIAbLDAPDirectory)
+          return "ldap";
+        return "mapi+other";
+      }
+      else if (aKey == "ab_name")
+      {
+        return aDir._directory.dirName;
+      }
+    }
+
+    function abNameCompare(a, b)
+    {
+      return a.localeCompare(b);
+    }
+
+    function abTypeCompare(a, b)
+    {
+      return (AB_ORDER.indexOf(a) - AB_ORDER.indexOf(b));
+    }
+
+    const SORT_PRIORITY = ["ab_type", "ab_name"];
+    const SORT_FUNCS = [abTypeCompare, abNameCompare];
+
+    function abSort(a, b)
+    {
+      for (let i = 0; i < SORT_FUNCS.length; i++)
+      {
+        let sortBy = SORT_PRIORITY[i];
+        let aValue = getDirectoryValue(a, sortBy);
+        let bValue = getDirectoryValue(b, sortBy);
+
+        if (!aValue && !bValue)
+          continue;
+        if (!aValue)
+          return -1;
+        if (!bValue)
+          return 1;
+        if (aValue != bValue)
+        {
+          let result = SORT_FUNCS[i](aValue, bValue);
+          if (result != 0)
+            return result;
+        }
+      }
+      return 0;
+    }
+
+    this._rowMap.sort(abSort);
+
+    if (this._tree)
+      this._tree.rowCountChanged(0, this._rowMap.length - oldCount);
+
+    this._restoreOpenStates();
+  },
+
+  // nsIAbListener interfaces
+  onItemAdded: function dtv_onItemAdded(aParent, aItem)
+  {
+    if (!(aItem instanceof Components.interfaces.nsIAbDirectory))
+      return;
+    //xxx we can optimize this later
+    this._rebuild();
+
+    if (!this._tree)
+      return;
+
+    // Now select this new item
+    var index = this.getIndexOfDirectory(aItem);
+    if (index > -1)
+      this.selection.select(index);
+  },
+
+  onItemRemoved: function dtv_onItemRemoved(aParent, aItem)
+  {
+    if (!(aItem instanceof Components.interfaces.nsIAbDirectory))
+      return;
+    //xxx we can optimize this later
+    this._rebuild();
+
+    if (!this._tree)
+      return;
+
+    // If we're deleting a top-level address-book, just select the first book.
+    if (aParent.URI == "moz-abdirectory://")
+    {
+      this.selection.select(0);
+      return;
+    }
+
+    // Now select this parent item.
+    var index = this.getIndexOfDirectory(aParent);
+    if (index > -1)
+      this.selection.select(index);
+  },
+
+  onItemPropertyChanged: function dtv_onItemProp(aItem, aProp, aOld, aNew)
+  {
+    if (!(aItem instanceof Components.interfaces.nsIAbDirectory))
+      return;
+
+    var index = this.getIndexOfDirectory(aItem);
+    if (index > -1)
+      this._tree.invalidateRow(index);
+  }
+};
+
+var gDirectoryTreeView = new directoryTreeView();
--- a/suite/mailnews/addrbook/addressbook.js
+++ b/suite/mailnews/addrbook/addressbook.js
@@ -34,25 +34,27 @@
  * 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 ***** */
 
+Components.utils.import("resource:///modules/mailServices.js");
+
 const nsIAbListener = Components.interfaces.nsIAbListener;
 const kPrefMailAddrBookLastNameFirst = "mail.addr_book.lastnamefirst";
+const kPersistCollapseMapStorage = "directoryTree.json";
 
 var cvPrefs = 0;
 var gSearchTimer = null;
 var gStatusText = null;
 var gQueryURIFormat = null;
 var gSearchInput;
-var gDirTree;
 var gSearchBox;
 var gCardViewBox;
 var gCardViewBoxEmail1;
 var gPreviousDirTreeIndex = -1;
 
 // Constants that correspond to choices
 // in Address Book->View -->Show Name as
 const kDisplayName = 0;
@@ -88,45 +90,48 @@ var gAddressBookAbListener = {
           // parent address book when we remove a mailing list.
           //
           // For simple address books we don't need to move up the list, as
           // we want to select the next one upon removal.
           if (directory.isMailList && gPreviousDirTreeIndex > 0)
             --gPreviousDirTreeIndex;
 
           // Now get the parent of the row.
-          var newRow = dirTree.view.getParentIndex(gPreviousDirTreeIndex);
+          var newRow = gDirTree.view.getParentIndex(gPreviousDirTreeIndex);
 
           // if we have no parent (i.e. we are an address book), use the
           // previous index.
           if (newRow == -1)
             newRow = gPreviousDirTreeIndex;
 
           // Fall back to the first address book if we're not in a valid range
-          if (newRow >= dirTree.view.rowCount)
+          if (newRow >= gDirTree.view.rowCount)
             newRow = 0;
 
           // Now select the new item.
-          dirTree.view.selection.select(newRow);
+          gDirTree.view.selection.select(newRow);
         }
       }
     }
     catch (ex) {
     }
   },
   onItemPropertyChanged: function(item, property, oldValue, newValue) {
     // will not be called
   }
 };
 
 function OnUnloadAddressBook()
-{  
-  Components.classes["@mozilla.org/abmanager;1"]
-            .getService(Components.interfaces.nsIAbManager)
-            .removeAddressBookListener(gAddressBookAbListener);
+{
+  MailServices.ab.removeAddressBookListener(gAddressBookAbListener);
+  MailServices.ab.removeAddressBookListener(gDirectoryTreeView);
+
+  // Shutdown the tree view - this will also save the open/collapsed
+  // state of the tree view to a JSON file.
+  gDirectoryTreeView.shutdown(kPersistCollapseMapStorage);
 
   CloseAbView();
 }
 
 var gAddressBookAbViewListener = {
   onSelectionChanged: function() {
     ResultsPaneSelectionChanged();
   },
@@ -156,52 +161,44 @@ function OnLoadAddressBook()
   OnLoadCardView();
 
   // Before and after callbacks for the customizeToolbar code
   var abToolbox = getAbToolbox();
   abToolbox.customizeInit = AbToolboxCustomizeInit;
   abToolbox.customizeDone = AbToolboxCustomizeDone;
   abToolbox.customizeChange = AbToolboxCustomizeChange;
 
-  //workaround - add setTimeout to make sure dynamic overlays get loaded first
-  setTimeout(OnLoadDirTree, 0);
+  // Initialize the Address Book tree view
+  gDirectoryTreeView.init(gDirTree, kPersistCollapseMapStorage);
+
+  SelectFirstAddressBook();
 
   // if the pref is locked disable the menuitem New->LDAP directory
   if (gPrefs.prefIsLocked("ldap_2.disable_button_add"))
     document.getElementById("addLDAP").setAttribute("disabled", "true");
 
   // Add a listener, so we can switch directories if the current directory is
   // deleted. This listener cares when a directory (= address book), or a
   // directory item is/are removed. In the case of directory items, we are
   // only really interested in mailing list changes and not cards but we have
   // to have both.
-  Components.classes["@mozilla.org/abmanager;1"]
-            .getService(Components.interfaces.nsIAbManager)
-            .addAddressBookListener(gAddressBookAbListener,
-                                    nsIAbListener.directoryRemoved |
-                                    nsIAbListener.directoryItemRemoved);
+  MailServices.ab.addAddressBookListener(gAddressBookAbListener,
+                                         nsIAbListener.directoryRemoved,
+                                         nsIAbListener.directoryItemRemoved);
+  MailServices.ab.addAddressBookListener(gDirectoryTreeView, nsIAbListener.all);
 
-  var dirTree = GetDirTree();
-  dirTree.addEventListener("click",DirPaneClick,true);
-  dirTree.controllers.appendController(DirPaneController);
+  gDirTree.controllers.appendController(DirPaneController);
 
   // Ensure we don't load xul error pages into the main window
   window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
         .getInterface(Components.interfaces.nsIWebNavigation)
         .QueryInterface(Components.interfaces.nsIDocShell)
         .useErrorPages = false;
 }
 
-function OnLoadDirTree() {
-  var treeBuilder = dirTree.builder.QueryInterface(Components.interfaces.nsIXULTreeBuilder);
-  treeBuilder.addObserver(abDirTreeObserver);
-
-  SelectFirstAddressBook();
-}
-
 function GetCurrentPrefs()
 {
 	// prefs
 	if ( cvPrefs == 0 )
 		cvPrefs = new Object;
 
 	cvPrefs.prefs = gPrefs;
 	
@@ -381,19 +378,17 @@ function AbPrintPreviewAddressBook()
 
 function AbExport()
 {
   try {
     var selectedABURI = GetSelectedDirectory();
     if (!selectedABURI) return;
     
     var directory = GetDirectoryFromURI(selectedABURI);
-    Components.classes["@mozilla.org/abmanager;1"]
-              .getService(Components.interfaces.nsIAbManager)
-              .exportAddressBook(window, directory);
+    MailServices.ab.exportAddressBook(window, directory);
   }
   catch (ex) {
     var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
 
     if (promptService) {
       var message;
       switch (ex.result) {
         case Components.results.NS_ERROR_FILE_ACCESS_DENIED:
--- a/suite/mailnews/addrbook/addressbook.xul
+++ b/suite/mailnews/addrbook/addressbook.xul
@@ -73,16 +73,18 @@
     onunload="OnUnloadAddressBook()">
 
   <stringbundleset id="stringbundleset">
     <stringbundle id="bundle_addressBook" src="chrome://messenger/locale/addressbook/addressBook.properties"/>
     <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
     <stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
   </stringbundleset>
 
+<script type="application/javascript" src="chrome://messenger/content/jsTreeView.js"/>
+<script type="application/javascript" src="chrome://messenger/content/addressbook/abTrees.js"/>
 <script type="application/javascript" src="chrome://messenger/content/accountUtils.js"/>
 <script type="application/javascript" src="chrome://messenger/content/widgetglue.js"/>
 <script type="application/javascript" src="chrome://messenger/content/addressbook/addressbook.js"/>
 <script type="application/javascript" src="chrome://messenger/content/addressbook/abCommon.js"/>
 <script type="application/javascript" src="chrome://communicator/content/contentAreaClick.js"/>
 <script type="application/javascript" src="chrome://global/content/printUtils.js"/>
 <script type="application/javascript" src="chrome://messenger/content/msgPrintEngine.js"/>
 
--- a/suite/mailnews/jar.mn
+++ b/suite/mailnews/jar.mn
@@ -102,16 +102,17 @@ messenger.jar:
     content/messenger/addressbook/abCardOverlay.xul                            (addrbook/abCardOverlay.xul)
     content/messenger/addressbook/abCardViewOverlay.js                         (addrbook/abCardViewOverlay.js)
     content/messenger/addressbook/abCardViewOverlay.xul                        (addrbook/abCardViewOverlay.xul)
     content/messenger/addressbook/abMailListDialog.xul                         (addrbook/abMailListDialog.xul)
     content/messenger/addressbook/abEditListDialog.xul                         (addrbook/abEditListDialog.xul)
     content/messenger/addressbook/abListOverlay.xul                            (addrbook/abListOverlay.xul)
     content/messenger/addressbook/abSelectAddressesDialog.js                   (addrbook/abSelectAddressesDialog.js)
     content/messenger/addressbook/abSelectAddressesDialog.xul                  (addrbook/abSelectAddressesDialog.xul)
+    content/messenger/addressbook/abTrees.js                                   (addrbook/abTrees.js)
     content/messenger/addressbook/addressbook-panel.xul                        (addrbook/addressbook-panel.xul)
     content/messenger/addressbook/addressbook-panel.js                         (addrbook/addressbook-panel.js)
     content/messenger/addressbook/pref-addressing.js                           (addrbook/prefs/pref-addressing.js)
     content/messenger/addressbook/pref-addressing.xul                          (addrbook/prefs/pref-addressing.xul)
 #ifdef XP_MACOSX
     content/messenger/platformMailnewsOverlay.xul                              (mac/platformMailnewsOverlay.xul)
 #else
 #ifdef XP_WIN32