Bug 1636531 - Enable search in multiple/all address books. r=frg
authorIan Neal <iann_cvs@blueyonder.co.uk>
Wed, 06 May 2020 00:45:49 +0100
changeset 39224 4bf4b3ce8d53355b8d02452c9d1b99da0fac95d0
parent 39223 f349036e7311c828d5602a6791b15098a01537a2
child 39225 94a426143805198f756b6fc772aced706f8d94d4
push id402
push userclokep@gmail.com
push dateMon, 29 Jun 2020 20:48:04 +0000
reviewersfrg
bugs1636531, 170270, 1140768, 1143812, 1142705, 824150, 691141, 1319409
Bug 1636531 - Enable search in multiple/all address books. r=frg Port the relevant parts of the following bugs to SeaMonkey: * Bug 170270 - Enable search in multiple/all address books * Bug 1140768 - Fix typo of "gShowAbColumnInComposeSidbar" in abContactsPanel.js and improve the logic of hiding the "addressbook" column * Bug 1143812 - Always allow creating new contacts and lists after opening addressbook by using Personal Addressbook when a usable AB is not selected * Bug 1142705 - Contacts sidebar doesn't remember the last selected address book after Bug 170270 checked in * Bug 824150 - Code cleanup in /mail/ and /mailnews/ * Bug 691141 - rework AB contact photo storing UI * Bug 1319409 - Rename GetSelectedDirectory() function to getSelectedDirURI(), and various related code cleanup/refactoring
suite/locales/en-US/chrome/mailnews/addressbook/abMainWindow.dtd
suite/locales/en-US/chrome/mailnews/addressbook/abResultsPaneOverlay.dtd
suite/locales/en-US/chrome/mailnews/addressbook/addressBook.properties
suite/mailnews/components/addrbook/content/abCardOverlay.js
suite/mailnews/components/addrbook/content/abCommon.js
suite/mailnews/components/addrbook/content/abResultsPaneOverlay.xul
suite/mailnews/components/addrbook/content/abSelectAddressesDialog.js
suite/mailnews/components/addrbook/content/abTrees.js
suite/mailnews/components/addrbook/content/addressbook-panel.js
suite/mailnews/components/addrbook/content/addressbook-panel.xul
suite/mailnews/components/addrbook/content/addressbook.js
suite/mailnews/components/addrbook/content/addressbook.xul
suite/themes/classic/mac/messenger/addressbook/addressbook.css
suite/themes/classic/messenger/addressbook/addressbook.css
suite/themes/modern/messenger/addressbook/addressbook.css
--- a/suite/locales/en-US/chrome/mailnews/addressbook/abMainWindow.dtd
+++ b/suite/locales/en-US/chrome/mailnews/addressbook/abMainWindow.dtd
@@ -1,71 +1,67 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <!-- Title -->
 <!ENTITY addressbookWindow.title                        "Address Book">
-
-<!-- Menus:  the . means that the menu item isn't implemented yet -->
-
+<!ENTITY blankResultsPaneMessage.label                  "This address book shows contacts only after a search">
+<!ENTITY localResultsOnlyMessage.label                  "Contacts from remote address books are not shown until you search">
 <!-- File Menu -->
 <!ENTITY newContact.label                               "Contact…">
-<!-- LOCALIZATION NOTE (newContact.accesskey) : DONT_TRANSLATE -->
 <!ENTITY newContact.accesskey                           "C">
+<!ENTITY newContact.key                                 "N">
 <!ENTITY newListCmd.label                               "Mailing List…">
-<!-- LOCALIZATION NOTE (newListCmd.accesskey) : DONT_TRANSLATE -->
 <!ENTITY newListCmd.accesskey                           "L">
 <!ENTITY newAddressBookCmd.label                        "Address Book…">
 <!ENTITY newAddressBookCmd.accesskey                    "o">
 <!ENTITY newLDAPDirectoryCmd.label                      "LDAP Directory…">
-<!-- LOCALIZATION NOTE (newLDAPDirectoryCmd.accesskey) : DONT_TRANSLATE -->
 <!ENTITY newLDAPDirectoryCmd.accesskey                  "D">
 <!ENTITY newIM.label                                    "IM">
 <!ENTITY printContactViewCmd.label                      "Print Contact…">
-<!ENTITY printContactViewCmd.accesskey                  "p">
+<!ENTITY printContactViewCmd.accesskey                  "P">
+<!ENTITY printContactViewCmd.key                        "P">
 <!ENTITY printPreviewContactViewCmd.label               "Print Preview Contact">
 <!ENTITY printPreviewContactViewCmd.accesskey           "v">
-<!ENTITY printContactViewCmd.key                        "P">
 <!ENTITY printAddressBook.label                         "Print Address Book…">
 <!ENTITY printAddressBook.accesskey                     "A">
 <!ENTITY printPreviewAddressBook.label                  "Print Preview Address Book">
 <!ENTITY printPreviewAddressBook.accesskey              "B">
 
 <!-- Edit Menu -->
 <!ENTITY deleteAbCmd.label                              "Delete Address Book">
 <!ENTITY deleteContactCmd.label                         "Delete Contact">
 <!ENTITY deleteContactsCmd.label                        "Delete Selected Contacts">
 <!ENTITY deleteListCmd.label                            "Delete List">
 <!ENTITY deleteListsCmd.label                           "Delete Selected Lists">
 <!ENTITY deleteItemsCmd.label                           "Delete Selected Items">
 <!ENTITY swapFirstNameLastNameCmd.label                 "Swap First/Last Name">
 <!ENTITY swapFirstNameLastNameCmd.accesskey             "w">
 <!ENTITY propertiesCmd.label                            "Properties…">
-<!-- LOCALIZATION NOTE (propertiesCmd.accesskey) : DONT_TRANSLATE -->
 <!ENTITY propertiesCmd.accesskey                        "i">
 <!ENTITY propertiesCmd.key                              "i">
 
 <!-- View Menu -->
 <!ENTITY showAbToolbarCmd.label                         "Address Book Toolbar">
 <!ENTITY showAbToolbarCmd.accesskey                     "o">
 <!ENTITY layoutMenu.label                               "Layout">
 <!ENTITY layoutMenu.accesskey                           "L">
 <!ENTITY showDirectoryPane.label                        "Directory Pane">
 <!ENTITY showDirectoryPane.accesskey                    "D">
 <!ENTITY showContactPane2.label                         "Contact Pane">
 <!ENTITY showContactPane2.accesskey                     "C">
 <!ENTITY menu_ShowNameAs.label                          "Show Name As">
 <!ENTITY menu_ShowNameAs.accesskey                      "n">
 <!ENTITY firstLastCmd.label                             "First Last">
-<!ENTITY firstLastCmd.accesskey                         "f">
+<!ENTITY firstLastCmd.accesskey                         "F">
 <!ENTITY lastFirstCmd.label                             "Last, First">
-<!ENTITY lastFirstCmd.accesskey                         "l">
+<!ENTITY lastFirstCmd.accesskey                         "L">
 <!ENTITY displayNameCmd.label                           "Display Name">
-<!ENTITY displayNameCmd.accesskey                       "d">
+<!ENTITY displayNameCmd.accesskey                       "D">
 <!-- LOCALIZATION NOTE (toggleDirectoryPaneCmd.key): This is only used on the
      mac platform, other platforms use VK_F9. -->
 <!ENTITY toggleDirectoryPaneCmd.key                     "S">
 
 <!-- Tasks Menu -->
 <!ENTITY importCmd.label                                "Import…">
 <!ENTITY importCmd.accesskey                            "I">
 <!ENTITY exportCmd.label                                "Export…">
--- a/suite/locales/en-US/chrome/mailnews/addressbook/abResultsPaneOverlay.dtd
+++ b/suite/locales/en-US/chrome/mailnews/addressbook/abResultsPaneOverlay.dtd
@@ -1,12 +1,14 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
+<!ENTITY Addrbook.label                        "Address Book">
+<!ENTITY Addrbook.accesskey                    "B">
 <!ENTITY GeneratedName.label                   "Name">
 <!ENTITY GeneratedName.accesskey               "N">
 <!ENTITY PrimaryEmail.label                    "Email">
 <!ENTITY PrimaryEmail.accesskey                "E">
 <!ENTITY Company.label                         "Organization">
 <!ENTITY Company.accesskey                     "z">
 <!ENTITY _PhoneticName.label                   "Phonetic Name">
 <!ENTITY _PhoneticName.accesskey               "o">
--- a/suite/locales/en-US/chrome/mailnews/addressbook/addressBook.properties
+++ b/suite/locales/en-US/chrome/mailnews/addressbook/addressBook.properties
@@ -7,16 +7,18 @@
 #
 
 ## LOCALIZATION NOTE (mailingListTitleEdit): %S will be replaced by the Mailing List's display name
 mailingListTitleEdit=Edit %S
 emptyListName=You must enter a list name.
 lastFirstFormat=%S, %S
 firstLastFormat=%S %S
 
+allAddressBooks=All Address Books
+
 newContactTitle=New Contact
 # %S will be the card's display name
 newContactTitleWithDisplayName=New Contact for %S
 editContactTitle=Edit Contact
 # %S will be the card's display name
 editContactTitleWithDisplayName=Edit Contact for %S
 # don't translate vCard
 editVCardTitle=Edit vCard
--- a/suite/mailnews/components/addrbook/content/abCardOverlay.js
+++ b/suite/mailnews/components/addrbook/content/abCardOverlay.js
@@ -152,30 +152,46 @@ function OnLoadNewCard()
   GetCardValues(gEditCard.card, document);
 
   // FIX ME - looks like we need to focus on both the text field and the tab widget
   // probably need to do the same in the addressing widget
 
   // focus on first or last name based on the pref
   var focus = document.getElementById(gEditCard.displayLastNameFirst
                                       ? "LastName" : "FirstName");
-  if ( focus ) {
+  if (focus) {
     // XXX Using the setTimeout hack until bug 103197 is fixed
     setTimeout( function(firstTextBox) { firstTextBox.focus(); }, 0, focus );
   }
 }
 
+/**
+ * Get the source directory containing the card we are editing.
+ */
+function getContainingDirectory() {
+  let directory = GetDirectoryFromURI(gEditCard.abURI);
+  // If the source directory is "All Address Books", find the parent
+  // address book of the card being edited and reflect the changes in it.
+  if (directory.URI == kAllDirectoryRoot + "?") {
+    let dirId =
+      gEditCard.card.directoryId
+                    .substring(0, gEditCard.card.directoryId.indexOf("&"));
+    directory = MailServices.ab.getDirectoryFromId(dirId);
+  }
+  return directory;
+}
+
 function EditCardOKButton()
 {
   if (!CheckCardRequiredDataPresence(document))
     return false;  // don't close window
 
   // See if this card is in any mailing list
   // if so then we need to update the addresslists of those mailing lists
-  var directory = GetDirectoryFromURI(gEditCard.abURI);
+  let directory = getContainingDirectory();
 
   // if the directory is a mailing list we need to search all the mailing lists
   // in the parent directory if the card exists.
   if (directory.isMailList) {
     var parentURI = GetParentDirectoryFromMailingListURI(gEditCard.abURI);
     directory = GetDirectoryFromURI(parentURI);
   }
 
--- a/suite/mailnews/components/addrbook/content/abCommon.js
+++ b/suite/mailnews/components/addrbook/content/abCommon.js
@@ -6,16 +6,19 @@ var {Services} = ChromeUtils.import("res
 const {MailServices} = ChromeUtils.import("resource:///modules/MailServices.jsm");
 const {IOUtils} = ChromeUtils.import("resource:///modules/IOUtils.js");
 
 var gDirTree = null;
 var abList = null;
 var gAbResultsTree = null;
 var gAbView = null;
 var gAddressBookBundle;
+// A boolean variable determining whether AB column should be shown in AB
+// sidebar in compose window.
+var gShowAbColumnInComposeSidebar = false;
 
 const kDefaultSortColumn = "GeneratedName";
 const kDefaultAscending = "ascending";
 const kDefaultDescending = "descending";
 // kDefaultYear will be used in birthday calculations when no year is given;
 // this is a leap year so that Feb 29th works.
 const kDefaultYear = nearestLeap(new Date().getFullYear());
 const kMaxYear = 9999;
@@ -35,16 +38,17 @@ var DirPaneController =
     switch (command) {
       case "cmd_selectAll":
       case "cmd_delete":
       case "button_delete":
       case "cmd_properties":
       case "cmd_printcard":
       case "cmd_printcardpreview":
       case "cmd_newlist":
+      case "cmd_newCard":
         return true;
       default:
         return false;
     }
   },
 
   isCommandEnabled: function(command)
   {
@@ -64,17 +68,18 @@ var DirPaneController =
         // Context-sensitive labels for Edit > Delete menuitem.
         if (command == "cmd_delete") {
           goSetMenuValue(command, selectedDir.isMailList ?
                                   "valueList" : "valueAddressBook");
         }
 
         // If it's one of these special ABs, return false to disable deletion.
         if (selectedDirURI == kPersonalAddressbookURI ||
-            selectedDirURI == kCollectedAddressbookURI)
+            selectedDirURI == kCollectedAddressbookURI ||
+            selectedDirURI == (kAllDirectoryRoot + "?"))
           return false;
 
         // If the directory is a mailing list, and it is read-only,
         // return false to disable deletion.
         if (selectedDir.isMailList && selectedDir.readOnly)
           return false;
 
         // If the selected directory is an ldap directory,
@@ -97,16 +102,17 @@ var DirPaneController =
         return true;
       }
       case "cmd_printcard":
       case "cmd_printcardpreview":
         return (GetSelectedCardIndex() != -1);
       case "cmd_properties":
         return (getSelectedDirectoryURI() != null);
       case "cmd_newlist":
+      case "cmd_newCard":
         return true;
       default:
         return false;
     }
   },
 
   doCommand: function(command)
   {
@@ -122,16 +128,19 @@ var DirPaneController =
           AbDeleteSelectedDirectory();
         break;
       case "cmd_properties":
         AbEditSelectedDirectory();
         break;
       case "cmd_newlist":
         AbNewList();
         break;
+      case "cmd_newCard":
+        AbNewCard();
+        break;
     }
   },
 
   onEvent: function(event)
   {
     // on blur events set the menu item texts back to the normal values
     if (event == "blur")
       goSetMenuValue("cmd_delete", "valueDefault");
@@ -231,16 +240,22 @@ function AbDeleteDirectory(aURI)
   MailServices.ab.deleteAddressBook(aURI);
 }
 
 function InitCommonJS()
 {
   gDirTree = document.getElementById("dirTree");
   abList = document.getElementById("addressbookList");
   gAddressBookBundle = document.getElementById("bundle_addressBook");
+
+  // Make an entry for "All Address Books".
+  if (abList) {
+    abList.insertItemAt(0, gAddressBookBundle.getString("allAddressBooks"),
+                        kAllDirectoryRoot + "?");
+  }
 }
 
 function UpgradeAddressBookResultsPaneUI(prefName)
 {
   // placeholder in case any new columns get added to the address book
   // var resultsPaneUIVersion = Services.prefs.getIntPref(prefName);
 }
 
@@ -266,19 +281,41 @@ function AbDelete()
     if (types == kListsAndCards)
       confirmDeleteMessage = gAddressBookBundle.getString("confirmDeleteListsAndContacts");
     else if (types == kMultipleListsOnly)
       confirmDeleteMessage = gAddressBookBundle.getString("confirmDeleteMailingLists");
     else if (types == kSingleListOnly)
       confirmDeleteMessage = gAddressBookBundle.getString("confirmDeleteMailingList");
   }
 
-  if (confirmDeleteMessage &&
-      Services.prompt.confirm(window, null, confirmDeleteMessage))
+  if (!confirmDeleteMessage ||
+      !Services.prompt.confirm(window, null, confirmDeleteMessage)) {
+    return;
+  }
+
+  if (getSelectedDirectoryURI() == (kAllDirectoryRoot + "?")) {
+    // Delete cards from "All Address Books" view.
+    let cards = GetSelectedAbCards();
+    for (let i = 0; i < cards.length; i++) {
+      let dirId = cards[i].directoryId
+                          .substring(0, cards[i].directoryId.indexOf("&"));
+      let directory = MailServices.ab.getDirectoryFromId(dirId);
+
+      let cardArray =
+        Cc["@mozilla.org/array;1"]
+          .createInstance(Ci.nsIMutableArray);
+      cardArray.appendElement(cards[i], false);
+      if (directory)
+        directory.deleteCards(cardArray);
+    }
+    SetAbView(kAllDirectoryRoot + "?");
+  } else {
+    // Delete cards from address books or mailing lists.
     gAbView.deleteSelectedCards();
+  }
 }
 
 function AbNewCard()
 {
   goNewCardDialog(getSelectedDirectoryURI());
 }
 
 function AbEditCard(card)
@@ -452,21 +489,26 @@ function DirPaneDoubleClick(event)
       gDirTree.view.selection.count == 1 &&
       getSelectedDirectory().isMailList) {
     AbEditSelectedDirectory();
   }
 }
 
 function DirPaneSelectionChange()
 {
+  let uri = getSelectedDirectoryURI();
   // clear out the search box when changing folders...
   onAbClearSearch(false);
   if (gDirectoryTreeView.selection &&
       gDirectoryTreeView.selection.count == 1) {
-    ChangeDirectoryByURI(getSelectedDirectoryURI());
+    ChangeDirectoryByURI(uri);
+    document.getElementById("localResultsOnlyMessage")
+            .setAttribute("hidden",
+                          !gDirectoryTreeView.hasRemoteAB ||
+                          uri != kAllDirectoryRoot + "?");
   }
 }
 
 function ChangeDirectoryByURI(uri = kPersonalAddressbookURI)
 {
   SetAbView(uri);
 
   // Actively de-selecting if there are any pre-existing selections
@@ -539,20 +581,20 @@ function InitViewSortByMenu()
     var sortDirection = kDefaultAscending;
 
     if (gAbView) {
       sortColumn = gAbView.sortColumn;
       sortDirection = gAbView.sortDirection;
     }
 
     // this approach is necessary to support generic columns that get overlayed.
-    var elements = document.getElementsByAttribute("name","sortas");
-    for (var i=0; i<elements.length; i++) {
-      let cmd = elements[i].getAttribute("id");
-      let columnForCmd = cmd.split("cmd_SortBy")[1];
+    let elements = document.querySelectorAll('[name="sortas"]');
+    for (let i = 0; i < elements.length; i++) {
+      let cmd = elements[i].id;
+      let columnForCmd = cmd.substr(10); // everything right of cmd_SortBy
       setSortByMenuItemCheckState(cmd, (sortColumn == columnForCmd));
     }
 
     setSortByMenuItemCheckState("sortAscending", (sortDirection == kDefaultAscending));
     setSortByMenuItemCheckState("sortDescending", (sortDirection == kDefaultDescending));
 }
 
 function GenerateAddressFromCard(card)
--- a/suite/mailnews/components/addrbook/content/abResultsPaneOverlay.xul
+++ b/suite/mailnews/components/addrbook/content/abResultsPaneOverlay.xul
@@ -68,17 +68,21 @@
     <splitter class="tree-splitter"/>
     <treecol id="HomePhone"
              persist="hidden ordinal width sortDirection" flex="1"
              label="&HomePhone.label;" hidden="true"/>
     <splitter class="tree-splitter"/>
     <treecol id="WorkPhone"
              persist="hidden ordinal width sortDirection" flex="1"
              label="&WorkPhone.label;"/>
-
+    <splitter class="tree-splitter"/>
+    <treecol id="addrbook"
+             persist="hidden ordinal width sortDirection" flex="1"
+             hidden="true"
+             label="&Addrbook.label;"/>
     <!-- LOCALIZATION NOTE: _PhoneticName may be enabled for Japanese builds. -->
     <!--
     <splitter class="tree-splitter"/>
     <treecol id="_PhoneticName"
              persist="hidden ordinal width sortDirection" flex="1"
              label="&_PhoneticName.label;" hidden="true"/>
      -->
 
--- a/suite/mailnews/components/addrbook/content/abSelectAddressesDialog.js
+++ b/suite/mailnews/components/addrbook/content/abSelectAddressesDialog.js
@@ -380,9 +380,20 @@ function onEnterInSearchBar()
 function DirPaneSelectionChangeMenulist()
 {
   if (abList && abList.selectedItem) {
     if (gSearchInput.value && (gSearchInput.value != ""))
       onEnterInSearchBar();
     else
       ChangeDirectoryByURI(abList.value);
   }
+
+  // Hide the addressbook column if the selected addressbook isn't
+  // "All address books". Since the column is redundant in all other cases.
+  let addrbookColumn = document.getElementById("addrbook");
+  if (abList.value.startsWith(kAllDirectoryRoot + "?")) {
+    addrbookColumn.hidden = !gShowAbColumnInComposeSidebar;
+    addrbookColumn.removeAttribute("ignoreincolumnpicker");
+  } else {
+    addrbookColumn.hidden = true;
+    addrbookColumn.setAttribute("ignoreincolumnpicker", "true");
+  }
 }
--- a/suite/mailnews/components/addrbook/content/abTrees.js
+++ b/suite/mailnews/components/addrbook/content/abTrees.js
@@ -4,16 +4,75 @@
 
 /**
  * This file contains our implementation for various addressbook trees.  It
  * depends on jsTreeView.js being loaded before this script is loaded.
  */
 
 const {IOUtils} = ChromeUtils.import("resource:///modules/IOUtils.js");
 
+// Tree Sort helper methods.
+var AB_ORDER = ["aab", "pab", "mork", "ldap", "mapi+other", "anyab", "cab"];
+
+function getDirectoryValue(aDir, aKey) {
+  if (aKey == "ab_type") {
+    if (aDir._directory.URI == kAllDirectoryRoot + "?")
+      return "aab";
+    if (aDir._directory.URI == kPersonalAddressbookURI)
+      return "pab";
+    if (aDir._directory.URI == kCollectedAddressbookURI)
+      return "cab";
+    if (aDir._directory instanceof Ci.nsIAbMDBDirectory)
+      return "mork";
+    if (aDir._directory instanceof Ci.nsIAbLDAPDirectory)
+      return "ldap";
+
+    // If there is any other AB type.
+    return "mapi+other";
+  } else if (aKey == "ab_name") {
+    return aDir._directory.dirName;
+  }
+
+  // This should never happen.
+  return null;
+}
+
+function abNameCompare(a, b) {
+  return a.localeCompare(b);
+}
+
+function abTypeCompare(a, b) {
+  return (AB_ORDER.indexOf(a) - AB_ORDER.indexOf(b));
+}
+
+var SORT_PRIORITY = ["ab_type", "ab_name"];
+var 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)
+      return 0;
+    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;
+}
+
 /**
  * Each abDirTreeItem corresponds to one row in the tree view.
  */
 function abDirTreeItem(aDirectory)
 {
   this._directory = aDirectory;
 }
 
@@ -42,32 +101,37 @@ abDirTreeItem.prototype =
   },
 
   _children: null,
   get children()
   {
     if (!this._children)
     {
       this._children = [];
-      var myEnum = this._directory.childNodes;
+      let myEnum;
+      if (this._directory.URI == (kAllDirectoryRoot + "?"))
+        myEnum = MailServices.ab.directories;
+      else
+        myEnum = this._directory.childNodes;
+
       while (myEnum.hasMoreElements())
       {
         var abItem = new abDirTreeItem(
           myEnum.getNext().QueryInterface(Ci.nsIAbDirectory));
+        if (gDirectoryTreeView&&
+            this.id == kAllDirectoryRoot + "?" &&
+            getDirectoryValue(abItem, "ab_type") == "ldap")
+          gDirectoryTreeView.hasRemoteAB = true;
+
         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);
+      this._children.sort(abSort);
     }
     return this._children;
   },
 
   getProperties: function atv_getProps()
   {
     var properties = []
     if (this._directory.isMailList)
@@ -83,16 +147,18 @@ abDirTreeItem.prototype =
 /**
  * Our actual implementation of nsITreeView.
  */
 function directoryTreeView() {}
 directoryTreeView.prototype =
 {
   __proto__: new PROTO_TREE_VIEW(),
 
+  hasRemoteAB: false,
+
   init: function dtv_init(aTree, aJSONFile)
   {
     if (aJSONFile) {
       // Parse our persistent-open-state json file
       let data = IOUtils.loadFileToString(aJSONFile);
       if (data) {
         this._persistOpenMap = JSON.parse(data);
       }
@@ -149,88 +215,36 @@ directoryTreeView.prototype =
   /**
    * 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() {
     this._rowMap = [];
 
-    var dirEnum = MailServices.ab.directories;
-    while (dirEnum.hasMoreElements())
-      this._rowMap.push(new abDirTreeItem(
-        dirEnum.getNext().QueryInterface(Ci.nsIAbDirectory)));
+    // Make an entry for All Address Books.
+    let rootAB = MailServices.ab.getDirectory(kAllDirectoryRoot + "?");
+    rootAB.dirName = gAddressBookBundle.getString("allAddressBooks");
+    this._rowMap.push(new abDirTreeItem(rootAB));
 
     // 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 Ci.nsIAbMDBDirectory)
-          return "mork";
-        if ("nsIAbLDAPDirectory" in Ci &&
-            aDir._directory instanceof Ci.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);
 
     this._restoreOpenStates();
   },
 
+  getIndexForId: function(aId) {
+    for (let i = 0; i < this._rowMap.length; i++) {
+      if (this._rowMap[i].id == aId)
+        return i;
+    }
+
+    return -1;
+  },
+
   // nsIAbListener interfaces
   onItemAdded: function dtv_onItemAdded(aParent, aItem)
   {
     if (!(aItem instanceof Ci.nsIAbDirectory))
       return;
 
     var oldCount = this._rowMap.length;
     var tree = this._tree;
@@ -257,16 +271,25 @@ directoryTreeView.prototype =
     var tree = this._tree;
     this._tree = null;
     this._rebuild();
     if (!tree)
       return;
 
     this._tree = tree;
     tree.rowCountChanged(itemIndex, this._rowMap.length - oldCount);
+
+    // This does not currently work, see Bug 1323563.
+    // If we're deleting a top-level address-book, just select the first book.
+    // if (aParent.URI == kAllDirectoryRoot ||
+    //     aParent.URI == kAllDirectoryRoot + "?") {
+    //   this.selection.select(0);
+    //   return;
+    // }
+
     var parentIndex = this.getIndexOfDirectory(aParent);
     if (parentIndex > -1)
       tree.invalidateRow(parentIndex);
 
     if (!this.selection.count)
     {
       // The previously selected item was a member of the deleted subtree.
       // Select the parent of the subtree.
--- a/suite/mailnews/components/addrbook/content/addressbook-panel.js
+++ b/suite/mailnews/components/addrbook/content/addressbook-panel.js
@@ -5,16 +5,18 @@
 var gIsMsgCompose = false;
 
 function GetAbViewListener()
 {
   // the ab panel doesn't care if the total changes, or if the selection changes
   return null;
 }
 
+var mutationObs = null;
+
 function AbPanelLoad()
 {
   InitCommonJS();
 
   UpgradeAddressBookResultsPaneUI("mailnews.ui.addressbook_panel_results.version");
 
   var abPopup = document.getElementById('addressbookList');
 
@@ -23,31 +25,49 @@ function AbPanelLoad()
   var temp = abPopup.value;
   abPopup.selectedItem = null;
   abPopup.value = temp;
   if (!abPopup.selectedItem)
     abPopup.selectedIndex = 0;
 
   ChangeDirectoryByURI(abPopup.value);
 
+  mutationObs = new MutationObserver(function(aMutations) {
+    aMutations.forEach(function(mutation) {
+      if (getSelectedDirectoryURI() == (kAllDirectoryRoot + "?") &&
+          mutation.type == "attributes" &&
+          mutation.attributeName == "hidden") {
+        let curState = document.getElementById("addrbook").hidden;
+        gShowAbColumnInComposeSidebar = !curState;
+      }
+    });
+  });
+
+  document.getElementById("addrbook").hidden = !gShowAbColumnInComposeSidebar;
+
+  mutationObs.observe(document.getElementById("addrbook"),
+                      { attributes: true, childList: true });
+
   gSearchInput = document.getElementById("searchInput");
 
   // for the compose window we want to show To, Cc, Bcc and a separator
   // for all other windows we want to show Compose Mail To
   var popup = document.getElementById("composeMail");
   gIsMsgCompose = parent.document
                         .documentElement
                         .getAttribute("windowtype") == "msgcompose";
   for (var i = 0; i < 4; i++)
     popup.childNodes[i].hidden = !gIsMsgCompose;
   popup.childNodes[4].hidden = gIsMsgCompose;
 }
 
 function AbPanelUnload()
 {
+  mutationObs.disconnect();
+
   CloseAbView();
 }
 
 function AbPanelAdd(addrtype)
 {
   var cards = GetSelectedAbCards();
   var count = cards.length;
 
--- a/suite/mailnews/components/addrbook/content/addressbook-panel.xul
+++ b/suite/mailnews/components/addrbook/content/addressbook-panel.xul
@@ -76,16 +76,20 @@
 
     <tree id="abResultsTree" flex="1" context="composeMail" onclick="AbResultsPaneOnClick(event);" class="plain"
           sortCol="GeneratedName" persist="sortCol">
   <treecols>
     <!-- these column ids must match up to the mork column names, see nsIAddrDatabase.idl -->
     <treecol id="GeneratedName"
           persist="hidden ordinal width sortDirection" flex="1" label="&GeneratedName.label;" primary="true"/>
     <splitter class="tree-splitter"/>
+    <treecol id="addrbook"
+             persist="hidden ordinal width sortDirection" hidden="true"
+             flex="1" label="&Addrbook.label;"/>
+    <splitter class="tree-splitter"/>
     <treecol id="PrimaryEmail"
           persist="hidden ordinal width sortDirection"
       hiddenbydefault="true"
       flex="1" label="&PrimaryEmail.label;"/>
   </treecols>
   <treechildren ondragstart="nsDragAndDrop.startDrag(event, abResultsPaneObserver);"/>
 </tree>
 
--- a/suite/mailnews/components/addrbook/content/addressbook.js
+++ b/suite/mailnews/components/addrbook/content/addressbook.js
@@ -121,17 +121,16 @@ function GetCurrentPrefs()
     menuitem.setAttribute('checked', 'true');
 
   // show phonetic fields if indicated by the pref
   if (GetLocalizedStringPref("mail.addr_book.show_phonetic_fields") == "true")
     document.getElementById("cmd_SortBy_PhoneticName")
             .setAttribute("hidden", "false");
 }
 
-
 function SetNameColumn(cmd)
 {
   var prefValue;
 
   switch (cmd)
   {
   case 'firstLastCmd':
     prefValue = kFirstNameFirst;
@@ -348,17 +347,20 @@ function SetStatusText(total)
         statusText = gAddressBookBundle.getString("noMatchFound");
       } else {
         statusText = PluralForm
           .get(total, gAddressBookBundle.getString("matchesFound1"))
           .replace("#1", total);
       }
     }
     else
-      statusText = gAddressBookBundle.getFormattedString("totalContactStatus", [gAbView.directory.dirName, total]);
+      statusText =
+        gAddressBookBundle.getFormattedString(
+          "totalContactStatus",
+          [getSelectedDirectory().dirName, total]);
 
     gStatusText.setAttribute("label", statusText);
   }
   catch(ex) {
     dump("failed to set status text:  " + ex + "\n");
   }
 }
 
@@ -380,17 +382,16 @@ function onAdvancedAbSearch()
     window.openDialog("chrome://messenger/content/ABSearchDialog.xul", "",
                       "chrome,resizable,status,centerscreen,dialog=no",
                       {directory: selectedDirURI});
 }
 
 function onEnterInSearchBar()
 {
   ClearCardViewPane();
-
   if (!gQueryURIFormat) {
     // Get model query from pref. We don't want the query starting with "?"
     // as we have to prefix "?and" to this format.
     gQueryURIFormat = getModelQuery("mail.addr_book.quicksearchquery.format");
   }
 
   let searchURI = getSelectedDirectoryURI();
   if (!searchURI) return;
@@ -400,16 +401,24 @@ function onEnterInSearchBar()
    already has a query, like
    moz-abldapdirectory://nsdirectory.netscape.com:389/ou=People,dc=netscape,dc=com?(or(Department,=,Applications))
   */
   // Use helper method to split up search query to multi-word search
   // query against multiple fields.
   let searchWords = getSearchTokens(gSearchInput.value);
   searchURI += generateQueryURI(gQueryURIFormat, searchWords);
 
+  if (searchURI == kAllDirectoryRoot)
+    searchURI += "?";
+
+  document.getElementById("localResultsOnlyMessage")
+          .setAttribute("hidden",
+                        !gDirectoryTreeView.hasRemoteAB ||
+                        searchURI != kAllDirectoryRoot + "?");
+
   SetAbView(searchURI);
 
   // XXX todo
   // this works for synchronous searches of local addressbooks,
   // but not for LDAP searches
   SelectFirstCard();
 }
 
--- a/suite/mailnews/components/addrbook/content/addressbook.xul
+++ b/suite/mailnews/components/addrbook/content/addressbook.xul
@@ -65,18 +65,18 @@
                 events="focus,addrbook-select"
                 oncommandupdate="CommandUpdate_AddressBook()"/>
     <commandset id="selectEditMenuItems"/>
     <commandset id="undoEditMenuItems"/>
     <commandset id="globalEditMenuItems"/>
     <command id="cmd_newNavigator"/>
     <command id="cmd_newPrivateWindow"/>
     <command id="cmd_newEditor"/>
-    <command id="cmd_newcard" oncommand="AbNewCard();"/>
     <command id="cmd_newlist" oncommand="AbNewList();"/>
+    <command id="cmd_newCard" oncommand="goDoCommand('cmd_newCard');"/>
     <command id="cmd_newMessage" oncommand="AbNewMessage();"/>
     <command id="cmd_newim" oncommand="AbIMSelected()"/>
     <command id="cmd_printSetup" oncommand="PrintUtils.showPageSetup()"/>
     <command id="cmd_printcard" oncommand="AbPrintCard()"/>
     <command id="cmd_printcardpreview" oncommand="AbPrintPreviewCard()"/>
     <command id="cmd_printAddressBook" oncommand="AbPrintAddressBook()"/>
     <command id="cmd_printPreviewAddressBook" oncommand="AbPrintPreviewAddressBook()"/>
     <command id="cmd_close" oncommand="AbClose()"/>
@@ -113,16 +113,18 @@
   <key id="key_newBlankPage"/>
 #ifdef XP_MACOSX
   <key id="key_newMessage" key="&newMessageCmd.key;"
        modifiers="accel,shift" command="cmd_newMessage"/>
 #else
   <key id="key_newMessage" key="&newMessageCmd.key;"
        modifiers="accel" command="cmd_newMessage"/>
 #endif
+  <key id="key_newCard" key="&newContact.key;" modifiers="accel"
+       command="cmd_newCard"/>
   <key id="key_printCard" key="&printContactViewCmd.key;"
        command="cmd_printcard" modifiers="accel"/>
   <key id="key_close"/>
   <!-- Edit Menu -->
   <key id="key_delete"/>
   <key id="key_delete2"/> <!-- secondary delete key -->
   <key id="key_undo"/>
   <key id="key_redo"/>
@@ -163,17 +165,17 @@
   <menuitem id="dirTreeContext-properties"
             label="&editItemButton.label;"
             accesskey="&editItemButton.accesskey;"
             command="cmd_properties"/>
   <menuseparator/>
   <menuitem id="dirTreeContext-newcard"
             label="&newContactButton.label;"
             accesskey="&newContactButton.accesskey;"
-            command="cmd_newcard"/>
+            command="cmd_newCard"/>
   <menuitem id="dirTreeContext-newlist"
             label="&newlistButton.label;"
             accesskey="&newlistButton.accesskey;"
             command="cmd_newlist"/>
   <menuseparator/>
   <menuitem id="dirTreeContext-delete"
             label="&deleteItemButton.label;"
             accesskey="&deleteItemButton.accesskey;"
@@ -227,17 +229,18 @@
       <menubar id="ab-menubar">
         <menu id="menu_File">
           <menupopup id="menu_FilePopup">
             <menu id="menu_New">
               <menupopup id="menu_NewPopup">
                 <menuitem id="menu_newContact"
                           label="&newContact.label;"
                           accesskey="&newContact.accesskey;"
-                          oncommand="AbNewCard();"/>
+                          key="key_newCard"
+                          command="cmd_newCard"/>
                 <menuitem id="menu_newList"
                           label="&newListCmd.label;"
                           accesskey="&newListCmd.accesskey;"
                           command="cmd_newlist"/>
                 <menuitem id="menu_newAddrbook"
                           label="&newAddressBookCmd.label;"
                           accesskey="&newAddressBookCmd.accesskey;"
                           oncommand="AbNewAddressBook();"/>
@@ -415,16 +418,23 @@
             <menuitem label="&HomePhone.label;"
                                   id="cmd_SortByHomePhone"
                                   accesskey="&HomePhone.accesskey;"
                                   oncommand="SortResultPane('HomePhone');" name="sortas" type="radio" checked="true"/>
             <menuitem label="&WorkPhone.label;"
                                   id="cmd_SortByWorkPhone"
                                   accesskey="&WorkPhone.accesskey;"
                                   oncommand="SortResultPane('WorkPhone');" name="sortas" type="radio" checked="true"/>
+            <menuitem label="&Addrbook.label;"
+                      id="cmd_SortByaddrbook"
+                      accesskey="&Addrbook.accesskey;"
+                      oncommand="SortResultPane('addrbook');"
+                      name="sortas"
+                      type="radio"
+                      checked="true"/>
             <menuseparator/>
             <menuitem id="sortAscending" type="radio" name="sortdirection" label="&sortAscending.label;" accesskey="&sortAscending.accesskey;" oncommand="AbSortAscending()"/>
             <menuitem id="sortDescending" type="radio" name="sortdirection" label="&sortDescending.label;" accesskey="&sortDescending.accesskey;" oncommand="AbSortDescending()"/>
                     </menupopup>
                 </menu>
             </menupopup>
         </menu>
         <menu id="tasksMenu">
@@ -456,17 +466,17 @@
            accesskey="&showAbToolbarCmd.accesskey;"
            defaultset="button-newcard,button-newlist,separator,button-editcard,button-newmessage,button-newim,button-abdelete,spring,searchBox,throbber-box"
            context="toolbar-context-menu">
     <toolbarbutton id="button-newcard"
                    class="toolbarbutton-1"
                    label="&newContactButton.label;"
                    tooltiptext="&newContactButton.tooltip;"
                    removable="true"
-                   oncommand="AbNewCard();"/>
+                   command="cmd_newCard"/>
     <toolbarbutton id="button-newlist"
                    class="toolbarbutton-1"
                    label="&newlistButton.label;"
                    tooltiptext="&newlistButton.tooltip;"
                    removable="true"
                    command="cmd_newlist"/>
     <toolbarbutton id="button-editcard"
                    class="toolbarbutton-1"
@@ -541,17 +551,25 @@
       </tree>
     </vbox>
 
     <splitter id="dirTree-splitter" collapse="before" persist="state">
       <grippy/>
     </splitter>
 
     <vbox flex="1" style="min-width:100px">
-
+      <description id="localResultsOnlyMessage"
+                   value="&localResultsOnlyMessage.label;"/>
+      <vbox id="blankResultsPaneMessageBox"
+            flex="1"
+            pack="center"
+            align="center">
+        <description id="blankResultsPaneMessage"
+                     value="&blankResultsPaneMessage.label;"/>
+      </vbox>
       <!-- results pane -->
       <tree id="abResultsTree" context="abResultsTreeContext" flex="1" />
 
       <splitter id="results-splitter" collapse="after" persist="state">
         <grippy/>
       </splitter>
 
       <!-- card view -->
--- a/suite/themes/classic/mac/messenger/addressbook/addressbook.css
+++ b/suite/themes/classic/mac/messenger/addressbook/addressbook.css
@@ -298,16 +298,25 @@ toolbar[iconsize="small"] > #button-abde
 }
 
 #DirCol > .treecol-text {
   padding: 6px 0 6px 8px;
   font-weight: bold;
   color: #738193;
 }
 
+#blankResultsPaneMessage {
+  font-style: italic;
+}
+
+#localResultsOnlyMessage {
+  font-style: italic;
+  text-align: center;
+}
+
 /* CardView styles - used in the Card View Pane */
  
 #CardViewBox {
   -moz-user-focus: ignore;
   overflow: auto;
   min-width: 150px;
   background-color: #FFFFFF;
 }
--- a/suite/themes/classic/messenger/addressbook/addressbook.css
+++ b/suite/themes/classic/messenger/addressbook/addressbook.css
@@ -220,16 +220,25 @@ toolbar[iconsize="small"] > #button-abde
 toolbar[iconsize="small"] > #button-abdelete:hover:active {
   -moz-image-region: rect(0 59px 19px 40px);
 }
 
 toolbar[iconsize="small"] > #button-abdelete[disabled] {
   -moz-image-region: rect(0 79px 19px 60px) !important;
 }
 
+#blankResultsPaneMessage {
+  font-style: italic;
+}
+
+#localResultsOnlyMessage {
+  font-style: italic;
+  text-align: center;
+}
+
 /* CardView styles - used in the Card View Pane */
 
 #CardViewOuterBox {
   border-left: 1px solid ThreeDShadow;
   border-top: 1px solid ThreeDShadow;
   border-right: 1px solid ThreeDHighlight;
 }
 
--- a/suite/themes/modern/messenger/addressbook/addressbook.css
+++ b/suite/themes/modern/messenger/addressbook/addressbook.css
@@ -116,16 +116,25 @@
 #button-abdelete:hover:active {
   -moz-image-region: rect(408px 149px 441px 100px);
 }
 
 #button-abdelete[disabled] {
   -moz-image-region: rect(408px 199px 441px 150px) !important;
 } 
 
+#blankResultsPaneMessage {
+  font-style: italic;
+}
+
+#localResultsOnlyMessage {
+  font-style: italic;
+  text-align: center;
+}
+
 /* ::::: Card View pane ::::: */
 
 #CardViewBox {
   -moz-user-focus: ignore;
   min-width: 150px;
   background-color: #EFEFEF;
   overflow: auto;
 }