1 /* ***** BEGIN LICENSE BLOCK *****
  2  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3  *
  4  * The contents of this file are subject to the Mozilla Public License Version
  5  * 1.1 (the "License"); you may not use this file except in compliance with
  6  * the License. You may obtain a copy of the License at
  7  * http://www.mozilla.org/MPL/
  8  *
  9  * Software distributed under the License is distributed on an "AS IS" basis,
 10  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 11  * for the specific language governing rights and limitations under the
 12  * License.
 13  *
 14  * The Original Code is gContactSync.
 15  *
 16  * The Initial Developer of the Original Code is
 17  * Josh Geenen <gcontactsync@pirules.org>.
 18  * Portions created by the Initial Developer are Copyright (C) 2008-2011
 19  * the Initial Developer. All Rights Reserved.
 20  *
 21  * Contributor(s):
 22  *
 23  * Alternatively, the contents of this file may be used under the terms of
 24  * either the GNU General Public License Version 2 or later (the "GPL"), or
 25  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 26  * in which case the provisions of the GPL or the LGPL are applicable instead
 27  * of those above. If you wish to allow use of your version of this file only
 28  * under the terms of either the GPL or the LGPL, and not to allow others to
 29  * use your version of this file under the terms of the MPL, indicate your
 30  * decision by deleting the provisions above and replace them with the notice
 31  * and other provisions required by the GPL or the LGPL. If you do not delete
 32  * the provisions above, a recipient may use your version of this file under
 33  * the terms of any one of the MPL, the GPL or the LGPL.
 34  *
 35  * ***** END LICENSE BLOCK ***** */
 36 
 37 if (!com) var com = {}; // A generic wrapper variable
 38 // A wrapper for all GCS functions and variables
 39 if (!com.gContactSync) com.gContactSync = {};
 40 
 41 window.addEventListener("load",
 42   /** Initializes the ABOverlay class when the window has finished loading */
 43   function gCS_abOverlayLoadListener(e) {
 44     com.gContactSync.ABOverlay.initialize();
 45   },
 46 false);
 47 
 48 /**
 49  * The Address Book window overlay overrides certain functions to add
 50  * gContactSync fields to the address book.
 51  * @class
 52  */
 53 com.gContactSync.ABOverlay = {
 54   /**
 55    * Special links for various IM protocols
 56    * Format: Type (from Google): protocol
 57    */
 58   links: {
 59     /** AOL Instant Messenger link */
 60     AIM:         "aim:goim?screenname=",
 61     /** MSN Messenger link */
 62     MSN:         "msnim:chat?contact=",
 63     /** Yahoo Me3ssenger link */
 64     YAHOO:       "ymsgr:sendim?",
 65     /** Skype link */
 66     SKYPE:       "skype:",
 67     /** Skype chat link */
 68     SKYPES:      "?chat",
 69     /** Jabber link */
 70     JABBER:      "xmpp:",
 71     /** XMPP link */
 72     XMPP:        "xmpp:",
 73     /** Google Talk link */
 74     GOOGLE_TALK: "gtalk:chat?jid="
 75   },
 76   /**
 77    * Called when the overlay is loaded and overrides some methods to add
 78    * gContactSync fields.
 79    */
 80   initialize: function ABOverlay_initialize() {
 81     // determine if this is before or after Bug 413260 landed
 82     var card = Components.classes["@mozilla.org/addressbook/cardproperty;1"]
 83                          .createInstance(Components.interfaces.nsIAbCard);
 84     this.mBug413260 = card.getProperty ? true : false;
 85 
 86     com.gContactSync.originalOnLoadCardView = OnLoadCardView;
 87     OnLoadCardView = this.myOnLoadCardView;
 88     if (com.gContactSync.Preferences.mSyncPrefs.enableSyncBtn.value)
 89       this.setupButton();    // insert the Sync button
 90     // add the extra attributes as tree columns to show and
 91     this.addTreeCols(); // sort by in the results pane if this is after 413260 
 92     // override the onDrop method of abDirTreeObserver
 93     // so when a card is copied the extra attributes are copied with it
 94     if (com.gContactSync.Preferences.mSyncPrefs.overrideCopy.value)
 95       abDirTreeObserver.onDrop = com.gContactSync.myOnDrop;
 96     // override the display card view pane
 97     com.gContactSync.originalDisplayCardViewPane = DisplayCardViewPane;
 98     DisplayCardViewPane = this.myDisplayCardViewPane;
 99     // Add a reset menuitem to the directory tree context menu
100     if (com.gContactSync.Preferences.mSyncPrefs.addReset.value) {
101       this.addResetContext();
102     }
103     // override the ab results tree function
104     //com.gContactSync.originalSetAbView = SetAbView;
105     //SetAbView = com.gContactSync.SetAbView;
106     // Fix the style for description elements accidentally set in the
107     // Duplicate Contacts Manager extension
108     // https://www.mozdev.org/bugs/show_bug.cgi?id=21883
109     if (com.gContactSync.Preferences.mSyncPrefs.fixDupContactManagerCSS.value)
110       this.fixDescriptionStyle();
111     // load the card view (required by seamonkey)
112     if (document.getElementById("ab-menubar"))
113       this.myOnLoadCardView();
114   },
115   /**
116    * Adds treecol elements to the address book results tree that shows cards in
117    * the currently selected directory.  These treecols allow the user to show
118    * and sort by extra attributes that are added by this extension.  This will
119    * only work after Bug 413260 landed, so in Thunderbird 3.0b1pre and after.
120    */
121   addTreeCols: function ABOverlay_addTreeCols() {
122     // get the treecols XUL element
123     var treeCols = document.getElementById("abResultsTreeCols");
124     if (!treeCols || !treeCols.appendChild)
125       return;
126     // if Bug 413260 isn't applied in this version of TB, or if the pref was
127     // changed to false, then stop here
128     if (!this.mBug413260 || !com.gContactSync.Preferences.mSyncPrefs.newColLabels.value)
129       return;
130     // get the added attributes
131     var ids = com.gContactSync.ContactConverter.getExtraSyncAttributes(false, false),
132         id, splitter, treeCol;
133     // iterate through every added attribute and add a treecol for it unless
134     // it is a postal address
135     for (i = 0, length = ids.length; i < length; i++) {
136       id = ids[i];
137       if (id.indexOf("Type") !== -1)
138         continue; // skip addresses and Types
139       // make and add the splitter first
140       splitter = document.createElement("splitter");
141       splitter.setAttribute("class", "tree-splitter");
142       treeCols.appendChild(splitter);
143       // make the new treecol
144       treeCol = document.createElement("treecol");
145       // then set it up with the ID and other attributes
146       treeCol.setAttribute("id",      id);
147       treeCol.setAttribute("class",   "sortDirectionIndicator");
148       treeCol.setAttribute("hidden",  "true");
149       treeCol.setAttribute("persist", "hidden ordinal width sortDirection");
150       treeCol.setAttribute("flex",    "1");
151       treeCol.setAttribute("label",   com.gContactSync.StringBundle.getStr(id));
152       // append it to the treecols element
153       treeCols.appendChild(treeCol);
154     }
155     
156     // Fix/rename phone number columns if the phoneColLabels is on AND phone
157     // number types have been added.
158     if (com.gContactSync.Preferences.mSyncPrefs.phoneColLabels.value &&
159         com.gContactSync.Preferences.mSyncPrefs.phoneTypes.value) {
160       // fix/rename the existing phone numbers
161       var arr = ["WorkPhone", "HomePhone", "FaxNumber", "CellularNumber",
162                  "PagerNumber", "HomeFaxNumber", "OtherNumber"];
163       // the strings from the string bundle
164       var arr2 = ["firstNumber", "secondNumber", "thirdNumber", "fourthNumber",
165                   "fifthNumber", "sixthNumber", "seventhNumber"];
166       var elem;
167       for (var i = 0; i < arr.length; i++) {
168         elem = document.getElementById(arr[i]);
169         if (!elem) {
170           continue;
171         }
172         // remove it
173         treeCols.removeChild(elem);
174         elem.setAttribute("label", com.gContactSync.StringBundle.getStr(arr2[i]));
175         // and then add it to the end of the treecols element
176         treeCols.appendChild(elem);
177       }
178     }
179   },
180   /**
181    * Sets up the Sync button to go between the Write and Delete buttons and adds
182    * a separator between Sync and Delete.
183    * @returns {boolean} True if the button was added manually.
184    */
185   setupButton: function ABOverlay_setupButton() {
186     try {
187       com.gContactSync.LOGGER.VERBOSE_LOG("Trying to add button");
188       // get the toolbar with the buttons
189       var toolbar     = document.getElementById("abToolbar"); // seamonkey
190       if (!toolbar) {
191         com.gContactSync.LOGGER.VERBOSE_LOG("Didn't find the toolbar");
192         return false;
193       }
194       // setup the separators
195       var separator   = document.createElement("toolbarseparator");
196       var separator2  = document.createElement("toolbarseparator");
197       // setup the button
198       var button      = document.createElement("toolbarbutton");
199       button.setAttribute("class", "gContactSync-Button toolbarbutton-1" + 
200                           " chromeclass-toolbar-additional");
201       button.setAttribute("id",           "button-sync");
202       button.setAttribute("label",
203                           com.gContactSync.StringBundle.getStr("syncButton"));
204       button.setAttribute("oncommand",    "com.gContactSync.Sync.begin();");
205       button.setAttribute("tooltiptext",
206                           com.gContactSync.StringBundle.getStr("syncTooltip"));
207       button.setAttribute("insertbefore", "new-separator");
208 
209       var deleteButton = document.getElementById("button-delete");
210       var writeButton  = document.getElementById("button-newmessage");
211       var addedButton  = false;
212       // first, try to insert it after the delete button
213       if (deleteButton) {
214         try {
215           // insert the separator before the Delete button
216           toolbar.insertBefore(separator, deleteButton);
217           // insert the button before the separator
218           toolbar.insertBefore(button, separator);
219           com.gContactSync.LOGGER.VERBOSE_LOG("Added the button before the delete button");
220           addedButton = true;
221           // insert the second separator before the button if necessary
222           if (button.previousSibling && button.previousSibling.nodeName != "toolbarseparator") {
223               toolbar.insertBefore(separator2, button);
224               com.gContactSync.LOGGER.VERBOSE_LOG("Also added a separator before the button");
225           }
226         }
227         catch (e) {
228           com.gContactSync.LOGGER.LOG_WARNING("Couldn't setup the sync button before the delete button", e);
229         }
230       }
231       // if that doesn't work, try after the write button
232       if (writeButton && !addedButton) {
233         try {
234           // insert the separator before the Write button
235           toolbar.insertBefore(separator, writeButton);
236           // insert the button before the separator
237           toolbar.insertBefore(button, separator);
238           com.gContactSync.LOGGER.VERBOSE_LOG("Added the button before the compose button");
239           com.gContactSync.LOGGER.VERBOSE_LOG("Added a separator after the button");
240           addedButton = true;
241           // insert the second separator before the button if necessary
242           if (button.previousSibling && button.previousSibling.nodeName != "toolbarseparator") {
243               toolbar.insertBefore(separator2, button);
244               com.gContactSync.LOGGER.VERBOSE_LOG("Added a separator before the button");
245           }
246         }
247         catch (e) {
248           com.gContactSync.LOGGER.LOG_WARNING("Couldn't setup the sync button before the write button", e);
249         }
250       }
251       // if all else fails try to append the button at the end of the toolbar
252       if (!addedButton) {
253         com.gContactSync.LOGGER.VERBOSE_LOG("Attempting to append the toolbar button");
254         toolbar.appendChild(separator);
255         toolbar.appendChild(button);
256       }
257       if (com.gContactSync.Preferences.mSyncPrefs.forceBtnImage.value) {
258         com.gContactSync.LOGGER.VERBOSE_LOG("Forcing the listStyleImage for the button");
259         document.getElementById("button-sync").style.listStyleImage =
260           "url('chrome://gcontactsync/skin/logo_main_24.png')";
261       }
262       com.gContactSync.LOGGER.VERBOSE_LOG("Finished adding button\n");
263       return true;
264     }
265     catch(e) {
266        com.gContactSync.LOGGER.LOG_WARNING("Couldn't setup the sync button", e);
267     }
268     return false;
269   },
270   /**
271    * Modifies the SetAbView function.  Unused
272    */
273   // NOTE - this function can break search and more if not overridden properly
274   mySetAbView: function ABOverlay_mySetAbView(aURI, aSearchView, aSortCol, aSortDir) {
275     // call the original
276     com.gContactSync.originalSetAbView.apply(this, arguments);
277     // TODO fix this
278     /*
279     var children =  gAbResultsTree.getElementsByAttribute("ondraggesture", "nsDragAndDrop.startDrag(event, abResultsPaneObserver);");
280     var treeChildren = children[0];
281     var str = "";
282     for (var i = 0; i < children[0].children.length; i++) {
283       str += children[0].children[i] + "\n";
284     }
285     com.gContactSync.alert(str + "\n" + children[0].children);
286     */
287     /*for (var i in gAbResultsTree.children[0])
288       str += i + "\n";
289     str += "1:\n";
290     for (var i in gAbResultsTree.children[1])
291       str += i + "\n";
292     com.gContactSync.alert(str);*/
293     // now find and hide any dummy e-mail addresses
294   },
295   /**
296    * Updates the Card View pane boxes and headers for whether or not they should
297    * be visible based on additional attributes added by gContactSync.
298    * Links the third and fourth e-mail address as well as the "other" address.
299    * Should be set to override the DisplayCardViewPane function in
300    * abCardViewOverlay.js.  Requires that the original function should be set as
301    * the com.gContactSync.originalDisplayCardViewPane variable.
302    * @param aCard {nsIAbCard} The card being viewed.
303    */
304   myDisplayCardViewPane: function ABOverlay_myDisplayCardViewPane(aCard) {
305     // call the original first
306     com.gContactSync.originalDisplayCardViewPane.apply(this, arguments);
307     if (aCard.isMailList) {
308       // collapse all the attributes added
309       com.gContactSync.ABOverlay.hideNodes(com.gContactSync.ContactConverter.getExtraSyncAttributes(false, true));
310       try {
311         // then collapse the e-mail boxes
312         cvData.cvThirdEmailBox.collapsed = true;
313         cvData.cvFourthEmailBox.collapsed = true;
314       } catch(e) {}
315       return; // and quit, nothing was added for mail lists
316     }
317     try {
318       var contact;
319       
320       // If able to get the selected directory then show the lists this contact
321       // is in
322       if (GetSelectedDirectory === undefined || !cvData.cvLists) {
323         contact = new com.gContactSync.TBContact(aCard, null);
324       } else if (GetSelectedDirectory()) {
325         
326         var ab = (com.gContactSync.GAbManager.getAllAddressBooks(2))[GetSelectedDirectory()];
327         
328         if (!ab) {
329           // It is a mailing list, truncate everything from the last "/" to
330           // get the AB's URI.
331           ab = (com.gContactSync.GAbManager.getAllAddressBooks(2))
332                   [GetSelectedDirectory().substring(0,GetSelectedDirectory().lastIndexOf("/"))];
333         }
334         
335         if (!ab) {
336           cvData.cvLists.parentNode.collapsed = true;
337           contact = new com.gContactSync.TBContact(aCard, null);
338         } else {
339           cvData.cvLists.parentNode.collapsed = false;
340           contact = new com.gContactSync.TBContact(aCard, ab);
341           
342           // Get all lists in this AB, but do NOT get it's contacts yet
343           var lists = ab.getAllLists(true);
344           var listsWithContact = [];
345           
346           // Iterate through each list in this AB
347           // If the list is "broken" (enumeration through it's contacts fails)
348           // then just ignore the error and move on.
349           for (var i in lists) {
350             lists[i].setIgnoreIfBroken(true);
351             lists[i].getAllContacts();
352             if (lists[i].hasContact(contact)) {
353               listsWithContact.push(lists[i].getName());
354             }
355             lists[i].setIgnoreIfBroken(false);
356           }
357           
358           cvData.cvLists.data = listsWithContact.join(" | ");
359         }
360       }
361       com.gContactSync.ABOverlay.showNodes(com.gContactSync.ContactConverter.getExtraSyncAttributes(false, true));
362       var primaryEmail = com.gContactSync.GAbManager.getCardValue(aCard,
363                                                  com.gContactSync.dummyEmailName);
364       // if the primary e-mail address is the dummy address, hide it
365       if (com.gContactSync.isDummyEmail(primaryEmail)) {
366         // TODO recalculate if the contact info box must be collapsed too
367         switch (com.gContactSync.dummyEmailName) {
368           case "PrimaryEmail" :
369             cvData.cvEmail1Box.collapsed = true;
370             break;
371           case "SecondEmail" :
372             cvData.cvEmail2Box.collapsed = true;
373             break;
374           default:
375             com.gContactSync.alertError("Error - invalid dummy email name");
376         }
377       }
378       cvData.cvThirdEmailBox.collapsed = false;
379       cvData.cvFourthEmailBox.collapsed = false;
380       // Contact section (ThirdEmail, FourthEmail, TalkScreenName, MSNScreenName,
381       // JabberScreenName, YahooScreenName, ICQScreenName)
382       var visible     = !cvData.cvbContact.getAttribute("collapsed");
383       // don't show the Third and Fourth e-mail addresses in Postbox
384       if (!contact.mPostbox) {
385         var thirdEmail  = contact.getValue("ThirdEmail");
386         var fourthEmail = contact.getValue("FourthEmail");
387         visible = HandleLink(cvData.cvThirdEmail, com.gContactSync.StringBundle.getStr("ThirdEmail"),
388                              thirdEmail, cvData.cvThirdEmailBox, "mailto:" +
389                              thirdEmail) || visible;
390         // Workaround for a bug where the collapsed attributes set here don't
391         // seem to get applied
392         document.getElementById(cvData.cvThirdEmailBox.id).collapsed = cvData.cvThirdEmailBox.collapsed;
393         visible = HandleLink(cvData.cvFourthEmail, com.gContactSync.StringBundle.getStr("FourthEmail"),
394                              fourthEmail, cvData.cvFourthEmailBox, "mailto:" +
395                              fourthEmail) || visible;
396       }
397     
398       visible = com.gContactSync.ABOverlay.getVisible(aCard, ["TalkScreenName",
399                                                               "JabberScreenName",
400                                                               "YahooScreenName",
401                                                               "MSNScreenName",
402                                                               "ICQScreenName"],
403                                                       visible, true);
404       cvSetVisible(cvData.cvhContact, visible);
405       cvSetVisible(cvData.cvbContact, visible);
406       // Other section (OtherAddress)
407       var visible = !cvData.cvhOther.getAttribute("collapsed");
408       visible = com.gContactSync.ABOverlay.getVisible(aCard, ["OtherAddress"], visible);
409       cvSetVisible(cvData.cvhOther, visible);
410       cvSetVisible(cvData.cvbOther, visible);
411       // setup the OtherAddress MapIt button 
412       if (cvData.cvOtherAddress && cvData.cvOtherAddress.childNodes[0] &&
413           cvData.cvOtherAddress.childNodes[0].nodeValue) {
414         var baseUrl = "http://maps.google.com/maps?q=";
415         var address = cvData.cvOtherAddress.childNodes[0].nodeValue;
416         // remove the label
417         var index = address.indexOf(":")
418         if (index != -1 && address.length > index + 2)
419           address = address.substring(address.indexOf(":") + 2);
420         cvData.cvOtherMapIt.setAttribute("url",  baseUrl + encodeURIComponent(address));
421         cvSetVisible(cvData.cvbOtherMapItBox, true);
422       }  
423       else {
424         cvData.cvOtherMapIt.setAttribute("url", "");
425         cvSetVisible(cvData.cvbOtherMapItBox, false);
426       }
427       // setup the OtherAddress MapIt button 
428       if (cvData.cvOtherAddress && cvData.cvOtherAddress.childNodes[0] &&
429           cvData.cvOtherAddress.childNodes[0].nodeValue) {
430         var baseUrl = "http://maps.google.com/maps?q=";
431         var address = cvData.cvOtherAddress.childNodes[0].nodeValue;
432         // remove the label
433         var index = address.indexOf(":");
434         if (index != -1 && address.length > index + 2)
435           address = address.substring(address.indexOf(":") + 2);
436         cvData.cvOtherMapIt.setAttribute("url",  baseUrl + encodeURIComponent(address));
437         cvSetVisible(cvData.cvbOtherMapItBox, true);
438       }  
439       else {
440         cvData.cvOtherMapIt.setAttribute("url", "");
441         cvSetVisible(cvData.cvbOtherMapItBox, false);
442       }
443       // Home Section (FullHomeAddress)
444       var visible = !cvData.cvhHome.getAttribute("collapsed");
445       visible = com.gContactSync.ABOverlay.getVisible(aCard, ["FullHomeAddress"], visible);
446       cvSetVisible(cvData.cvhHome, visible);
447       cvSetVisible(cvData.cvbHome, visible);
448       // setup the HomeAddress MapIt button 
449       if (cvData.cvFullHomeAddress && cvData.cvFullHomeAddress.childNodes[0] &&
450           cvData.cvFullHomeAddress.childNodes[0].nodeValue) {
451         var baseUrl = "http://maps.google.com/maps?q=";
452         var address = cvData.cvFullHomeAddress.childNodes[0].nodeValue;
453         // remove the label
454         var index = address.indexOf(":")
455         if (index != -1 && address.length > index + 2)
456           address = address.substring(address.indexOf(":") + 2);
457         cvData.cvFullHomeMapIt.setAttribute("url",  baseUrl + encodeURIComponent(address));
458         cvSetVisible(cvData.cvbFullHomeMapItBox, true);
459         // hide the old home address...
460         com.gContactSync.ABOverlay.collapseAddress("Home");
461       }  
462       else {
463         cvData.cvFullHomeMapIt.setAttribute("url", "");
464         cvSetVisible(cvData.cvbFullHomeMapItBox, false);
465       }
466       // Work Section (FullWorkAddress)
467       var visible = !cvData.cvhWork.getAttribute("collapsed");
468       visible = com.gContactSync.ABOverlay.getVisible(aCard, ["FullWorkAddress"], visible);
469       cvSetVisible(cvData.cvhWork, visible);
470       cvSetVisible(cvData.cvbWork, visible);
471       // setup the WorkAddress MapIt button 
472       if (cvData.cvFullWorkAddress && cvData.cvFullWorkAddress.childNodes[0] &&
473           cvData.cvFullWorkAddress.childNodes[0].nodeValue) {
474         var baseUrl = "http://maps.google.com/maps?q=";
475         var address = cvData.cvFullWorkAddress.childNodes[0].nodeValue;
476         // remove the label
477         var index = address.indexOf(":");
478         if (index != -1 && address.length > index + 2)
479           address = address.substring(address.indexOf(":") + 2);
480         cvData.cvFullWorkMapIt.setAttribute("url",  baseUrl + encodeURIComponent(address));
481         cvSetVisible(cvData.cvbFullWorkMapItBox, true);
482         // hide the old Work address...
483         com.gContactSync.ABOverlay.collapseAddress("Work");
484       }  
485       else {
486         cvData.cvFullWorkMapIt.setAttribute("url", "");
487         cvSetVisible(cvData.cvbFullWorkMapItBox, false);
488       }
489       // Other section (relations)
490       var visible = !cvData.cvhOther.getAttribute("collapsed");
491       // Relation fields
492       visible = com.gContactSync.ABOverlay.getVisible(aCard, ["Relation0",
493                                                               "Relation1",
494                                                               "Relation2",
495                                                               "Relation3"],
496                                                       visible, true);
497       cvSetVisible(cvData.cvhOther, visible);
498       cvSetVisible(cvData.cvbOther, visible);
499       // Phone section (add OtherNumber and HomeFaxNumber)
500       // first, add the existing nodes to cvData under a name that actually
501       // matches the attribute
502       cvData.cvWorkPhone = cvData.cvPhWork;
503       cvData.cvHomePhone = cvData.cvPhHome;
504       cvData.cvFaxNumber = cvData.cvPhFax;
505       cvData.cvCellularNumber = cvData.cvPhCellular;
506       cvData.cvPagerNumber = cvData.cvPhPager;
507       // then set the value and labels for the new and old phone nodes
508       var visible = !cvData.cvhPhone.getAttribute("collapsed");
509       visible = com.gContactSync.ABOverlay.getVisible(aCard, ["WorkPhone", "HomePhone", "FaxNumber",
510                                            "CellularNumber", "PagerNumber",
511                                            "OtherNumber", "HomeFaxNumber"],
512                                    visible, true);
513       cvSetVisible(cvData.cvhPhone, visible);
514       cvSetVisible(cvData.cvbPhone, visible);
515     } catch(e) { 
516         com.gContactSync.alertError("Error while modifying view pane: " + e);
517         com.gContactSync.LOGGER.LOG_WARNING("Error while modifying the view pane.", e);
518     }
519   },
520   /**
521    * Collapses (hides) the old components of an address: Address Line 1 and 2,
522    * the City, State Zip line, and the Country of the given type (Home or Work).
523    * @param aPrefix {string} The type of address.  Must be 'Home' or 'Work'.
524    */
525   collapseAddress: function ABOverlay_collapseAddress(aPrefix) {
526     if (!aPrefix || (aPrefix != "Home" && aPrefix != "Work"))
527       return;
528     var arr = ["Address", "Address2", "CityStZip", "Country"];
529     for (var i = 0, length = arr.length; i < length; i++) {
530       var id = "cv" + aPrefix + arr[i];
531       var elem = document.getElementById(id);
532       if (elem && elem.setAttribute)
533         elem.setAttribute("collapsed", true);
534     }
535     var mapItBox = document.getElementById("cvb" + aPrefix + "MapItBox");
536     if (mapItBox && mapItBox.setAttribute)
537       mapItBox.setAttribute("collapsed", true);
538   },
539   /**
540    * Hides all of the nodes based on the array.  The node must be a propery of
541    * cvData with the same name as the element in aArray prefixed with a 'cv'.
542    * For example, to hide cvData.cvHomeAddress the element would be
543    * 'HomeAddress'.
544    * @param {array} aArray An array of names as described above.
545    */
546   hideNodes: function ABOverlay_hideNodes(aArray) {
547     for (var i = 0, length = aArray.length; i < length; i++) {
548       if (aArray[i].indexOf("Type") != -1)
549         continue;
550       try {
551         cvSetVisible(cvData["cv" + aArray[i]], false);
552       }
553       catch (e) {
554         com.gContactSync.LOGGER.LOG_WARNING("Error while hiding node '" + aArray[i] + "'", e);
555       }
556     }
557   },
558   /**
559    * Shows all of the nodes based on the array.  The node must be a propery of
560    * cvData with the same name as the element in aArray prefixed with a 'cv'.
561    * For example, to show cvData.cvHomeAddress the element would be
562    * 'HomeAddress'.
563    * @param aArray {array} An array of names as described above.
564    */
565   showNodes: function ABOverlay_showNodes(aArray) {
566     for (var i = 0, length = aArray.length; i < length; i++) {
567       if (aArray[i].indexOf("Type") != -1)
568         continue;
569       try {
570         cvSetVisible(cvData["cv" + aArray[i]], true);
571       }
572       catch (e) {
573         com.gContactSync.LOGGER.LOG_WARNING("Error while showing node '" + aArray[i] + "'", e);
574       }
575     }
576   },
577   /**
578    * A helper method for myDisplayCardViewPane that iterates through an array of
579    * attributes and returns true if at least one of them is present in the given
580    * card.
581    * @param aCard         {nsIAbCard} The card whose attributes are checked.
582    * @param aArray        {array}     The array of attributes to check for in
583    *                                  the card.
584    * @param aVisible      {boolean}   Optional. True if the element was
585    *                                  previously visible.
586    * @param aUseTypeLabel {boolean}   Optional.  True if the labels should be
587    *                                  the type of the attribute instead of the
588    *                                  attribute's name.
589    * @returns {boolean} True if at least one attribute in aArray is present in aCard.
590    */
591   getVisible: function ABOverlay_getVisible(aCard, aArray, aVisible, aUseTypeLabel) {
592     var visible = aVisible;
593     // return true if the card has the current attribute
594     for (var i = 0; i < aArray.length; i++) {
595       var attr = aArray[i];
596       var value = com.gContactSync.GAbManager.getCardValue(aCard, attr);
597       // get the name of the string to find in the bundle
598       var label = aUseTypeLabel ? com.gContactSync.GAbManager.getCardValue(aCard, attr + "Type")
599                                 : attr;
600       // get the actual string
601       // if the label is null (ie aUseTypeLabel was true, but there wasn't a type)
602       // then use the attribute's string as a default value
603       var str = label && label != "" ? com.gContactSync.StringBundle.getStr(label)
604                                      : com.gContactSync.StringBundle.getStr(attr);
605       var prefix = this.links[label];
606       // Make this a link if there is a prefix (aim:goim?...), the pref is set,
607       // and there is a box for this data
608       if (prefix && com.gContactSync.Preferences.mSyncPrefs.enableImUrls.value && cvData["cv" + attr + "Box"]) {
609         var suffix = this.links[label + "S"] ? this.links[label + "S"] : "";
610         visible    = HandleLink(cvData["cv" + attr], str, value,
611                                 cvData["cv" + attr + "Box"], prefix +
612                                 value + suffix) || visible;
613       }
614       else
615         visible = cvSetNodeWithLabel(cvData["cv" + attr], str, value) || visible;
616     }
617     return visible;
618   },
619   /**
620    * Sets up a few nodes and labels in addition to what the OnLoadCardView
621    * function does in abCardViewOverlay.js.  Should be run when the Overlay is
622    * loaded.
623    */
624   myOnLoadCardView: function ABOverlay_myOnLoadCardView() {
625     if (!com.gContactSync.originalOnLoadCardView)
626       return;
627     com.gContactSync.originalOnLoadCardView.apply(this, arguments);
628 
629     // add the <description> elements
630     var vbox = document.getElementById("cvbContact");
631     // setup the third and fourth e-mail addresses
632     var xhtml = "http://www.w3.org/1999/xhtml";
633     cvData.cvThirdEmailBox = com.gContactSync.ABOverlay.makeDescElement("ThirdEmailBox",
634                                                                         "CardViewLink");
635     cvData.cvThirdEmail = document.createElementNS(xhtml, "html:a");
636     cvData.cvThirdEmail.setAttribute("id", "ThirdEmail");
637     cvData.cvThirdEmailBox.appendChild(cvData.cvThirdEmail);
638     cvData.cvFourthEmailBox = com.gContactSync.ABOverlay.makeDescElement("FourthEmailBox",
639                                                                          "CardViewLink");
640     cvData.cvFourthEmail = document.createElementNS(xhtml, "html:a");
641     cvData.cvFourthEmail.setAttribute("id", "FourthEmail");
642     cvData.cvFourthEmailBox.appendChild(cvData.cvFourthEmail);
643     vbox.insertBefore(cvData.cvFourthEmailBox, document.getElementById("cvScreennameBox"));
644     vbox.insertBefore(cvData.cvThirdEmailBox, cvData.cvFourthEmailBox);
645     
646     // the screennames
647     if (com.gContactSync.Preferences.mSyncPrefs.enableImUrls.value) {
648       cvData.cvTalkScreenNameBox  = com.gContactSync.ABOverlay.makeDescElement("TalkScreenNameBox",
649                                                                                "CardViewLink");
650       cvData.cvTalkScreenName     = document.createElementNS(xhtml, "html:a");
651       cvData.cvTalkScreenName.setAttribute("id", "TalkScreenName");
652       cvData.cvTalkScreenNameBox.appendChild(cvData.cvTalkScreenName);
653       cvData.cvICQScreenNameBox   = com.gContactSync.ABOverlay.makeDescElement("ICQScreenNameBox",
654                                                                                "CardViewLink");
655       cvData.cvICQScreenName      = document.createElementNS(xhtml, "html:a");
656       cvData.cvICQScreenName.setAttribute("id", "ICQScreenName");    
657       cvData.cvICQScreenNameBox.appendChild(cvData.cvICQScreenName);
658       cvData.cvYahooScreenNameBox  = com.gContactSync.ABOverlay.makeDescElement("YahooScreenNameBox",
659                                                                                 "CardViewLink");
660       cvData.cvYahooScreenName     = document.createElementNS(xhtml, "html:a");
661       cvData.cvYahooScreenName.setAttribute("id", "YahooScreenName");    
662       cvData.cvYahooScreenNameBox.appendChild(cvData.cvYahooScreenName);
663       cvData.cvMSNScreenNameBox    = com.gContactSync.ABOverlay.makeDescElement("MSNScreenNameBox",
664                                                                                 "CardViewLink");
665       cvData.cvMSNScreenName       = document.createElementNS(xhtml, "html:a");
666       cvData.cvMSNScreenName.setAttribute("id", "MSNScreenName");    
667       cvData.cvMSNScreenNameBox.appendChild(cvData.cvMSNScreenName);
668       cvData.cvJabberScreenNameBox = com.gContactSync.ABOverlay.makeDescElement("JabberScreenNameBox",
669                                                                                 "CardViewLink");
670       cvData.cvJabberScreenName    = document.createElementNS(xhtml, "html:a");
671       cvData.cvJabberScreenName.setAttribute("id", "JabberScreenName");
672       cvData.cvJabberScreenNameBox.appendChild(cvData.cvJabberScreenName);
673       
674       vbox.appendChild(cvData.cvTalkScreenNameBox);
675       vbox.appendChild(cvData.cvICQScreenNameBox);
676       vbox.appendChild(cvData.cvMSNScreenNameBox);
677       vbox.appendChild(cvData.cvYahooScreenNameBox);
678       vbox.appendChild(cvData.cvJabberScreenNameBox);
679     }
680     else {
681       cvData.cvTalkScreenName   = com.gContactSync.ABOverlay.makeDescElement("TalkScreenName",   "CardViewText");
682       cvData.cvICQScreenName    = com.gContactSync.ABOverlay.makeDescElement("ICQScreenName",    "CardViewText");
683       cvData.cvYahooScreenName  = com.gContactSync.ABOverlay.makeDescElement("YahooScreenName",  "CardViewText");
684       cvData.cvMSNScreenName    = com.gContactSync.ABOverlay.makeDescElement("MSNScreenName",    "CardViewText");
685       cvData.cvJabberScreenName = com.gContactSync.ABOverlay.makeDescElement("JabberScreenName", "CardViewText");
686       vbox.appendChild(cvData.cvTalkScreenName);
687       vbox.appendChild(cvData.cvICQScreenName);
688       vbox.appendChild(cvData.cvMSNScreenName);
689       vbox.appendChild(cvData.cvYahooScreenName);
690       vbox.appendChild(cvData.cvJabberScreenName);
691     }
692     // Other Address
693     vbox = document.getElementById("cvbOther");
694     var otherHbox = document.createElement("hbox");
695     var otherVbox = document.createElement("vbox");
696     otherVbox.setAttribute("flex", "1");
697     cvData.cvOtherAddress = com.gContactSync.ABOverlay.makeDescElement("OtherAddress", "CardViewText");
698     if (Components.classes["@mozilla.org/abmanager;1"]) // TB 3 - style should be pre-wrap
699       cvData.cvOtherAddress.setAttribute("style", "white-space: pre-wrap;");
700     else // TB 2 - the style should be -moz-pre-wrap
701       cvData.cvOtherAddress.setAttribute("style", "white-space: -moz-pre-wrap;");
702     // setup the MapIt box
703     cvData.cvbOtherMapItBox = document.createElement("vbox");
704     cvData.cvbOtherMapItBox.setAttribute("id",   "cvbOtherMapItBox");
705     cvData.cvbOtherMapItBox.setAttribute("pack", "end");
706     cvData.cvOtherMapIt = document.createElement("button");
707     cvData.cvOtherMapIt.setAttribute("label",
708                                      com.gContactSync.StringBundle.getStr("getMap"));
709     cvData.cvOtherMapIt.setAttribute("url",       "");
710     cvData.cvOtherMapIt.setAttribute("oncommand", "MapIt('cvOtherMapIt');");
711     cvData.cvOtherMapIt.setAttribute("tooltip",
712                                      com.gContactSync.StringBundle.getStr("getMapTooltip"));
713     cvData.cvOtherMapIt.setAttribute("id",        "cvOtherMapIt");
714     otherVbox.appendChild(cvData.cvOtherAddress);
715     cvData.cvbOtherMapItBox.appendChild(cvData.cvOtherMapIt);
716     otherHbox.appendChild(otherVbox);
717     otherHbox.appendChild(cvData.cvbOtherMapItBox);
718     vbox.appendChild(otherHbox);
719     // FullHomeAddress
720     vbox = document.getElementById("cvbHome");
721     var FullHomeHbox = document.createElement("hbox");
722     var FullHomeVbox = document.createElement("vbox");
723     FullHomeVbox.setAttribute("flex", "1");
724     cvData.cvFullHomeAddress = com.gContactSync.ABOverlay.makeDescElement("FullHomeAddress", "CardViewText");
725     if (Components.classes["@mozilla.org/abmanager;1"]) // TB 3 - style should be pre-wrap
726       cvData.cvFullHomeAddress.setAttribute("style", "white-space: pre-wrap;");
727     else // TB 2 - the style should be -moz-pre-wrap
728       cvData.cvFullHomeAddress.setAttribute("style", "white-space: -moz-pre-wrap;");
729     // setup the MapIt box
730     cvData.cvbFullHomeMapItBox = document.createElement("vbox");
731     cvData.cvbFullHomeMapItBox.setAttribute("id", "cvbFullHomeMapItBox");
732     cvData.cvbFullHomeMapItBox.setAttribute("pack", "end");
733     cvData.cvFullHomeMapIt = document.createElement("button");
734     cvData.cvFullHomeMapIt.setAttribute("label",
735                                         com.gContactSync.StringBundle.getStr("getMap"));
736     cvData.cvFullHomeMapIt.setAttribute("url",        "");
737     cvData.cvFullHomeMapIt.setAttribute("oncommand", "MapIt('cvFullHomeMapIt');");
738     cvData.cvFullHomeMapIt.setAttribute("tooltip",
739                                         com.gContactSync.StringBundle.getStr("getMapTooltip"));
740     cvData.cvFullHomeMapIt.setAttribute("id",        "cvFullHomeMapIt");
741     FullHomeVbox.appendChild(cvData.cvFullHomeAddress);
742     cvData.cvbFullHomeMapItBox.appendChild(cvData.cvFullHomeMapIt);
743     FullHomeHbox.appendChild(FullHomeVbox);
744     FullHomeHbox.appendChild(cvData.cvbFullHomeMapItBox);
745     var homeWebPageBox = document.getElementById("cvHomeWebPageBox");
746     if (homeWebPageBox)
747       vbox.insertBefore(FullHomeHbox, homeWebPageBox);
748     else
749       vbox.appendChild(FullHomeHbox);
750     // FullWorkAddress
751     vbox = document.getElementById("cvbWork");
752     var FullWorkHbox = document.createElement("hbox");
753     var FullWorkVbox = document.createElement("vbox");
754     FullWorkVbox.setAttribute("flex", "1");
755     cvData.cvFullWorkAddress = com.gContactSync.ABOverlay.makeDescElement("FullWorkAddress", "CardViewText");
756     if (Components.classes["@mozilla.org/abmanager;1"]) // TB 3 - style should be pre-wrap
757       cvData.cvFullWorkAddress.setAttribute("style", "white-space: pre-wrap;");
758     else // TB 2 - the style should be -moz-pre-wrap
759       cvData.cvFullWorkAddress.setAttribute("style", "white-space: -moz-pre-wrap;");
760     // setup the MapIt box
761     cvData.cvbFullWorkMapItBox = document.createElement("vbox");
762     cvData.cvbFullWorkMapItBox.setAttribute("id", "cvbFullWorkMapItBox");
763     cvData.cvbFullWorkMapItBox.setAttribute("pack", "end");
764     cvData.cvFullWorkMapIt = document.createElement("button");
765     cvData.cvFullWorkMapIt.setAttribute("label", com.gContactSync.StringBundle.getStr("getMap"));
766     cvData.cvFullWorkMapIt.setAttribute("url", "");
767     cvData.cvFullWorkMapIt.setAttribute("oncommand", "MapIt('cvFullWorkMapIt');");
768     cvData.cvFullWorkMapIt.setAttribute("tooltip", com.gContactSync.StringBundle.getStr("getMapTooltip"));
769     cvData.cvFullWorkMapIt.setAttribute("id", "cvFullWorkMapIt");
770     FullWorkVbox.appendChild(cvData.cvFullWorkAddress);
771     cvData.cvbFullWorkMapItBox.appendChild(cvData.cvFullWorkMapIt);
772     FullWorkHbox.appendChild(FullWorkVbox);
773     FullWorkHbox.appendChild(cvData.cvbFullWorkMapItBox);
774     var WorkWebPageBox = document.getElementById("cvWorkWebPageBox");
775     if (WorkWebPageBox)
776       vbox.insertBefore(FullWorkHbox, WorkWebPageBox);
777     else
778       vbox.appendChild(FullWorkHbox);
779 
780     // Work section
781     cvData.cvJobDescription = com.gContactSync.ABOverlay.makeDescElement("JobDescription", "CardViewText");
782     cvData.cvCompanySymbol  = com.gContactSync.ABOverlay.makeDescElement("CompanySymbol",  "CardViewText");
783     vbox = document.getElementById("cvbWork");
784     // Add the job description after the job title
785     vbox.insertBefore(cvData.cvJobDescription, cvData.cvJobTitle.nextSibling);
786     // Add the company symbol after the company name
787     vbox.insertBefore(cvData.cvCompanySymbol, cvData.cvCompany.nextSibling);
788 
789     // Other section    
790     vbox = document.getElementById("cvbOther");
791     var otherHbox = document.createElement("hbox");
792     var otherVbox = document.createElement("vbox");
793     otherVbox.setAttribute("flex", "1");
794     // Relation fields)
795     for (var i = 0; i < 4; i++) {
796       cvData["cvRelation" + i] = com.gContactSync.ABOverlay.makeDescElement("Relation" + i, "CardViewText");
797       otherVbox.appendChild(cvData["cvRelation" + i]);
798     }
799 
800     // Other Number and HomeFaxNumber
801     cvData.cvOtherNumber = com.gContactSync.ABOverlay.makeDescElement("OtherNumber", "CardViewText");
802     cvData.cvHomeFaxNumber = com.gContactSync.ABOverlay.makeDescElement("HomeFaxNumber", "CardViewText");
803     vbox = document.getElementById("cvbPhone");
804     vbox.appendChild(cvData.cvHomeFaxNumber);
805     vbox.appendChild(cvData.cvOtherNumber);
806     
807     // Add a description where the mailing lists the selected contact is in
808     // will appear, if possible
809     if (GetSelectedDirectory !== undefined) {
810       var desc = document.createElement("description");
811       cvData.cvLists = document.createTextNode("1");
812       desc.style.paddingTop = "6px";
813       desc.appendChild(cvData.cvLists);
814       vbox = document.getElementById("CardViewInnerBox");
815       vbox.insertBefore(desc, document.getElementById("CardTitle").nextSibling);
816     } else {
817       cvData.cvLists = null;
818     }
819   },
820   /**
821    * Makes and returns a <description> element of the given class and with an ID
822    * of aName with a prefix of "cv"
823    * @param aName  {string} The ID of the element that will be prefixed with a
824    *                        "cv"
825    * @param aClass {string} The class of the element.
826    * @returns {XML} A new <description> element.
827    */
828   makeDescElement: function ABOverlay_makeDescElement(aName, aClass) {
829     var elem = document.createElement("description");
830     elem.setAttribute("class", aClass);
831     elem.setAttribute("id", "cv" + aName);
832     return elem;
833   },
834   /**
835    * Adds a 'Reset' menuitem to the Address Book contaxt menu for the list on
836    * the left side of the Address Book window.
837    */
838   addResetContext: function ABOverlay_addResetContext() {
839     var replaceFrom = document.createElement("menuitem"),
840         replaceTo   = document.createElement("menuitem");
841 
842     replaceFrom.id  = "dirTreeContext-replaceFrom";
843     replaceTo.id    = "dirTreeContext-replaceTo";
844     replaceFrom.setAttribute("label",     com.gContactSync.StringBundle.getStr("reset"));
845     replaceFrom.setAttribute("accesskey", com.gContactSync.StringBundle.getStr("resetKey"));
846     replaceFrom.setAttribute("oncommand", "com.gContactSync.ABOverlay.resetSelectedAB()");
847     replaceTo.setAttribute("label",       com.gContactSync.StringBundle.getStr("replaceTo"));
848     replaceTo.setAttribute("accesskey",   com.gContactSync.StringBundle.getStr("replaceToKey"));
849     replaceTo.setAttribute("oncommand",   "com.gContactSync.ABOverlay.replaceToSelectedAB()");
850     document.getElementById("dirTreeContext").appendChild(replaceFrom);
851     document.getElementById("dirTreeContext").appendChild(replaceTo);
852   },
853   /**
854    * Resets the currently selected address book after showing a confirmation
855    * dialog.
856    */
857   resetSelectedAB: function ABOverlay_resetSelectedAB() {
858     var dirTree  = document.getElementById("dirTree");
859     var selected = dirTree.builderView.getResourceAtIndex(dirTree.currentIndex);
860     var ab = new com.gContactSync.GAbManager.getGAbByURI(selected.Value);
861     // make sure the AB was not already reset
862     if (ab.mPrefs.reset === "true") {
863       com.gContactSync.alert(com.gContactSync.StringBundle.getStr("alreadyReset"));
864       return;
865     }
866     // show a confirm dialog to make sure the user knows what's about to happen
867     if (com.gContactSync.confirm(com.gContactSync.StringBundle.getStr("resetConfirm2"))) {
868       if (ab.reset()) {
869         var restartStr = com.gContactSync.StringBundle.getStr("pleaseRestart");
870         com.gContactSync.Preferences.setSyncPref("needRestart", true);
871         this.setStatusBarText(restartStr);
872         com.gContactSync.alertError(restartStr);
873       }
874     }
875   },
876   /**
877    * Updates the LastModifiedDate of all contacts in the selected AB.
878    */
879   replaceToSelectedAB: function ABOverlay_replaceToSelectedAB() {
880     var dirTree  = document.getElementById("dirTree");
881     var selected = dirTree.builderView.getResourceAtIndex(dirTree.currentIndex);
882     var ab = new com.gContactSync.GAbManager.getGAbByURI(selected.Value);
883     ab.replaceToServer();
884     com.gContactSync.alert(com.gContactSync.StringBundle.getStr("replaceToComplete"));
885   },
886   /**
887    * Fixes the description style as set (accidentally?) by the
888    * Duplicate Contacts Manager extension in duplicateContactsManager.css
889    * It appears that the new description style was applied to addressbook.xul
890    * on accident when it was meant only for duplicateEntriesWindow.xul
891    *
892    * @returns {boolean} true if the description style was removed.
893    */
894   fixDescriptionStyle: function ABOverlay_fixDescriptionStyle() {
895     // Make sure this is addressbook.xul only
896     if (document.location && document.location.href.indexOf("/addressbook.xul") != -1) {
897       var ss = document.styleSheets;
898       var s;
899       // Iterate through each stylesheet and look for one from
900       // Duplicate Contacts Manager
901       for (var i = 0; i < ss.length; i++) {
902         // If this is duplicateContactsManager.css then remove the
903         // description style
904         if (ss[i] && ss[i].href == "chrome://duplicatecontactsmanager/skin/duplicateContactsManager.css") {
905           var rules = ss[i].cssRules;
906           for (var j = 0; j < rules.length; j++) {
907             if (rules[j].selectorText == "description") {
908               ss[i].deleteRule(j);
909               return true;
910             }
911           }
912         }
913       }
914     }
915     return false;
916   }
917 }
918