summaryrefslogtreecommitdiffstats
path: root/mailnews/addrbook/content/addrbookWidgets.xml
blob: 2481d5cd9e903e7f280d934f4ed4aaa6adaab621 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
<?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>

      <constructor>
        <![CDATA[
          Components.utils.import("resource:///modules/mailServices.js");
          // Init the address book cache.
          const nsIAbDirectory = Components.interfaces.nsIAbDirectory;
          let directories = MailServices.ab.directories;
          while (directories && directories.hasMoreElements()) {
            var ab = directories.getNext();
            if (ab instanceof nsIAbDirectory && this._matches(ab))
              this._directories.push(ab);
          }

          this._directories.sort(this._compare);

          // Now create menuitems for all displayed directories.
          var menulist = this.parentNode;
          var value = this._value;
          this._directories.forEach(function (ab) {
            menulist.appendItem(ab.dirName, ab[value]);
          });
          if (this.hasAttribute("none")) {
            // Create a dummy menuitem representing no selection.
            this._directories.unshift(null);
            menulist.insertItemAt(0, this.getAttribute("none"), "");
          }

          // Attempt to select the persisted or otherwise first directory.
          menulist.value = menulist.value;
          if (!menulist.selectedItem && this.hasChildNodes())
            menulist.selectedIndex = 0;

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

      <destructor>
        <![CDATA[
          Components.utils.import("resource:///modules/mailServices.js");
          MailServices.ab.removeAddressBookListener(this);

          // Empty out anything in the list.
          while (this.hasChildNodes())
            this.lastChild.remove();
        ]]>
      </destructor>

      <!-- nsIAbListener methods -->
      <method name="onItemAdded">
        <parameter name="aParentDir"/>
        <parameter name="aItem"/>
        <body><![CDATA[
          // Are we interested in this new directory?
          if (aItem instanceof Components.interfaces.nsIAbDirectory &&
              !aItem.isMailList && this._matches(aItem)) {
            this._directories.push(aItem);
            this._directories.sort(this._compare);
            // Insert the new menuitem at the position to which it was sorted.
            this.parentNode.insertItemAt(this._directories.indexOf(aItem),
                                         aItem.dirName, aItem[this._value]);
          }
        ]]></body>
      </method>

      <method name="onItemRemoved">
        <parameter name="aParentDir"/>
        <parameter name="aItem"/>
        <body><![CDATA[
          if (aItem instanceof Components.interfaces.nsIAbDirectory &&
              !aItem.isMailList) {
            // 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)
              // 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 Components.interfaces.nsIAbDirectory &&
              !aItem.isMailList) {
            // 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) {
              // Cache the matching item so that we can use indexOf next time.
              aItem = this._directories[oldIndex];
              var child = this.childNodes[oldIndex];
              child.label = aItem.dirName;
              this._directories.sort(this._compare);
              // Reorder the menuitems if renaming changed the directory index.
              var newIndex = this._directories.indexOf(aItem);
              if (newIndex < oldIndex)
                this.insertBefore(child, this.childNodes[newIndex]);
              else if (newIndex > oldIndex)
                this.insertBefore(child, this.childNodes[newIndex].nextSibling);
            }
          }
        ]]></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>

      <!-- 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",
                                                 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>