Bug 118624 - Implement customizable prefs: mail.addr_book.quicksearchquery.format, autocompletequery.format; add Nickname & other fields to AB quick search, contacts side bar and SM's Select Addresses dialogue. r=aceman, rs=rkent, a=rkent
authorThomas Düllmann <bugzilla2007@duellmann24.net>
Mon, 19 Oct 2015 10:04:29 +0200
changeset 26617 90416ee7dce21db37f2d403faa8b115a11b74ac5
parent 26616 1c78970960b21148c148a05637066f788b3e5ec3
child 26618 4b637d031765aa391be6f5360b82786553ff7418
push id1850
push userclokep@gmail.com
push dateWed, 08 Mar 2017 19:29:12 +0000
treeherdercomm-esr52@028df196b2d9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaceman, rkent, rkent
bugs118624
Bug 118624 - Implement customizable prefs: mail.addr_book.quicksearchquery.format, autocompletequery.format; add Nickname & other fields to AB quick search, contacts side bar and SM's Select Addresses dialogue. r=aceman, rs=rkent, a=rkent
mail/components/addrbook/content/abContactsPanel.js
mail/components/addrbook/content/addressbook.js
mailnews/addrbook/src/nsAbAutoCompleteSearch.js
mailnews/base/util/ABQueryUtils.jsm
mailnews/mailnews.js
--- a/mail/components/addrbook/content/abContactsPanel.js
+++ b/mail/components/addrbook/content/abContactsPanel.js
@@ -180,21 +180,19 @@ function CommandUpdate_AddressBook()
 {
   goUpdateCommand('cmd_delete');
   goUpdateCommand('cmd_properties');
 }
 
 function onEnterInSearchBar()
 {
   if (!gQueryURIFormat) {
-    gQueryURIFormat = Services.prefs.getComplexValue("mail.addr_book.quicksearchquery.format",
-      Components.interfaces.nsIPrefLocalizedString).data;
-
-    // Remove the preceeding '?' as we have to prefix "?and" to this format.
-    gQueryURIFormat = gQueryURIFormat.slice(1);
+    // 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");
   }
 
   var searchURI = GetSelectedDirectory();
   var searchInput = document.getElementById("peopleSearchInput");
 
   // Use helper method to split up search query to multi-word search
   // query against multiple fields.
   if (searchInput) {
--- a/mail/components/addrbook/content/addressbook.js
+++ b/mail/components/addrbook/content/addressbook.js
@@ -501,22 +501,19 @@ function onAdvancedAbSearch()
                       "chrome,resizable,status,centerscreen,dialog=no",
                       {directory: selectedABURI});
 }
 
 function onEnterInSearchBar()
 {
   ClearCardViewPane();
   if (!gQueryURIFormat) {
-    gQueryURIFormat = Services.prefs
-      .getComplexValue("mail.addr_book.quicksearchquery.format",
-                       Components.interfaces.nsIPrefLocalizedString).data;
-
-    // Remove the preceeding '?' as we have to prefix "?and" to this format.
-    gQueryURIFormat = gQueryURIFormat.slice(1);
+    // 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");
   }
 
   var searchURI = GetSelectedDirectory();
   if (!searchURI) return;
 
   /*
    XXX todo, handle the case where the LDAP url
    already has a query, like
--- a/mailnews/addrbook/src/nsAbAutoCompleteSearch.js
+++ b/mailnews/addrbook/src/nsAbAutoCompleteSearch.js
@@ -11,16 +11,21 @@ var ACR = Components.interfaces.nsIAutoC
 var nsIAbAutoCompleteResult = Components.interfaces.nsIAbAutoCompleteResult;
 
 function nsAbAutoCompleteResult(aSearchString) {
   // Can't create this in the prototype as we'd get the same array for
   // all instances
   this._searchResults = []; // final results
   this.searchString = aSearchString;
   this._collectedValues = new Map();  // temporary unsorted results
+  // Get model query from pref; this will return mail.addr_book.autocompletequery.format.phonetic
+  // if mail.addr_book.show_phonetic_fields == true
+  this._modelQuery = getModelQuery("mail.addr_book.autocompletequery.format");
+  // check if the currently active model query has been modified by user
+  this._modelQueryHasUserValue = modelQueryHasUserValue("mail.addr_book.autocompletequery.format");
 }
 
 nsAbAutoCompleteResult.prototype = {
   _searchResults: null,
 
   // nsIAutoCompleteResult
 
   searchString: null,
@@ -206,30 +211,36 @@ nsAbAutoCompleteSearch.prototype = {
           if (email)
             this._addToResult(commentColumn, directory, card, email, false, result);
         }
       }
     }
   },
 
   /**
-   * Checks a card against the search parameters to see if it should be
-   * included in the result.
+   * Checks the parent card and email address of an autocomplete results entry
+   * from a previous result against the search parameters to see if that entry
+   * should still be included in the narrowed-down result.
    *
    * @param aCard        The card to check.
    * @param aEmailToUse  The email address to check against.
    * @param aSearchWords Array of words in the multi word search string.
    * @return             True if the card matches the search parameters, false
    *                     otherwise.
    */
   _checkEntry: function _checkEntry(aCard, aEmailToUse, aSearchWords) {
     // Joining values of many fields in a single string so that a single
     // search query can be fired on all of them at once. Separating them
     // using spaces so that field1=> "abc" and field2=> "def" on joining
     // shouldn't return true on search for "bcd".
+    // Note: This should be constructed from model query pref using
+    // getModelQuery("mail.addr_book.autocompletequery.format")
+    // but for now we hard-code the default value equivalent of the pref here
+    // or else bail out before and reconstruct the full c++ query if the pref
+    // has been customized (modelQueryHasUserValue), so that we won't get here.
     let cumulativeFieldText = aCard.displayName + " " +
                               aCard.firstName + " " +
                               aCard.lastName + " " +
                               aEmailToUse + " " +
                               aCard.getProperty("NickName", "");
     if (aCard.isMailList)
       cumulativeFieldText += " " + aCard.getProperty("Notes", "");
     cumulativeFieldText = cumulativeFieldText.toLocaleLowerCase();
@@ -349,19 +360,30 @@ nsAbAutoCompleteSearch.prototype = {
 
     // Find out about the comment column
     try {
       this._commentColumn = Services.prefs.getIntPref("mail.autoComplete.commentColumn");
     } catch(e) { }
 
     if (aPreviousResult instanceof nsIAbAutoCompleteResult &&
         aSearchString.startsWith(aPreviousResult.searchString) &&
-        aPreviousResult.searchResult == ACR.RESULT_SUCCESS) {
-      // We have successful previous matches, therefore iterate through the
-      // list and reduce as appropriate
+        aPreviousResult.searchResult == ACR.RESULT_SUCCESS &&
+        !result._modelQueryHasUserValue &&
+        result._modelQuery == aPreviousResult._modelQuery) {
+      // We have successful previous matches, and model query has not changed since
+      // previous search, therefore just iterate through the list of previous result
+      // entries and reduce as appropriate (via _checkEntry function).
+      // Test for model query change is required: when reverting back from custom to
+      // default query, result._modelQueryHasUserValue==false, but we must bail out.
+      // Todo: However, if autocomplete model query has been customized, we fall
+      // back to using the full query again instead of reducing result list in js;
+      // The full query might be less performant as it's fired against entire AB,
+      // so we should try morphing the query for js. We can't use the _checkEntry
+      // js query yet because it is hardcoded (mimic default model query).
+      // At least we now allow users to customize their autocomplete model query...
       for (let i = 0; i < aPreviousResult.matchCount; ++i) {
         let card = aPreviousResult.getCardAt(i);
         let email = aPreviousResult.getEmailToUse(i);
         if (this._checkEntry(card, email, searchWords)) {
           // Add matches into the results array. We re-sort as needed later.
           result._searchResults.push({
             value: aPreviousResult.getValueAt(i),
             comment: aPreviousResult.getCommentAt(i),
@@ -373,30 +395,27 @@ nsAbAutoCompleteSearch.prototype = {
               aPreviousResult.getValueAt(i).toLocaleLowerCase(),
               fullString)
           });
         }
       }
     }
     else
     {
-      // Construct the search query; using a query means we can optimise
-      // on running the search through c++ which is better for string
+      // Construct the search query from pref; using a query means we can
+      // optimise on running the search through c++ which is better for string
       // comparisons (_checkEntry is relatively slow).
       // When user's fullstring search expression is a multiword query, search
       // for each word separately so that each result contains all the words
       // from the fullstring in the fields of the addressbook card
       // (see bug 558931 for explanations).
-      let modelQuery = "(or(DisplayName,c,@V)(FirstName,c,@V)(LastName,c,@V)" +
-                       "(NickName,c,@V)(PrimaryEmail,c,@V)(SecondEmail,c,@V)" +
-                       "(and(IsMailList,=,TRUE)(Notes,c,@V)))";
       // Use helper method to split up search query to multi-word search
       // query against multiple fields.
       let searchWords = getSearchTokens(fullString);
-      let searchQuery = generateQueryURI(modelQuery, searchWords);
+      let searchQuery = generateQueryURI(result._modelQuery, searchWords);
 
       // Now do the searching
       let allABs = this._abManager.directories;
 
       // We're not going to bother searching sub-directories, currently the
       // architecture forces all cards that are in mailing lists to be in ABs as
       // well, therefore by searching sub-directories (aka mailing lists) we're
       // just going to find duplicates.
--- a/mailnews/base/util/ABQueryUtils.jsm
+++ b/mailnews/base/util/ABQueryUtils.jsm
@@ -1,17 +1,20 @@
 /* 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/. */
 
 /**
  * This file contains helper methods for dealing with addressbook search URIs.
  */
 
-this.EXPORTED_SYMBOLS = ["getSearchTokens", "generateQueryURI", "encodeABTermValue"];
+this.EXPORTED_SYMBOLS = ["getSearchTokens", "getModelQuery",
+                         "modelQueryHasUserValue", "generateQueryURI",
+                         "encodeABTermValue"];
+Components.utils.import("resource://gre/modules/Services.jsm");
 
 /**
  * Parse the multiword search string to extract individual search terms
  * (separated on the basis of spaces) or quoted exact phrases to search
  * against multiple fields of the addressbook cards.
  *
  * @param aSearchString The full search string entered by the user.
  *
@@ -46,16 +49,56 @@ function getSearchTokens(aSearchString) 
     searchWords = quotedTerms.concat(searchString.split(/\s+/));
   } else {
     searchWords = quotedTerms;
   }
 
   return searchWords;
 }
 
+/**
+ * For AB quicksearch or recipient autocomplete, get the normal or phonetic model
+ * query URL part from prefs, allowing users to customize these searches.
+ * @param aBasePrefName  the full pref name of default, non-phonetic model query,
+ *                       e.g. mail.addr_book.quicksearchquery.format
+ *                       If phonetic search is used, corresponding pref must exist:
+ *                       e.g. mail.addr_book.quicksearchquery.format.phonetic
+ * @return               depending on mail.addr_book.show_phonetic_fields pref,
+ *                       the value of aBasePrefName or aBasePrefName + ".phonetic"
+ */
+function getModelQuery(aBasePrefName) {
+  let modelQuery = "";
+  if (Services.prefs.getComplexValue("mail.addr_book.show_phonetic_fields",
+      Components.interfaces.nsIPrefLocalizedString).data == "true") {
+    modelQuery = Services.prefs.getCharPref(aBasePrefName + ".phonetic");
+  } else {
+    modelQuery = Services.prefs.getCharPref(aBasePrefName);
+  }
+  // remove leading "?" to migrate existing customized values for mail.addr_book.quicksearchquery.format
+  // todo: could this be done in a once-off migration at install time to avoid repetitive calls?
+  if (modelQuery.startsWith("?"))
+    modelQuery = modelQuery.slice(1);
+  return modelQuery;
+}
+
+/**
+ * Check if the currently used pref with the model query was customized by user.
+ * @param aBasePrefName  the full pref name of default, non-phonetic model query,
+ *                       e.g. mail.addr_book.quicksearchquery.format
+ *                       If phonetic search is used, corresponding pref must exist:
+ *                       e.g. mail.addr_book.quicksearchquery.format.phonetic
+ * @return               true or false
+ */
+function modelQueryHasUserValue(aBasePrefName) {
+  if (Services.prefs.getComplexValue("mail.addr_book.show_phonetic_fields",
+      Components.interfaces.nsIPrefLocalizedString).data == "true")
+    return Services.prefs.prefHasUserValue(aBasePrefName + ".phonetic");
+  return Services.prefs.prefHasUserValue(aBasePrefName);
+}
+
 /*
  * Given a database model query and a list of search tokens,
  * return query URI.
  *
  * @param aModelQuery database model query
  * @param aSearchWords an array of search tokens.
  *
  * @return query URI.
--- a/mailnews/mailnews.js
+++ b/mailnews/mailnews.js
@@ -144,22 +144,41 @@ pref("mailnews.start_page.url", "chrome:
 pref("messenger.throbber.url", "chrome://messenger-region/locale/region.properties");
 pref("compose.throbber.url", "chrome://messenger-region/locale/region.properties");
 pref("addressbook.throbber.url", "chrome://messenger-region/locale/region.properties");
 pref("mail.accountwizard.deferstorage", false);
 // |false|: Show both name and address, even for people in my addressbook.
 pref("mail.showCondensedAddresses", false);
 #endif
 
-// the format for "mail.addr_book.quicksearchquery.format" is:
-// @V == the escaped value typed in the quick search bar in the addressbook
+// mail.addr_book.quicksearchquery.format is the model query used for:
+// * TB: AB Quick Search and composition's Contact Side Bar
+// * SM: AB Quick Search and composition's Select Addresses dialogue
+//
+// The format for "mail.addr_book.quicksearchquery.format" is:
+// @V == the escaped value typed in the quick search bar in the address book
+// c  == contains | bw == beginsWith | ...
+//
+// Note, changing the fields searched might require changing labels:
+// SearchNameOrEmail.label in messenger.dtd,
+// searchNameAndEmail.emptytext in abMainWindow.dtd, etc.
 //
-// note, changing this might require a change to SearchNameOrEmail.label in
-// messenger.dtd or searchNameAndEmail.emptytext in abMainWindow.dtd
-pref("mail.addr_book.quicksearchquery.format", "chrome://messenger/locale/messenger.properties");
+// mail.addr_book.quicksearchquery.format will be used if mail.addr_book.show_phonetic_fields is "false"
+pref("mail.addr_book.quicksearchquery.format", "(or(DisplayName,c,@V)(FirstName,c,@V)(LastName,c,@V)(NickName,c,@V)(PrimaryEmail,c,@V)(SecondEmail,c,@V)(and(IsMailList,=,TRUE)(Notes,c,@V))(Company,c,@V)(Department,c,@V)(JobTitle,c,@V)(WebPage1,c,@V)(WebPage2,c,@V))");
+// mail.addr_book.quicksearchquery.format.phonetic will be used if mail.addr_book.show_phonetic_fields is "true"
+pref("mail.addr_book.quicksearchquery.format.phonetic", "(or(DisplayName,c,@V)(FirstName,c,@V)(LastName,c,@V)(NickName,c,@V)(PrimaryEmail,c,@V)(SecondEmail,c,@V)(and(IsMailList,=,TRUE)(Notes,c,@V))(Company,c,@V)(Department,c,@V)(JobTitle,c,@V)(WebPage1,c,@V)(WebPage2,c,@V)(PhoneticFirstName,c,@V)(PhoneticLastName,c,@V))");
+
+// mail.addr_book.autocompletequery.format is the model query used for:
+// * TB: Recipient Autocomplete (composition, mailing list properties dialogue)
+// * SM: Recipient Autocomplete (composition, mailing list properties dialogue)
+//
+// mail.addr_book.autocompletequery.format will be used if mail.addr_book.show_phonetic_fields is "false"
+pref("mail.addr_book.autocompletequery.format", "(or(DisplayName,c,@V)(FirstName,c,@V)(LastName,c,@V)(NickName,c,@V)(PrimaryEmail,c,@V)(SecondEmail,c,@V)(and(IsMailList,=,TRUE)(Notes,c,@V)))");
+// mail.addr_book.autocompletequery.format.phonetic will be used if mail.addr_book.show_phonetic_fields is "true"
+pref("mail.addr_book.autocompletequery.format.phonetic", "(or(DisplayName,c,@V)(FirstName,c,@V)(LastName,c,@V)(NickName,c,@V)(PrimaryEmail,c,@V)(SecondEmail,c,@V)(and(IsMailList,=,TRUE)(Notes,c,@V))(PhoneticFirstName,c,@V)(PhoneticLastName,c,@V))");
 
 // values for "mail.addr_book.lastnamefirst" are:
 //0=displayname, 1=lastname first, 2=firstname first
 pref("mail.addr_book.lastnamefirst", 0);
 pref("mail.addr_book.displayName.autoGeneration", true);
 pref("mail.addr_book.displayName.lastnamefirst", "chrome://messenger/locale/messenger.properties");
 pref("mail.addr_book.show_phonetic_fields", "chrome://messenger/locale/messenger.properties");
 pref("mail.html_compose",                   true);