Bug 531285 - Allow multiple map services to be selectable via the Get Map button in AB contact. r=mkmelin, ui-r=Josiah, r=Neil
authoraceman <acelists@atlas.sk>
Wed, 23 Sep 2015 23:38:47 +0200
changeset 23420 6b0dd3834439cdd74aef606eb9b119bebd1c9dc9
parent 23419 0c9f89670ac89a2bcd4c7653611670a9f2a97af0
child 23421 243713ca559c073c6bc33e8c47a7d88a80807a7c
push id1538
push userclokep@gmail.com
push dateMon, 14 Dec 2015 22:26:08 +0000
treeherdercomm-beta@1d71b57e0749 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmkmelin, Josiah, Neil
bugs531285
Bug 531285 - Allow multiple map services to be selectable via the Get Map button in AB contact. r=mkmelin, ui-r=Josiah, r=Neil
mail/base/content/messenger.css
mail/components/addrbook/content/abCardViewOverlay.js
mail/components/addrbook/content/addressbook.xul
mail/locales/en-US/chrome/messenger-region/region.properties
mailnews/addrbook/content/addrbookWidgets.xml
mailnews/mailnews.js
suite/locales/en-US/chrome/mailnews/region.properties
suite/mailnews/addrbook/abCardViewOverlay.js
suite/mailnews/addrbook/addressbook.xul
suite/mailnews/messenger.css
--- a/mail/base/content/messenger.css
+++ b/mail/base/content/messenger.css
@@ -243,16 +243,20 @@ tabmail {
 menupopup[type="folder"] {
   -moz-binding: url("chrome://messenger/content/folderWidgets.xml#folder-menupopup");
 }
 
 .addrbooksPopup {
   -moz-binding: url("chrome://messenger/content/addressbook/addrbookWidgets.xml#addrbooks-menupopup");
 }
 
+.map-list {
+  -moz-binding: url("chrome://messenger/content/addressbook/addrbookWidgets.xml#map-list");
+}
+
 /**
  * Toolboxes default to overflow: visible, and we need to override that to
  * prevent the toolbox from causing its containing box (the entire content
  * area) to become wider than the viewport, which has the bad side-effect of
  * causing the _other_ children (i.e. tabmail, which contains important widgetry
  * like the message scrollbar) to be rendered out of view.
  *
  * As it turns out, we only need to override the mail-bar3 (at least of
--- a/mail/components/addrbook/content/abCardViewOverlay.js
+++ b/mail/components/addrbook/content/abCardViewOverlay.js
@@ -4,20 +4,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //NOTE: gAddressBookBundle must be defined and set or this Overlay won't work
 
 Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 var gProfileDirURL;
-
-var gMapItURLFormat = Services.prefs.getComplexValue("mail.addr_book.mapit_url.format",
-  Components.interfaces.nsIPrefLocalizedString).data;
-
 var gFileHandler = Services.io.getProtocolHandler("file")
   .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
 var gPhotoDisplayHandlers = {};
 
 var zListName;
 var zPrimaryEmail;
 var zSecondaryEmail;
 var zNickname;
@@ -242,32 +238,22 @@ function DisplayCardViewPane(realCard)
    visible = cvSetNode(data.cvHomeAddress2, card.getProperty("HomeAddress2")) ||
              visible;
    visible = cvSetCityStateZip(data.cvHomeCityStZip,
                                card.getProperty("HomeCity"),
                                card.getProperty("HomeState"),
                                card.getProperty("HomeZipCode")) || visible;
    visible = cvSetNode(data.cvHomeCountry, card.getProperty("HomeCountry")) ||
              visible;
-   if (visible) {
-     var homeMapItUrl = CreateMapItURL(card.getProperty("HomeAddress"),
-                                       card.getProperty("HomeAddress2"),
-                                       card.getProperty("HomeCity"),
-                                       card.getProperty("HomeState"),
-                                       card.getProperty("HomeZipCode"),
-                                       card.getProperty("HomeCountry"));
-    if (homeMapItUrl) {
-       cvSetVisible(data.cvbHomeMapItBox, true);
-       data.cvHomeMapIt.setAttribute('url', homeMapItUrl);
-    } else {
-       cvSetVisible(data.cvbHomeMapItBox, false);
-    }
-  } else {
-    cvSetVisible(data.cvbHomeMapItBox, false);
-  }
+
+  let mapURLList = data.cvHomeMapIt.firstChild;
+  if (visible)
+    mapURLList.initMapAddressFromCard(card, "Home");
+
+  cvSetVisible(data.cvbHomeMapItBox, !!mapURLList.mapURL);
 
   visible = HandleLink(data.cvHomeWebPage, "", card.getProperty("WebPage2"),
                        data.cvHomeWebPageBox, card.getProperty("WebPage2")) ||
             visible;
 
   cvSetVisible(data.cvhHome, visible);
   cvSetVisible(data.cvbHome, visible);
   if (card.isMailList) {
@@ -386,35 +372,21 @@ function DisplayCardViewPane(realCard)
   addressVisible = cvSetCityStateZip(data.cvWorkCityStZip,
                                      card.getProperty("WorkCity"),
                                      card.getProperty("WorkState"),
                                      card.getProperty("WorkZipCode")) ||
                    addressVisible;
   addressVisible = cvSetNode(data.cvWorkCountry,
                              card.getProperty("WorkCountry")) || addressVisible;
 
-        if (addressVisible) {
-          var workMapItUrl = CreateMapItURL(card.getProperty("WorkAddress"),
-                                            card.getProperty("WorkAddress2"),
-                                            card.getProperty("WorkCity"),
-                                            card.getProperty("WorkState"),
-                                            card.getProperty("WorkZipCode"),
-                                            card.getProperty("WorkCountry"));
-          data.cvWorkMapIt.setAttribute('url', workMapItUrl);
-          if (workMapItUrl) {
-      cvSetVisible(data.cvbWorkMapItBox, true);
-            data.cvWorkMapIt.setAttribute('url', workMapItUrl);
-          }
-          else {
-      cvSetVisible(data.cvbWorkMapItBox, false);
-          }
-        }
-        else {
-    cvSetVisible(data.cvbWorkMapItBox, false);
-        }
+  mapURLList = data.cvWorkMapIt.firstChild;
+  if (addressVisible)
+    mapURLList.initMapAddressFromCard(card, "Work");
+
+  cvSetVisible(data.cvbWorkMapItBox, !!mapURLList.mapURL);
 
         visible = HandleLink(data.cvWorkWebPage, "",
                              card.getProperty("WebPage1"),
                              data.cvWorkWebPageBox,
                              card.getProperty("WebPage1")) || addressVisible ||
                              visible;
 
   cvSetVisible(data.cvhWork, visible);
@@ -573,44 +545,33 @@ function OpenURLWithHistory(url)
       }]
     });
     var messenger = Components.classes["@mozilla.org/messenger;1"].createInstance();
     messenger = messenger.QueryInterface(Components.interfaces.nsIMessenger);
     messenger.launchExternalURL(url);
   } catch (ex) {}
 }
 
-function CreateMapItURL(address1, address2, city, state, zip, country)
-{
-  if (!gMapItURLFormat)
-    return null;
-
-  var urlFormat = gMapItURLFormat.replace("@A1", encodeURIComponent(address1));
-  urlFormat = urlFormat.replace("@A2", encodeURIComponent(address2));
-  urlFormat = urlFormat.replace("@CO", encodeURIComponent(country));
-  urlFormat = urlFormat.replace("@CI", encodeURIComponent(city));
-  urlFormat = urlFormat.replace("@ST", encodeURIComponent(state));
-  urlFormat = urlFormat.replace("@ZI", encodeURIComponent(zip));
-
-  return urlFormat;
-}
-
-function MapIt(id)
-{
-  OpenURLWithHistory(document.getElementById(id).getAttribute('url'));
-}
-
 function openLink(id)
 {
   OpenURLWithHistory(document.getElementById(id).getAttribute("href"));
 
   // return false, so we don't load the href in the addressbook window
   return false;
 }
 
+function openLinkWithUrl(aUrl)
+{
+  if (aUrl)
+    OpenURLWithHistory(aUrl);
+
+  // return false, so we don't load the href in the addressbook window
+  return false;
+}
+
 /* Display the contact photo from the nsIAbCard in the IMG element.
  * If the photo cannot be displayed, show the generic contact
  * photo.
  */
 function displayPhoto(aCard, aImg)
 {
   var type = aCard.getProperty("PhotoType", "");
   if (!gPhotoDisplayHandlers[type] ||
--- a/mail/components/addrbook/content/addressbook.xul
+++ b/mail/components/addrbook/content/addressbook.xul
@@ -763,17 +763,23 @@
                   <hbox>
                     <vbox flex="1">
                       <description class="CardViewText" id="cvHomeAddress"/>
                       <description class="CardViewText" id="cvHomeAddress2"/>
                       <description class="CardViewText" id="cvHomeCityStZip"/>
                       <description class="CardViewText" id="cvHomeCountry"/>
                     </vbox>
                     <vbox id="cvbHomeMapItBox" pack="end">
-                      <button id="cvHomeMapIt" url="" label="&mapItButton.label;" oncommand="MapIt('cvHomeMapIt');" tooltiptext="&mapIt.tooltip;"/>
+                      <button id="cvHomeMapIt"
+                              label="&mapItButton.label;"
+                              type="menu-button"
+                              oncommand="openLinkWithUrl(this.firstChild.mapURL);"
+                              tooltiptext="&mapIt.tooltip;">
+                        <menupopup class="map-list"/>
+                      </button>
                     </vbox>
                   </hbox>
                   <description class="CardViewLink" id="cvHomeWebPageBox">
                     <html:a onclick="return openLink('cvHomeWebPage');" href="" id="cvHomeWebPage"/>
                   </description>
                 </vbox>
                 <vbox id="cvbOther" class="cardViewGroup">
                   <description class="CardViewHeading" id="cvhOther">&other.heading;</description>
@@ -828,17 +834,23 @@
                   <hbox>
                     <vbox flex="1">
                       <description class="CardViewText" id="cvWorkAddress"/>
                       <description class="CardViewText" id="cvWorkAddress2"/>
                       <description class="CardViewText" id="cvWorkCityStZip"/>
                       <description class="CardViewText" id="cvWorkCountry"/>
                     </vbox>
                     <vbox id="cvbWorkMapItBox" pack="end">
-                      <button id="cvWorkMapIt" url="" label="&mapItButton.label;" oncommand="MapIt('cvWorkMapIt');" tooltiptext="&mapIt.tooltip;"/>
+                      <button id="cvWorkMapIt"
+                              label="&mapItButton.label;"
+                              type="menu-button"
+                              oncommand="openLinkWithUrl(this.firstChild.mapURL);"
+                              tooltiptext="&mapIt.tooltip;">
+                        <menupopup class="map-list"/>
+                      </button>
                     </vbox>
                   </hbox>
                   <description class="CardViewLink" id="cvWorkWebPageBox">
                     <html:a onclick="return openLink('cvWorkWebPage');" href="" id="cvWorkWebPage"/>
                   </description>
                 </vbox>
               </vbox>
             </hbox>
--- a/mail/locales/en-US/chrome/messenger-region/region.properties
+++ b/mail/locales/en-US/chrome/messenger-region/region.properties
@@ -7,21 +7,31 @@ browser.search.defaultenginename=Bing
 
 # Search engine order (order displayed in the search bar dropdown)s
 browser.search.order.1=Bing
 browser.search.order.2=Yahoo
 browser.search.order.3=
 
 # To make mapit buttons to disappear in the addressbook, specify empty string.  For example:
 # mail.addr_book.mapit_url.format=
-# The format for "mail.addr_book.mapit_url.format" is:
+# The mail.addr_book.mapit_url.format should start with the URL of the mapping
+# service and then the query part with placeholders to be subsituted from values
+# from the addressbook contact's address.
+# Available placeholders are:
 # @A1 == address, part 1
 # @A2 == address, part 2
 # @CI == city
 # @ST == state
 # @ZI == zip code
 # @CO == country
+# Default map service:
 mail.addr_book.mapit_url.format=http://maps.google.com/maps?q=@A1%20@A2%20@CI%20@ST%20@ZI%20@CO
+# List of available map services (up to 5 can be defined here):
+mail.addr_book.mapit_url.1.name=Google Maps
+mail.addr_book.mapit_url.1.format=http://maps.google.com/maps?q=@A1%20@A2%20@CI%20@ST%20@ZI%20@CO
+mail.addr_book.mapit_url.2.name=OpenStreetMap
+mail.addr_book.mapit_url.2.format=http://nominatim.openstreetmap.org/search.php?polygon=1&q=@A1%2C@A2%2C@CI%2C@ST%2C@ZI%2C@CO
+
 mailnews.messageid_browser.url=http://groups.google.com/search?as_umsgid=%mid
 
 # Recognize non-standard versions of "Re:" in subjects from localized versions of MS Outlook et al.
 # Specify a comma-separated list without spaces. For example: mailnews.localizedRe=AW,SV
 mailnews.localizedRe=
--- a/mailnews/addrbook/content/addrbookWidgets.xml
+++ b/mailnews/addrbook/content/addrbookWidgets.xml
@@ -200,9 +200,240 @@
             return 1;
 
           // Sort anything else by the dir type.
           return a.dirType - b.dirType;
         ]]></body>
       </method>
     </implementation>
   </binding>
+
+  <binding id="map-list"
+           extends="chrome://global/content/bindings/popup.xml#popup">
+    <implementation>
+      <property name="mapURL" readonly="true">
+        <getter><![CDATA[
+          return this._createMapItURL();
+        ]]></getter>
+      </property>
+
+      <constructor>
+        <![CDATA[
+          this._setWidgetDisabled(true);
+        ]]>
+      </constructor>
+
+      <!--
+        Initializes the necessary address data from an addressbook card.
+        @param aCard        A nsIAbCard to get the address data from.
+        @param aAddrPrefix  A prefix of the card properties to use. Use "Home" or "Work".
+        -->
+      <method name="initMapAddressFromCard">
+        <parameter name="aCard"/>
+        <parameter name="aAddrPrefix"/>
+        <body><![CDATA[
+          let mapItURLFormat = this._getMapURLPref(0);
+          let doNotShowMap = !mapItURLFormat || !aAddrPrefix || !aCard;
+          this._setWidgetDisabled(doNotShowMap);
+          if (doNotShowMap)
+            return;
+
+          this.setAttribute("map_address1", aCard.getProperty(aAddrPrefix + "Address"));
+          this.setAttribute("map_address2", aCard.getProperty(aAddrPrefix + "Address2"));
+          this.setAttribute("map_city"    , aCard.getProperty(aAddrPrefix + "City"));
+          this.setAttribute("map_state"   , aCard.getProperty(aAddrPrefix + "State"));
+          this.setAttribute("map_zip"     , aCard.getProperty(aAddrPrefix + "ZipCode"));
+          this.setAttribute("map_country" , aCard.getProperty(aAddrPrefix + "Country"));
+        ]]></body>
+      </method>
+
+      <!--
+        Initializes the necessary address data from passed in values.
+        -->
+      <method name="initMapAddress">
+        <parameter name="aAddr1"/>
+        <parameter name="aAddr2"/>
+        <parameter name="aCity"/>
+        <parameter name="aState"/>
+        <parameter name="aZip"/>
+        <parameter name="aCountry"/>
+        <body><![CDATA[
+          let mapItURLFormat = this._getMapURLPref(0);
+          let doNotShowMap = !mapItURLFormat || !(aAddr1 + aAddr2 + aCity + aState + aZip + aCountry);
+          this._setWidgetDisabled(doNotShowMap);
+          if (doNotShowMap)
+            return;
+
+          this.setAttribute("map_address1", aAddr1);
+          this.setAttribute("map_address2", aAddr2);
+          this.setAttribute("map_city"    , aCity);
+          this.setAttribute("map_state"   , aState);
+          this.setAttribute("map_zip"     , aZip);
+          this.setAttribute("map_country" , aCountry);
+        ]]></body>
+      </method>
+
+      <!--
+        Sets the disabled/enabled state of the parent widget (e.g. a button).
+        -->
+      <method name="_setWidgetDisabled">
+        <parameter name="aDisabled"/>
+        <body><![CDATA[
+          this.parentNode.disabled = aDisabled;
+        ]]></body>
+      </method>
+
+      <!--
+        Returns the Map service URL from localized pref. Returns null if there
+        is none at the given index.
+        @param aIndex  The index of the service to return. 0 is the default service.
+        -->
+      <method name="_getMapURLPref">
+        <parameter name="aIndex"/>
+        <body><![CDATA[
+          let url = null;
+          if (!aIndex) {
+            url = Services.prefs.getComplexValue("mail.addr_book.mapit_url.format",
+                                                 Components.interfaces.nsIPrefLocalizedString).data;
+          } else {
+            try {
+              url = Services.prefs.getComplexValue("mail.addr_book.mapit_url." + aIndex + ".format",
+                                                   Components.interfaces.nsIPrefLocalizedString).data;
+            } catch (e) { }
+          }
+
+          return url;
+        ]]></body>
+      </method>
+
+      <!--
+        Builds menuitem elements representing map services defined in prefs
+        and attaches them to the specified button.
+        -->
+      <method name="_listMapServices">
+        <body><![CDATA[
+          let index = 1;
+          let itemFound = true;
+          let defaultFound = false;
+          const kUserIndex = 100;
+          let aMapList = this;
+          while (aMapList.hasChildNodes()) {
+            aMapList.lastChild.remove();
+          }
+
+          let defaultUrl = this._getMapURLPref(0);
+
+          // Creates the menuitem with supplied data.
+          function addMapService(aUrl, aName) {
+            let item = document.createElement("menuitem");
+            item.setAttribute("url", aUrl);
+            item.setAttribute("label", aName);
+            item.setAttribute("type", "radio");
+            item.setAttribute("name", "mapit_service");
+            if (aUrl == defaultUrl)
+              item.setAttribute("checked", "true");
+            aMapList.appendChild(item);
+          }
+
+          // Generates a useful generic name by cutting out only the host address.
+          function generateName(aUrl) {
+            return new URL(aUrl).hostname;
+          }
+
+          // Add all defined map services as menuitems.
+          while (itemFound) {
+            let urlName;
+            let urlTemplate = this._getMapURLPref(index);
+            if (!urlTemplate) {
+              itemFound = false;
+            } else {
+              // Name is not mandatory, generate one if not found.
+              try {
+                urlName = Services.prefs.getComplexValue("mail.addr_book.mapit_url." + index + ".name",
+                                                         Components.interfaces.nsIPrefLocalizedString).data;
+              } catch (e) {
+                urlName = generateName(urlTemplate);
+              }
+            }
+            if (itemFound) {
+              addMapService(urlTemplate, urlName);
+              index++;
+              if (urlTemplate == defaultUrl)
+                defaultFound = true;
+            } else if (index < kUserIndex) {
+              // After iterating the base region provided urls, check for user defined ones.
+              index = kUserIndex;
+              itemFound = true;
+            }
+          }
+          if (!defaultFound) {
+            // If user had put a customized map URL into mail.addr_book.mapit_url.format
+            // preserve it as a new map service named with the URL.
+            // 'index' now points to the first unused entry in prefs.
+            let defaultName = generateName(defaultUrl);
+            addMapService(defaultUrl, defaultName);
+            Services.prefs.setCharPref("mail.addr_book.mapit_url." + index + ".format",
+                                        defaultUrl);
+            Services.prefs.setCharPref("mail.addr_book.mapit_url." + index + ".name",
+                                        defaultName);
+          }
+        ]]></body>
+      </method>
+
+      <!--
+        Save user selected mapping service.
+        @param aItem  The chosen menuitem with map service.
+        -->
+      <method name="_chooseMapService">
+        <parameter name="aItem"/>
+        <body><![CDATA[
+          // Save selected URL as the default.
+          let defaultUrl = Components.classes["@mozilla.org/pref-localizedstring;1"]
+                                     .createInstance(Components.interfaces.nsIPrefLocalizedString);
+          defaultUrl.data = aItem.getAttribute("url");
+          Services.prefs.setComplexValue("mail.addr_book.mapit_url.format",
+                                         Components.interfaces.nsIPrefLocalizedString, defaultUrl);
+        ]]></body>
+      </method>
+
+      <!--
+        Generate map URL in the href attribute.
+        -->
+      <method name="_createMapItURL">
+        <body><![CDATA[
+          let urlFormat = this._getMapURLPref(0);
+          if (!urlFormat)
+            return null;
+
+          let address1 = this.getAttribute("map_address1");
+          let address2 = this.getAttribute("map_address2");
+          let city     = this.getAttribute("map_city");
+          let state    = this.getAttribute("map_state");
+          let zip      = this.getAttribute("map_zip");
+          let country  = this.getAttribute("map_country");
+
+          urlFormat = urlFormat.replace("@A1", encodeURIComponent(address1));
+          urlFormat = urlFormat.replace("@A2", encodeURIComponent(address2));
+          urlFormat = urlFormat.replace("@CI", encodeURIComponent(city));
+          urlFormat = urlFormat.replace("@ST", encodeURIComponent(state));
+          urlFormat = urlFormat.replace("@ZI", encodeURIComponent(zip));
+          urlFormat = urlFormat.replace("@CO", encodeURIComponent(country));
+
+          return urlFormat;
+        ]]></body>
+      </method>
+    </implementation>
+
+    <handlers>
+      <handler event="command">
+        <![CDATA[
+          this._chooseMapService(event.target);
+          event.stopPropagation();
+        ]]>
+      </handler>
+      <handler event="popupshowing">
+        <![CDATA[
+          this._listMapServices();
+        ]]>
+      </handler>
+    </handlers>
+  </binding>
 </bindings>
--- a/mailnews/mailnews.js
+++ b/mailnews/mailnews.js
@@ -124,16 +124,26 @@ pref("mail.strictly_mime_headers",      
 // 0/1 (name param is encoded in a legacy way), 2(RFC 2231 only)
 // 0 the name param is never separated to multiple lines.
 pref("mail.strictly_mime.parm_folding",     1);
 pref("mail.label_ascii_only_mail_as_us_ascii", false);
 pref("mail.file_attach_binary",             false);
 pref("mail.show_headers",                   1); // some
 pref("mail.pane_config.dynamic",            0);
 pref("mail.addr_book.mapit_url.format", "chrome://messenger-region/locale/region.properties");
+pref("mail.addr_book.mapit_url.1.name", "chrome://messenger-region/locale/region.properties");
+pref("mail.addr_book.mapit_url.1.format", "chrome://messenger-region/locale/region.properties");
+pref("mail.addr_book.mapit_url.2.name", "chrome://messenger-region/locale/region.properties");
+pref("mail.addr_book.mapit_url.2.format", "chrome://messenger-region/locale/region.properties");
+pref("mail.addr_book.mapit_url.3.name", "chrome://messenger-region/locale/region.properties");
+pref("mail.addr_book.mapit_url.3.format", "chrome://messenger-region/locale/region.properties");
+pref("mail.addr_book.mapit_url.4.name", "chrome://messenger-region/locale/region.properties");
+pref("mail.addr_book.mapit_url.4.format", "chrome://messenger-region/locale/region.properties");
+pref("mail.addr_book.mapit_url.5.name", "chrome://messenger-region/locale/region.properties");
+pref("mail.addr_book.mapit_url.5.format", "chrome://messenger-region/locale/region.properties");
 #ifdef MOZ_SUITE
 pref("mailnews.start_page.url", "chrome://messenger-region/locale/region.properties");
 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);
--- a/suite/locales/en-US/chrome/mailnews/region.properties
+++ b/suite/locales/en-US/chrome/mailnews/region.properties
@@ -6,20 +6,29 @@
 # messenger.properties
 # mailnews.js
 mailnews.start_page.url=chrome://messenger/content/start.xhtml
 messenger.throbber.url=http://www.seamonkey-project.org/
 compose.throbber.url=http://www.seamonkey-project.org/
 addressbook.throbber.url=http://www.seamonkey-project.org/
 # To make mapit buttons to disappear in the addressbook, specify empty string.  For example:
 # mail.addr_book.mapit_url.format=
-# The format for "mail.addr_book.mapit_url.format" is:
+# The mail.addr_book.mapit_url.format should start with the URL of the mapping
+# service and then the query part with placeholders to be subsituted from values
+# from the addressbook contact's address.
+# Available placeholders are:
 # @A1 == address, part 1
 # @A2 == address, part 2
 # @CI == city
 # @ST == state
 # @ZI == zip code
 # @CO == country
+# Default map service:
 mail.addr_book.mapit_url.format=http://maps.google.com/maps?q=@A1%20@A2%20@CI%20@ST%20@ZI%20@CO
+# List of available map services (up to 5 can be defined here):
+mail.addr_book.mapit_url.1.name=Google Maps
+mail.addr_book.mapit_url.1.format=http://maps.google.com/maps?q=@A1%20@A2%20@CI%20@ST%20@ZI%20@CO
+mail.addr_book.mapit_url.2.name=OpenStreetMap
+mail.addr_book.mapit_url.2.format=http://nominatim.openstreetmap.org/search.php?polygon=1&q=@A1%2C@A2%2C@CI%2C@ST%2C@ZI%2C@CO
 mailnews.messageid_browser.url=http://groups.google.com/search?as_umsgid=%mid
 # Recognize non-standard versions of "Re:" in subjects from localized versions of MS Outlook et al.
 # Specify a comma-separated list without spaces. For example: mailnews.localizedRe=AW,SV
 mailnews.localizedRe=
--- a/suite/mailnews/addrbook/abCardViewOverlay.js
+++ b/suite/mailnews/addrbook/abCardViewOverlay.js
@@ -208,29 +208,22 @@ function DisplayCardViewPane(realCard)
   visible = hasScreenName || visible;
   visible = HandleLink(data.cvEmail2, zSecondaryEmail, card.getProperty("SecondEmail"), data.cvEmail2Box, "mailto:" + card.getProperty("SecondEmail")) || visible;
 
 	// Home section
 	visible = cvSetNode(data.cvHomeAddress, card.getProperty("HomeAddress"));
 	visible = cvSetNode(data.cvHomeAddress2, card.getProperty("HomeAddress2")) || visible;
 	visible = cvSetCityStateZip(data.cvHomeCityStZip, card.getProperty("HomeCity"), card.getProperty("HomeState"), card.getProperty("HomeZipCode")) || visible;
 	visible = cvSetNode(data.cvHomeCountry, card.getProperty("HomeCountry")) || visible;
-        if (visible) {
-          var homeMapItUrl = CreateMapItURL(card.getProperty("HomeAddress"), card.getProperty("HomeAddress2"), card.getProperty("HomeCity"), card.getProperty("HomeState"), card.getProperty("HomeZipCode"), card.getProperty("HomeCountry"));
-          if (homeMapItUrl) {
-	    cvSetVisible(data.cvbHomeMapItBox, true);
-            data.cvHomeMapIt.setAttribute('url', homeMapItUrl);
-          }
-          else {
-	    cvSetVisible(data.cvbHomeMapItBox, false);
-          }
-        }
-        else {
-	  cvSetVisible(data.cvbHomeMapItBox, false);
-        }
+
+  mapURLList = data.cvHomeMapIt.firstChild;
+  if (addressVisible)
+    mapURLList.initMapAddressFromCard(card, "Home");
+
+  cvSetVisible(data.cvbHomeMapItBox, !!mapURLList.mapURL);
 
   visible = HandleLink(data.cvHomeWebPage, "", card.getProperty("WebPage2"), data.cvHomeWebPageBox, card.getProperty("WebPage2")) || visible;
 
 	cvSetVisible(data.cvhHome, visible);
 	cvSetVisible(data.cvbHome, visible);
   if (card.isMailList) {
     // Description section
 	  visible = cvSetNode(data.cvDescription, card.getProperty("Notes"))
@@ -304,30 +297,21 @@ function DisplayCardViewPane(realCard)
 	visible = cvSetNode(data.cvDepartment, card.getProperty("Department")) || visible;
 	visible = cvSetNode(data.cvCompany, card.getProperty("Company")) || visible;
         
         var addressVisible = cvSetNode(data.cvWorkAddress, card.getProperty("WorkAddress"));
 	addressVisible = cvSetNode(data.cvWorkAddress2, card.getProperty("WorkAddress2")) || addressVisible;
 	addressVisible = cvSetCityStateZip(data.cvWorkCityStZip, card.getProperty("WorkCity"), card.getProperty("WorkState"), card.getProperty("WorkZipCode")) || addressVisible;
 	addressVisible = cvSetNode(data.cvWorkCountry, card.getProperty("WorkCountry")) || addressVisible;
 
-        if (addressVisible) {
-          var workMapItUrl = CreateMapItURL(card.getProperty("WorkAddress"), card.getProperty("WorkAddress2"), card.getProperty("WorkCity"), card.getProperty("WorkState"), card.getProperty("WorkZipCode"), card.getProperty("WorkCountry"));
-          data.cvWorkMapIt.setAttribute('url', workMapItUrl);
-          if (workMapItUrl) {
-	    cvSetVisible(data.cvbWorkMapItBox, true);
-            data.cvWorkMapIt.setAttribute('url', workMapItUrl);
-          }
-          else {
-	    cvSetVisible(data.cvbWorkMapItBox, false);
-          }
-        }
-        else {
-	  cvSetVisible(data.cvbWorkMapItBox, false);
-        }
+  mapURLList = data.cvWorkMapIt.firstChild;
+  if (addressVisible)
+    mapURLList.initMapAddressFromCard(card, "Work");
+
+  cvSetVisible(data.cvbWorkMapItBox, !!mapURLList.mapURL);
 
         visible = HandleLink(data.cvWorkWebPage, "", card.getProperty("WebPage1"), data.cvWorkWebPageBox, card.getProperty("WebPage1")) || addressVisible || visible;
 
 	cvSetVisible(data.cvhWork, visible);
 	cvSetVisible(data.cvbWork, visible);
 
 	// make the card view box visible
 	cvSetVisible(top.cvData.CardViewBox, true);
@@ -478,44 +462,31 @@ function HandleLink(node, label, value, 
   var visible = cvSetNodeWithLabel(node, label, value);
   if (visible)
     node.setAttribute('href', link);
   cvSetVisible(box, visible);
 
   return visible;
 }
 
-function MapIt(id)
-{
-  var button = document.getElementById(id);
-  openTopWin(button.getAttribute('url'));
-}
-
-function CreateMapItURL(address1, address2, city, state, zip, country)
-{
-  if (!gMapItURLFormat)
-    return null;
-
-  var urlFormat = gMapItURLFormat.replace("@A1", encodeURIComponent(address1));
-  urlFormat = urlFormat.replace("@A2", encodeURIComponent(address2));
-  urlFormat = urlFormat.replace("@CO", encodeURIComponent(country));
-  urlFormat = urlFormat.replace("@CI", encodeURIComponent(city));
-  urlFormat = urlFormat.replace("@ST", encodeURIComponent(state));
-  urlFormat = urlFormat.replace("@ZI", encodeURIComponent(zip));
-  
-  return urlFormat;
-}
-
 function openLink(aEvent)
 {
   openAsExternal(aEvent.target.getAttribute("href"));
   // return false, so we don't load the href in the addressbook window
   return false;
 }
 
+function openLinkWithUrl(aUrl)
+{
+  if (aUrl)
+    openAsExternal(aUrl);
+  // return false, so we don't load the href in the addressbook window
+  return false;
+}
+
 /* Display the contact photo from the nsIAbCard in the IMG element.
  * If the photo cannot be displayed, show the generic contact
  * photo.
  */
 function displayPhoto(aCard, aImg)
 {
   var type = aCard.getProperty("PhotoType", "");
   if (!gPhotoDisplayHandlers[type] ||
--- a/suite/mailnews/addrbook/addressbook.xul
+++ b/suite/mailnews/addrbook/addressbook.xul
@@ -582,20 +582,22 @@
                     <vbox flex="1">
                       <description class="CardViewText" id="cvHomeAddress"/>
                       <description class="CardViewText" id="cvHomeAddress2"/>
                       <description class="CardViewText" id="cvHomeCityStZip"/>
                       <description class="CardViewText" id="cvHomeCountry"/>
                     </vbox>
                     <vbox id="cvbHomeMapItBox" pack="end">
                       <button id="cvHomeMapIt"
-                              url=""
                               label="&mapItButton.label;"
-                              oncommand="MapIt('cvHomeMapIt');"
-                              tooltiptext="&mapIt.tooltip;"/>
+                              type="menu-button"
+                              oncommand="openLinkWithUrl(this.firstChild.mapURL);"
+                              tooltiptext="&mapIt.tooltip;">
+                        <menupopup class="map-list"/>
+                      </button>
                     </vbox>
                   </hbox>
                   <description class="CardViewLink" id="cvHomeWebPageBox">
                     <html:a onclick="return openLink(event);"
                             href=""
                             id="cvHomeWebPage"/>
                   </description>
                 </vbox>
@@ -644,20 +646,22 @@
                     <vbox flex="1">
                       <description class="CardViewText" id="cvWorkAddress"/>
                       <description class="CardViewText" id="cvWorkAddress2"/>
                       <description class="CardViewText" id="cvWorkCityStZip"/>
                       <description class="CardViewText" id="cvWorkCountry"/>
                     </vbox>
                     <vbox id="cvbWorkMapItBox" pack="end">
                       <button id="cvWorkMapIt"
-                              url=""
                               label="&mapItButton.label;"
-                              oncommand="MapIt('cvWorkMapIt');"
-                              tooltiptext="&mapIt.tooltip;"/>
+                              type="menu-button"
+                              oncommand="openLinkWithUrl(this.firstChild.mapURL);"
+                              tooltiptext="&mapIt.tooltip;">
+                        <menupopup class="map-list"/>
+                      </button>
                     </vbox>
                   </hbox>
                   <description class="CardViewLink" id="cvWorkWebPageBox">
                     <html:a onclick="return openLink(event);"
                             href=""
                             id="cvWorkWebPage"/>
                   </description>
                 </vbox>
--- a/suite/mailnews/messenger.css
+++ b/suite/mailnews/messenger.css
@@ -62,16 +62,20 @@ mail-tagfield {
 menupopup[type="folder"] {
   -moz-binding: url("chrome://messenger/content/folderWidgets.xml#folder-menupopup");
 }
 
 .addrbooksPopup {
   -moz-binding: url("chrome://messenger/content/addressbook/addrbookWidgets.xml#addrbooks-menupopup");
 }
 
+.map-list {
+  -moz-binding: url("chrome://messenger/content/addressbook/addrbookWidgets.xml#map-list");
+}
+
 #searchTermList > listitem {
   -moz-binding: url("chrome://messenger/content/searchWidgets.xml#listitem");
 }
 
 searchattribute {
   -moz-binding: url("chrome://messenger/content/mailWidgets.xml#searchattribute");
 }