mailnews/addrbook/content/addrbookWidgets.xml
author aceman <acelists@atlas.sk>
Tue, 30 Oct 2018 17:19:00 +0100
changeset 32778 ba5473b1f7dd0f3b724301a3b182b07d48e5a4ee
parent 32102 aa9abd3271f5776ebcd7b547ba0a78498239eaa0
child 33442 bd2ed7ad33c9714f381b6d8c5d9d8c5f4a1d40d8
permissions -rw-r--r--
Bug 1496518 - fix inclusions of nsIAuthModule. r=jorgk

<?xml version="1.0"?>
<!-- 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/. -->

<bindings id="addrbookBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
          xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="addrbooks-menupopup"
           extends="chrome://global/content/bindings/popup.xml#popup">
    <implementation implements="nsIAbListener">
      <!-- A cache of nsIAbDirectory objects. -->
      <field name="_directories">[]</field>

      <!-- Represents the nsIAbDirectory attribute used as the value of the
           parent menulist. Defaults to URI but can be e.g. dirPrefId -->
      <field name="_value">this.getAttribute("value") || "URI"</field>

      <field name="_stringBundle">null</field>

      <constructor>
        <![CDATA[
          ChromeUtils.import("resource:///modules/MailServices.jsm", this);
          ChromeUtils.import("resource:///modules/iteratorUtils.jsm", this);
          ChromeUtils.import("resource:///modules/StringBundle.js", this);
          this._stringBundle = new this
              .StringBundle("chrome://messenger/locale/addressbook/addressBook.properties");

          this._rebuild();
          const nsIAbListener = Ci.nsIAbListener;
          // Add a listener so we can update correctly if the list should change
          this.MailServices.ab
              .addAddressBookListener(this,
                                      nsIAbListener.itemAdded |
                                      nsIAbListener.directoryItemRemoved |
                                      nsIAbListener.directoryRemoved |
                                      nsIAbListener.itemChanged);
        ]]>
      </constructor>

      <destructor>
        <![CDATA[
          this.MailServices.ab.removeAddressBookListener(this);

          this._teardown();
        ]]>
      </destructor>

      <method name="_rebuild">
        <parameter name="aSelectValue"/>
        <body><![CDATA[
          // Init the address book cache.
          this._directories.length = 0;
          const nsIAbDirectory = Ci.nsIAbDirectory;
          let directories = this.MailServices.ab.directories;
          while (directories && directories.hasMoreElements()) {
            let ab = directories.getNext();
            if ((ab instanceof nsIAbDirectory) && this._matches(ab)) {
              this._directories.push(ab);
              if (this.getAttribute("mailinglists") == "true") {
                // Also append contained mailinglists.
                for (let list of this.fixIterator(ab.childNodes, Ci.nsIAbDirectory)) {
                  if (this._matches(list))
                    this._directories.push(list);
                }
              }
            }
          }

          this._sort();

          this._teardown();

          let menulist = this.parentNode;

          if (this.hasAttribute("none")) {
            // Create a dummy menuitem representing no selection.
            this._directories.unshift(null);
            let listItem = menulist.appendItem(this.getAttribute("none"), "");
            listItem.setAttribute("class", "menuitem-iconic abMenuItem");
            listItem.setAttribute("IsNone", "true");
          }

          if (this.hasAttribute("alladdressbooks")) {
            // Insert a menuitem representing All Addressbooks.
            let allABLabel = this.getAttribute("alladdressbooks");
            if (allABLabel == "true")
              allABLabel = this._stringBundle.getString("allAddressBooks");
            const allABURI = "moz-abdirectory://?";
            this._directories.unshift(null);
            let listItem = menulist.appendItem(allABLabel, allABURI);
            listItem.setAttribute("class", "menuitem-iconic abMenuItem");
            listItem.setAttribute("AddrBook", "true");
            listItem.setAttribute("IsAllAB", "true");
          }

          // Now create menuitems for all displayed directories.
          let value = this._value;
          for (let ab of this._directories) {
            if (!ab) // Skip the empty members added above.
              continue;

            let listItem = menulist.appendItem(ab.dirName, ab[value]);
            listItem.setAttribute("class", "menuitem-iconic abMenuItem");

            // Style the items by type.
            if (ab.isMailList)
              listItem.setAttribute("MailList", "true");
            else
              listItem.setAttribute("AddrBook", "true");

            if (ab.isRemote)
              listItem.setAttribute("IsRemote", "true");
            if (ab.isSecure)
              listItem.setAttribute("IsSecure", "true");
          }

          // Attempt to select the persisted or otherwise first directory.
          menulist.value = aSelectValue || this.parentNode.value;
          if (!menulist.selectedItem && this.hasChildNodes())
            menulist.selectedIndex = 0;
        ]]></body>
      </method>

      <method name="_teardown">
        <body><![CDATA[
          // Empty out anything in the list.
          // (Don't use menulist.removeAllItems() as it would remove
          // the menupopup with our special attributes too.)
          while (this.hasChildNodes())
            this.lastChild.remove();
        ]]></body>
      </method>

      <!-- nsIAbListener methods -->
      <method name="onItemAdded">
        <parameter name="aParentDir"/>
        <parameter name="aItem"/>
        <body><![CDATA[
          // Are we interested in this new directory?
          if (aItem instanceof Ci.nsIAbDirectory &&
              this._matches(aItem)) {
            this._rebuild();
          }
        ]]></body>
      </method>

      <method name="onItemRemoved">
        <parameter name="aParentDir"/>
        <parameter name="aItem"/>
        <body><![CDATA[
          if (aItem instanceof Ci.nsIAbDirectory) {
            // Find the item in the list to remove.
            // We can't use indexOf here because we need loose equality.
            for (var index = this._directories.length; --index >= 0; )
              if (this._directories[index] == aItem)
                break;
            if (index != -1) {
              this._directories.splice(index, 1);
              // Are we removing the selected directory?
              if (this.parentNode.selectedItem ==
                  this.removeChild(this.childNodes[index])) {
                // If so, try to select the first directory, if available.
                if (this.hasChildNodes())
                  this.firstChild.doCommand();
                else
                  this.parentNode.selectedItem = null;
              }
            }
          }
        ]]></body>
      </method>

      <method name="onItemPropertyChanged">
        <parameter name="aItem"/>
        <parameter name="aProperty"/>
        <parameter name="aOldValue"/>
        <parameter name="aNewValue"/>
        <body><![CDATA[
          if (aItem instanceof Ci.nsIAbDirectory) {
            // Find the item in the list to rename.
            // We can't use indexOf here because we need loose equality.
            for (var oldIndex = this._directories.length; --oldIndex >= 0; )
              if (this._directories[oldIndex] == aItem)
                break;
            if (oldIndex != -1)
              this._rebuild();
          }
        ]]></body>
      </method>

      <!-- Private methods -->
      <!-- Tests to see whether this directory should display in the list. -->
      <method name="_matches">
        <parameter name="ab"/>
        <body><![CDATA[
          // This condition is used for instance when creating cards
          if (this.getAttribute("writable") == "true" && ab.readOnly)
            return false;

          // This condition is used for instance when creating mailing lists
          if (this.getAttribute("supportsmaillists") == "true" &&
              !ab.supportsMailingLists)
            return false;

          return this.getAttribute(ab.isRemote ? "localonly" : "remoteonly") != "true";
        ]]></body>
      </method>

      <!-- Sort all our directories. -->
      <method name="_sort">
        <body><![CDATA[
          let lists = {};
          let lastAB;
          // If there are any mailing lists, pull them out of the array temporarily.
          for (let d = 0; d < this._directories.length; d++) {
            if (this._directories[d].isMailList) {
              let [list] = this._directories.splice(d, 1);
              if (!(lastAB in lists))
                lists[lastAB] = [];
              lists[lastAB].push(list);
              d--;
            } else {
              lastAB = this._directories[d].URI;
            }
          }

          this._directories.sort(this._compare);

          // Push mailing lists back appending them after their respective
          // containing addressbook.
          for (let d = this._directories.length - 1; d >= 0; d--) {
            let abURI = this._directories[d].URI;
            if (abURI in lists) {
              lists[abURI].sort(function(a,b) { return a.dirName.localeCompare(b.dirName); });
              let listIndex = d;
              for (let list of lists[abURI]) {
                listIndex++;
                this._directories.splice(listIndex, 0, list);
              }
              delete lists[abURI];
            }
          }
        ]]></body>
      </method>

      <!-- Used to sort directories in order -->
      <method name="_compare">
        <parameter name="a"/>
        <parameter name="b"/>
        <body><![CDATA[
          // Null at the very top.
          if (!a)
            return -1;

          if (!b)
            return 1;

          // Personal at the top.
          const kPersonalAddressbookURI = "moz-abmdbdirectory://abook.mab";
          if (a.URI == kPersonalAddressbookURI)
            return -1;

          if (b.URI == kPersonalAddressbookURI)
            return 1;

          // Collected at the bottom.
          const kCollectedAddressbookURI = "moz-abmdbdirectory://history.mab";
          if (a.URI == kCollectedAddressbookURI)
            return 1;

          if (b.URI == kCollectedAddressbookURI)
            return -1;

          // Sort books of the same type by name.
          if (a.dirType == b.dirType)
            return a.dirName.localeCompare(b.dirName);

          // If one of the dirTypes is PAB and the other is something else,
          // then the other will go below the one of type PAB.
          const PABDirectory = 2;
          if (a.dirType == PABDirectory)
            return -1;

          if (b.dirType == PABDirectory)
            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",
                                                 Ci.nsIPrefLocalizedString).data;
          } else {
            try {
              url = Services.prefs.getComplexValue("mail.addr_book.mapit_url." + aIndex + ".format",
                                                   Ci.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",
                                                         Ci.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 = Cc["@mozilla.org/pref-localizedstring;1"]
                             .createInstance(Ci.nsIPrefLocalizedString);
          defaultUrl.data = aItem.getAttribute("url");
          Services.prefs.setComplexValue("mail.addr_book.mapit_url.format",
                                         Ci.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>