--- a/mail/components/addrbook/content/abCommon.js
+++ b/mail/components/addrbook/content/abCommon.js
@@ -20,16 +20,17 @@
#
# Original Author:
# Paul Hangas <hangas@netscape.com>
#
# Contributor(s):
# Seth Spitzer <sspitzer@netscape.com>
# Mark Banner <mark@standard8.demon.co.uk>
# Simon Wilkinson <simon@sxw.org.uk>
+# Mike Conley <mconley@mozilla.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
@@ -37,17 +38,17 @@
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
Components.utils.import("resource:///modules/mailServices.js");
-var dirTree = 0;
+var gDirTree = 0;
var abList = 0;
var gAbResultsTree = null;
var gAbView = null;
var gAddressBookBundle;
var gPrefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
var gHeaderParser = Components.classes["@mozilla.org/messenger/headerparser;1"].getService(Components.interfaces.nsIMsgHeaderParser);
@@ -80,17 +81,17 @@ var DirPaneController =
},
isCommandEnabled: function(command)
{
var selectedDir;
switch (command) {
case "cmd_selectAll":
- // the dirTree pane
+ // the gDirTree pane
// only handles single selection
// so we forward select all to the results pane
// but if there is no gAbView
// don't bother sending to the results pane
return (gAbView != null);
case "cmd_delete":
case "button_delete":
var selectedDir = GetSelectedDirectory();
@@ -151,17 +152,17 @@ var DirPaneController =
switch (command) {
case "cmd_printcard":
case "cmd_printcardpreview":
case "cmd_selectAll":
SendCommandToResultsPane(command);
break;
case "cmd_delete":
case "button_delete":
- if (dirTree)
+ if (gDirTree)
AbDeleteSelectedDirectory();
break;
case "button_edit":
AbEditSelectedDirectory();
break;
case "cmd_newlist":
AbNewList();
break;
@@ -198,17 +199,17 @@ function AbNewAddressBook()
window.openDialog("chrome://messenger/content/addressbook/abAddressBookNameDialog.xul",
"",
"chrome,modal,resizable=no,centerscreen",
null);
}
function AbEditSelectedDirectory()
{
- if (dirTree.view.selection.count == 1) {
+ if (gDirTree.view.selection.count == 1) {
var selecteduri = GetSelectedDirectory();
var directory = GetDirectoryFromURI(selecteduri);
if (directory.isMailList) {
var dirUri = GetParentDirectoryFromMailingListURI(selecteduri);
goEditListDialog(null, selecteduri);
}
else {
window.openDialog(directory.propertiesChromeURI,
@@ -283,17 +284,17 @@ function GetParentRow(aTree, aRow)
return row;
parentLevel = aTree.view.getLevel(row);
}
return row;
}
function InitCommonJS()
{
- dirTree = document.getElementById("dirTree");
+ gDirTree = document.getElementById("dirTree");
abList = document.getElementById("addressbookList");
gAddressBookBundle = document.getElementById("bundle_addressBook");
}
function AbDelete()
{
var types = GetSelectedCardTypes();
if (types == kNothingSelected)
@@ -391,19 +392,18 @@ function goToggleSplitter( id, elementID
// Generate a list of cards from the selected mailing list
// and get a comma separated list of card addresses. If the
// item selected in the directory pane is not a mailing list,
// an empty string is returned.
function GetSelectedAddressesFromDirTree()
{
var addresses = "";
- if (dirTree.currentIndex >= 0) {
- var selectedResource = dirTree.builderView.getResourceAtIndex(dirTree.currentIndex);
- var directory = GetDirectoryFromURI(selectedResource.Value);
+ if (gDirTree.currentIndex >= 0) {
+ var directory = gDirectoryTreeView.getDirectoryAtIndex(gDirTree.currentIndex);
if (directory.isMailList) {
var listCardsCount = directory.addressLists.length;
var cards = new Array(listCardsCount);
for (var i = 0; i < listCardsCount; ++i)
cards[i] = directory.addressLists
.queryElementAt(i, Components.interfaces.nsIAbCard);
addresses = GetAddressesForCards(cards);
}
@@ -430,17 +430,17 @@ function GetAddressesForCards(cards)
if (generatedAddress)
addresses += "," + generatedAddress;
}
return addresses;
}
function SelectFirstAddressBook()
{
- dirTree.view.selection.select(0);
+ gDirTree.view.selection.select(0);
ChangeDirectoryByURI(GetSelectedDirectory());
gAbResultsTree.focus();
}
function DirPaneClick(event)
{
// we only care about left button events
@@ -455,32 +455,32 @@ function DirPaneClick(event)
}
function DirPaneDoubleClick(event)
{
// we only care about left button events
if (event.button != 0)
return;
- var row = dirTree.treeBoxObject.getRowAt(event.clientX, event.clientY);
- if (row == -1 || row > dirTree.view.rowCount-1) {
+ var row = gDirTree.treeBoxObject.getRowAt(event.clientX, event.clientY);
+ if (row == -1 || row > gDirTree.view.rowCount-1) {
// double clicking on a non valid row should not open the dir properties dialog
return;
}
- if (dirTree && dirTree.view.selection && dirTree.view.selection.count == 1)
+ if (gDirTree && gDirTree.view.selection && gDirTree.view.selection.count == 1)
AbEditSelectedDirectory();
}
function DirPaneSelectionChange()
{
// clear out the search box when changing folders...
onAbClearSearch();
- if (dirTree && dirTree.view.selection && dirTree.view.selection.count == 1) {
- gPreviousDirTreeIndex = dirTree.currentIndex;
+ if (gDirTree && gDirTree.view.selection && gDirTree.view.selection.count == 1) {
+ gPreviousDirTreeIndex = gDirTree.currentIndex;
ChangeDirectoryByURI(GetSelectedDirectory());
}
goUpdateCommand('cmd_newlist');
}
function ChangeDirectoryByURI(uri)
{
if (!uri)
@@ -602,28 +602,27 @@ function GetParentDirectoryFromMailingLi
}
return null;
}
function DirPaneHasFocus()
{
// returns true if diectory pane has the focus. Returns false, otherwise.
- return (top.document.commandDispatcher.focusedElement == dirTree)
+ return (top.document.commandDispatcher.focusedElement == gDirTree)
}
function GetSelectedDirectory()
{
if (abList)
return abList.value;
else {
- if (dirTree.currentIndex < 0)
+ if (gDirTree.currentIndex < 0)
return null;
- var selected = dirTree.builderView.getResourceAtIndex(dirTree.currentIndex)
- return selected.Value;
+ return gDirectoryTreeView.getDirectoryAtIndex(gDirTree.currentIndex).URI;
}
}
function onAbClearSearch()
{
gSearchInput.value = "";
onEnterInSearchBar();
}
new file mode 100644
--- /dev/null
+++ b/mail/components/addrbook/content/abTrees.js
@@ -0,0 +1,321 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mail Addressbook code.
+ *
+ * The Initial Developer of the Original Code is
+ * Joey Minta <jminta@gmail.com>
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/**
+ * This file contains our implementation for various addressbook trees. It
+ * depends on jsTreeView.js being loaded before this script is loaded.
+ */
+
+Components.utils.import("resource:///modules/mailServices.js");
+
+/**
+ * Each abDirTreeItem corresponds to one row in the tree view.
+ */
+function abDirTreeItem(aDirectory) {
+ this._directory = aDirectory;
+}
+
+abDirTreeItem.prototype = {
+ getText: function atv_getText() {
+ return this._directory.dirName;
+ },
+
+ get id() {
+ return this._directory.URI;
+ },
+
+ _open: false,
+ get open() {
+ return this._open;
+ },
+
+ _level: 0,
+ get level() {
+ return this._level;
+ },
+
+ _children: null,
+ get children() {
+ if (!this._children) {
+ this._children = [];
+ const Ci = Components.interfaces;
+ var myEnum = this._directory.childNodes;
+ while (myEnum.hasMoreElements()) {
+ var abItem = new abDirTreeItem(myEnum.getNext()
+ .QueryInterface(Ci.nsIAbDirectory));
+ this._children.push(abItem);
+ this._children[this._children.length - 1]._level = this._level + 1;
+ this._children[this._children.length - 1]._parent = this;
+ }
+
+ // We sort children based on their names
+ function nameSort(a, b) {
+ return a._directory.dirName.localeCompare(b._directory.dirName);
+ }
+ this._children.sort(nameSort);
+ }
+ return this._children;
+ },
+
+ getProperties: function atv_getProps(aProps) {
+ var atomSvc = Components.classes["@mozilla.org/atom-service;1"]
+ .getService(Components.interfaces.nsIAtomService);
+ if (this._directory.isMailList)
+ aProps.AppendElement(atomSvc.getAtom("IsMailList-true"));
+ if (this._directory.isRemote)
+ aProps.AppendElement(atomSvc.getAtom("IsRemote-true"));
+ if (this._directory.isSecure)
+ aProps.AppendElement(atomSvc.getAtom("IsSecure-true"));
+ }
+};
+
+/**
+ * Our actual implementation of nsITreeView.
+ */
+function directoryTreeView() {}
+directoryTreeView.prototype = {
+ __proto__: new PROTO_TREE_VIEW(),
+
+ init: function dtv_init(aTree, aJSONFile) {
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+
+ if (aJSONFile) {
+ // Parse our persistent-open-state json file
+ let file = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
+ file.append(aJSONFile);
+
+ if (file.exists()) {
+ let data = "";
+ let fstream = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ let sstream = Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ fstream.init(file, -1, 0, 0);
+ sstream.init(fstream);
+
+ while (sstream.available())
+ data += sstream.read(4096);
+
+ sstream.close();
+ fstream.close();
+ let JSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
+ this._persistOpenMap = JSON.decode(data);
+ }
+ }
+
+ this._rebuild();
+ aTree.view = this;
+ },
+
+ shutdown: function dtv_shutdown(aJSONFile) {
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+
+ // Write out the persistOpenMap to our JSON file
+ if (aJSONFile) {
+ // Write out our json file...
+ let JSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
+ let data = JSON.encode(this._persistOpenMap);
+ let file = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
+ file.append(aJSONFile);
+ let foStream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+
+ foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0);
+ foStream.write(data, data.length);
+ foStream.close();
+ }
+ },
+
+ // Override the dnd methods for those functions in abDragDrop.js
+ canDrop: function dtv_canDrop(aIndex, aOrientation) {
+ return abDirTreeObserver.canDrop(aIndex, aOrientation);
+ },
+
+ drop: function dtv_drop(aRow, aOrientation) {
+ abDirTreeObserver.onDrop(aRow, aOrientation);
+ },
+
+ getDirectoryAtIndex: function dtv_getDirForIndex(aIndex) {
+ return this._rowMap[aIndex]._directory;
+ },
+
+ // Override jsTreeView's isContainer, since we want to be able
+ // to react to drag-drop events for all items in the directory
+ // tree.
+ isContainer: function dtv_isContainer(aIndex) {
+ return true;
+ },
+
+ /**
+ * NOTE: This function will result in indeterminate rows being selected.
+ * Callers should take care to re-select a desired row after calling
+ * this function.
+ */
+ _rebuild: function dtv__rebuild() {
+ var oldCount = this._rowMap.length;
+ this._rowMap = [];
+
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+
+ var dirEnum = MailServices.ab.directories;
+
+ while (dirEnum.hasMoreElements()) {
+ this._rowMap.push(new abDirTreeItem(dirEnum.getNext().QueryInterface(Ci.nsIAbDirectory)));
+ }
+
+ // Sort our addressbooks now
+
+ const AB_ORDER = ["pab", "mork", "ldap", "mapi+other", "cab"];
+
+ function getDirectoryValue(aDir, aKey) {
+ if (aKey == "ab_type") {
+ if (aDir._directory.URI == kPersonalAddressbookURI)
+ return "pab";
+ if (aDir._directory.URI == kCollectedAddressbookURI)
+ return "cab";
+ if (aDir._directory instanceof Ci.nsIAbMDBDirectory)
+ return "mork";
+ if (aDir._directory instanceof Ci.nsIAbLDAPDirectory)
+ return "ldap";
+ return "mapi+other";
+ } else if (aKey == "ab_name") {
+ return aDir._directory.dirName;
+ }
+ }
+
+ function abNameCompare(a, b) {
+ return a.localeCompare(b);
+ }
+
+ function abTypeCompare(a, b) {
+ return (AB_ORDER.indexOf(a) - AB_ORDER.indexOf(b));
+ }
+
+ const SORT_PRIORITY = ["ab_type", "ab_name"];
+ const SORT_FUNCS = [abTypeCompare, abNameCompare];
+
+ function abSort(a, b) {
+ for (let i = 0; i < SORT_FUNCS.length; i++) {
+ let sortBy = SORT_PRIORITY[i];
+ let aValue = getDirectoryValue(a, sortBy);
+ let bValue = getDirectoryValue(b, sortBy);
+
+ if (!aValue && !bValue)
+ return 0;
+ if (!aValue)
+ return -1;
+ if (!bValue)
+ return 1;
+ if (aValue != bValue) {
+ let result = SORT_FUNCS[i](aValue, bValue);
+
+ if (result != 0)
+ return result;
+ }
+ }
+ return 0;
+ }
+
+ this._rowMap.sort(abSort);
+
+ if (this._tree)
+ this._tree.rowCountChanged(0, this._rowMap.length - oldCount);
+
+ this._restoreOpenStates();
+ },
+
+ // nsIAbListener interfaces
+ onItemAdded: function dtv_onItemAdded(aParent, aItem) {
+ if (!(aItem instanceof Components.interfaces.nsIAbDirectory))
+ return;
+ //xxx we can optimize this later
+ this._rebuild();
+
+ if (!this._tree)
+ return;
+
+ // Now select this new item
+ for (var [i, row] in Iterator(this._rowMap)) {
+ if (row.id == aItem.URI) {
+ this.selection.select(i);
+ break;
+ }
+ }
+ },
+
+ onItemRemoved: function dtv_onItemRemoved(aParent, aItem) {
+ if (!(aItem instanceof Components.interfaces.nsIAbDirectory))
+ return;
+ //xxx we can optimize this later
+ this._rebuild();
+
+ if (!this._tree)
+ return;
+
+ // If we're deleting a top-level address-book, just select the first book
+ if (aParent.URI == "moz-abdirectory://") {
+ this.selection.select(0);
+ return;
+ }
+
+ // Now select this parent item
+ for (var [i, row] in Iterator(this._rowMap)) {
+ if (row.id == aParent.URI) {
+ this.selection.select(i);
+ break;
+ }
+ }
+ },
+
+ onItemPropertyChanged: function dtv_onItemProp(aItem, aProp, aOld, aNew) {
+ if (!(aItem instanceof Components.interfaces.nsIAbDirectory))
+ return;
+
+ for (var i in this._rowMap) {
+ if (this._rowMap[i]._directory == aItem) {
+ this._tree.invalidateRow(i);
+ break;
+ }
+ }
+ }
+};
+
+var gDirectoryTreeView = new directoryTreeView();
--- a/mail/components/addrbook/content/addressbook.js
+++ b/mail/components/addrbook/content/addressbook.js
@@ -20,16 +20,17 @@
# the Initial Developer. All Rights Reserved.
#
# Original Author:
# Paul Hangas <hangas@netscape.com>
#
# Contributor(s):
# Seth Spitzer <sspitzer@netscape.com>
# Mark Banner <mark@standard8.demon.co.uk>
+# Joey Minta <jminta@gmail.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
@@ -37,26 +38,27 @@
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
// Ensure the activity modules are loaded for this window.
Components.utils.import("resource:///modules/activity/activityModules.js");
+Components.utils.import("resource:///modules/mailServices.js");
const nsIAbListener = Components.interfaces.nsIAbListener;
const kPrefMailAddrBookLastNameFirst = "mail.addr_book.lastnamefirst";
+const kPersistCollapseMapStorage = "directoryTree.json";
var cvPrefs = 0;
var gSearchTimer = null;
var gStatusText = null;
var gQueryURIFormat = null;
var gSearchInput;
-var gDirTree;
var gCardViewBox;
var gCardViewBoxEmail1;
var gPreviousDirTreeIndex = -1;
var msgWindow = Components.classes["@mozilla.org/messenger/msgwindow;1"]
.createInstance(Components.interfaces.nsIMsgWindow);
// Constants that correspond to choices
@@ -81,58 +83,61 @@ var gAddressBookAbListener = {
// option is to select the first.
if (gPreviousDirTreeIndex == -1) {
SelectFirstAddressBook();
}
else {
// Don't reselect if we already have a valid selection, this may be
// the case if items are being removed via other methods, e.g. sidebar,
// LDAP preference pane etc.
- if (dirTree.currentIndex == -1) {
+ if (gDirTree.currentIndex == -1) {
var directory = item.QueryInterface(Components.interfaces.nsIAbDirectory);
// If we are a mail list, move the selection up the list before
// trying to find the parent. This way we'll end up selecting the
// parent address book when we remove a mailing list.
//
// For simple address books we don't need to move up the list, as
// we want to select the next one upon removal.
if (directory.isMailList && gPreviousDirTreeIndex > 0)
--gPreviousDirTreeIndex;
// Now get the parent of the row.
- var newRow = dirTree.view.getParentIndex(gPreviousDirTreeIndex);
+ var newRow = gDirTree.view.getParentIndex(gPreviousDirTreeIndex);
// if we have no parent (i.e. we are an address book), use the
// previous index.
if (newRow == -1)
newRow = gPreviousDirTreeIndex;
// Fall back to the first adddress book if we're not in a valid range
- if (newRow >= dirTree.view.rowCount)
+ if (newRow >= gDirTree.view.rowCount)
newRow = 0;
// Now select the new item.
- dirTree.view.selection.select(newRow);
+ gDirTree.view.selection.select(newRow);
}
}
}
catch (ex) {
}
},
onItemPropertyChanged: function(item, property, oldValue, newValue) {
// will not be called
}
};
function OnUnloadAddressBook()
{
- Components.classes["@mozilla.org/abmanager;1"]
- .getService(Components.interfaces.nsIAbManager)
- .removeAddressBookListener(gAddressBookAbListener);
+ MailServices.ab.removeAddressBookListener(gAddressBookAbListener);
+ MailServices.ab.removeAddressBookListener(gDirectoryTreeView);
+
+ // Shutdown the tree view - this will also save the open/collapsed
+ // state of the tree view to a JSON file.
+ gDirectoryTreeView.shutdown(kPersistCollapseMapStorage);
Components.classes["@mozilla.org/messenger/services/session;1"]
.getService(Components.interfaces.nsIMsgMailSession)
.RemoveMsgWindow(msgWindow);
CloseAbView();
}
@@ -183,37 +188,38 @@ function delayedOnLoadAddressBook()
InitCommonJS();
GetCurrentPrefs();
// FIX ME - later we will be able to use onload from the overlay
OnLoadCardView();
- //workaround - add setTimeout to make sure dynamic overlays get loaded first
- setTimeout('OnLoadDirTree()', 0);
+ // Initialize the Address Book tree view
+ gDirectoryTreeView.init(gDirTree,
+ kPersistCollapseMapStorage);
+
+ SelectFirstAddressBook();
// if the pref is locked disable the menuitem New->LDAP directory
if (gPrefs.prefIsLocked("ldap_2.disable_button_add"))
document.getElementById("addLDAP").setAttribute("disabled", "true");
// Add a listener, so we can switch directories if the current directory is
// deleted. This listener cares when a directory (= address book), or a
// directory item is/are removed. In the case of directory items, we are
// only really interested in mailing list changes and not cards but we have
// to have both.
- Components.classes["@mozilla.org/abmanager;1"]
- .getService(Components.interfaces.nsIAbManager)
- .addAddressBookListener(gAddressBookAbListener,
- nsIAbListener.directoryRemoved |
- nsIAbListener.directoryItemRemoved);
+ MailServices.ab.addAddressBookListener(gAddressBookAbListener,
+ nsIAbListener.directoryRemoved |
+ nsIAbListener.directoryItemRemoved);
+ MailServices.ab.addAddressBookListener(gDirectoryTreeView, nsIAbListener.all);
- var dirTree = GetDirTree();
- dirTree.addEventListener("click",DirPaneClick,true);
- dirTree.controllers.appendController(DirPaneController);
+
+ gDirTree.controllers.appendController(DirPaneController);
// initialize the customizeDone method on the customizeable toolbar
var toolbox = document.getElementById("ab-toolbox");
toolbox.customizeDone = function(aEvent) { MailToolboxCustomizeDone(aEvent, "CustomizeABToolbar"); };
var toolbarset = document.getElementById('customToolbars');
toolbox.toolbarset = toolbarset;
@@ -223,22 +229,16 @@ function delayedOnLoadAddressBook()
.QueryInterface(Components.interfaces.nsIDocShell)
.useErrorPages = false;
Components.classes["@mozilla.org/messenger/services/session;1"]
.getService(Components.interfaces.nsIMsgMailSession)
.AddMsgWindow(msgWindow);
}
-function OnLoadDirTree() {
- var treeBuilder = dirTree.builder.QueryInterface(Components.interfaces.nsIXULTreeBuilder);
- treeBuilder.addObserver(abDirTreeObserver);
-
- SelectFirstAddressBook();
-}
function GetCurrentPrefs()
{
// prefs
if ( cvPrefs == 0 )
cvPrefs = new Object;
cvPrefs.prefs = gPrefs;
@@ -430,19 +430,17 @@ function AbPrintPreviewAddressBook()
function AbExport()
{
try {
var selectedABURI = GetSelectedDirectory();
if (!selectedABURI) return;
var directory = GetDirectoryFromURI(selectedABURI);
- Components.classes["@mozilla.org/abmanager;1"]
- .getService(Components.interfaces.nsIAbManager)
- .exportAddressBook(window, directory);
+ MailServices.ab.exportAddressBook(window, directory);
}
catch (ex) {
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
if (promptService) {
var message;
switch (ex.result) {
case Components.results.NS_ERROR_FILE_ACCESS_DENIED:
@@ -735,19 +733,16 @@ function AbOSXAddressBookExists()
catch (e) { }
// Address book exists if the uri is correct and the position is not zero.
return uriPresent && position != 0;
}
function AbShowHideOSXAddressBook()
{
- var abMgr = Components.classes["@mozilla.org/abmanager;1"]
- .getService(Components.interfaces.nsIAbManager);
-
if (AbOSXAddressBookExists())
- abMgr.deleteAddressBook(kOSXDirectoryURI);
+ MailServices.ab.deleteAddressBook(kOSXDirectoryURI);
else {
- abMgr.newAddressBook(
+ MailServices.ab.newAddressBook(
gAddressBookBundle.getString(kOSXPrefBase + ".description"),
kOSXDirectoryURI, 3, kOSXPrefBase);
}
}
--- a/mail/components/addrbook/content/addressbook.xul
+++ b/mail/components/addrbook/content/addressbook.xul
@@ -66,16 +66,18 @@
onunload="OnUnloadAddressBook()">
<stringbundleset id="stringbundleset">
<stringbundle id="bundle_addressBook" src="chrome://messenger/locale/addressbook/addressBook.properties"/>
<stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
<stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
</stringbundleset>
+<script type="application/javascript" src="chrome://messenger/content/jsTreeView.js"/>
+<script type="application/javascript" src="chrome://messenger/content/addressbook/abTrees.js"/>
<script type="application/javascript" src="chrome://messenger/content/accountUtils.js"/>
<script type="application/javascript" src="chrome://messenger/content/widgetglue.js"/>
<script type="application/javascript" src="chrome://messenger/content/mailCore.js"/>
<script type="application/javascript" src="chrome://messenger/content/addressbook/addressbook.js"/>
<script type="application/javascript" src="chrome://messenger/content/addressbook/abCommon.js"/>
<script type="application/javascript" src="chrome://communicator/content/contentAreaClick.js"/>
<script type="application/javascript" src="chrome://global/content/printUtils.js"/>
<script type="application/javascript" src="chrome://messenger/content/msgPrintEngine.js"/>
@@ -605,66 +607,28 @@
<hbox id="abContent" flex="1">
<vbox id="dirTreeBox" persist="width collapsed">
<sidebarheader id="abDirTreeHeader" align="center">
<label id="abDirTreeHeader-title" value="&dirTreeHeader.label;" control="dirTree"/>
</sidebarheader>
<!-- FIX ME - remove document.commandDispatcher.updateCommands() when tree selection calls this automatically -->
<tree id="dirTree" class="abDirectory plain" seltype="single" minwidth="150" flex="1" persist="width"
- datasources="rdf:addressdirectory" ref="moz-abdirectory://"
- flags="dont-build-content"
hidecolumnpicker="true"
context="dirTreeContext"
onselect="DirPaneSelectionChange(); document.commandDispatcher.updateCommands('addrbook-select');"
ondblclick="DirPaneDoubleClick(event);"
+ onclick="DirPaneClick(event);"
onblur="goOnEvent(this,'blur')">
-
+
<treecols>
- <treecol id="DirCol" flex="1" primary="true"
- crop="center" persist="width" ignoreincolumnpicker="true" hideheader="true"
- sort="?DirTreeNameSort" sortActive="true" sortDirection="ascending"/>
+ <treecol id="DirCol" flex="1" primary="true" hideheader="true"
+ crop="center" persist="width" ignoreincolumnpicker="true"/>
</treecols>
-
- <template>
- <rule>
- <conditions>
- <content uri="?container"/>
- <member container="?container" child="?member"/>
- </conditions>
-
- <bindings>
- <binding subject="?member"
- predicate="http://home.netscape.com/NC-rdf#DirName"
- object="?DirName"/>
- <binding subject="?member"
- predicate="http://home.netscape.com/NC-rdf#DirTreeNameSort"
- object="?DirTreeNameSort"/>
- <binding subject="?member"
- predicate="http://home.netscape.com/NC-rdf#IsMailList"
- object="?IsMailList"/>
- <binding subject="?member"
- predicate="http://home.netscape.com/NC-rdf#IsRemote"
- object="?IsRemote"/>
- <binding subject="?member"
- predicate="http://home.netscape.com/NC-rdf#IsSecure"
- object="?IsSecure"/>
- </bindings>
-
- <action>
- <treechildren>
- <treeitem uri="?member" persist="sortDirection sortColumn open">
- <treerow>
- <treecell label="?DirName" properties="IsMailList-?IsMailList IsRemote-?IsRemote IsSecure-?IsSecure"/>
- </treerow>
- </treeitem>
- </treechildren>
- </action>
- </rule>
- </template>
+ <treechildren/>
</tree>
</vbox>
<splitter id="dirTree-splitter" collapse="before" persist="state"/>
<vbox flex="1" minwidth="100">
<!-- results pane -->
--- a/mail/components/addrbook/jar.mn
+++ b/mail/components/addrbook/jar.mn
@@ -4,8 +4,9 @@ messenger.jar:
* content/messenger/addressbook/addressbook.xul (content/addressbook.xul)
* content/messenger/addressbook/abCommon.js (content/abCommon.js)
* content/messenger/addressbook/abCardOverlay.js (content/abCardOverlay.js)
content/messenger/addressbook/abCardOverlay.xul (content/abCardOverlay.xul)
* content/messenger/addressbook/abEditListDialog.xul (content/abEditListDialog.xul)
* content/messenger/addressbook/abMailListDialog.xul (content/abMailListDialog.xul)
* content/messenger/addressbook/abContactsPanel.xul (content/abContactsPanel.xul)
* content/messenger/addressbook/abContactsPanel.js (content/abContactsPanel.js)
+ content/messenger/addressbook/abTrees.js (content/abTrees.js)
new file mode 100644
--- /dev/null
+++ b/mail/test/mozmill/addrbook/test-address-book.js
@@ -0,0 +1,156 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Thunderbird Mail Client.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Mike Conley <mconley@mozillamessaging.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Tests for the address book.
+ */
+
+var MODULE_NAME = 'test-address-book';
+
+var RELATIVE_ROOT = '../shared-modules';
+var MODULE_REQUIRES = ['address-book-helpers', 'folder-display-helpers'];
+
+let abController = null;
+
+var addrBook1, addrBook2, addrBook3, addrBook4;
+var mListA, mListB, mListC, mListD, mListE;
+
+function setupModule(module)
+{
+ let fdh = collector.getModule('folder-display-helpers');
+ fdh.installInto(module);
+
+ let abh = collector.getModule('address-book-helpers');
+ abh.installInto(module);
+
+ // Open the address book main window
+ abController = open_address_book_window();
+
+ // Let's add some new address books. I'll add them
+ // out of order to properly test the alphabetical
+ // ordering of the address books.
+ ldapBook = create_ldap_address_book("LDAP Book");
+ addrBook3 = create_mork_address_book("AB 3");
+ addrBook1 = create_mork_address_book("AB 1");
+ addrBook4 = create_mork_address_book("AB 4");
+ addrBook2 = create_mork_address_book("AB 2");
+
+ mListA = create_mailing_list("ML A");
+ addrBook1.addMailList(mListA);
+
+ mListB = create_mailing_list("ML B");
+ addrBook2.addMailList(mListB);
+
+ mListC = create_mailing_list("ML C");
+ addrBook3.addMailList(mListC);
+
+ mListD = create_mailing_list("ML D");
+ addrBook3.addMailList(mListD);
+}
+
+/* Test that the address book manager automatically sorts
+ * address books.
+ *
+ * Currently, we sort address books as follows:
+ * 1. Personal Address Book
+ * 2. Mork Address Books
+ * 3. LDAP / Other Address Books
+ * 4. Collected Address Book
+ *
+ * With the Personal and Collapsed address books existing
+ * automatically, our address books *should* be in this order:
+ *
+ * Personal Address Book
+ * AB 1
+ * ML A
+ * AB 2
+ * ML B
+ * AB 3
+ * ML C
+ * ML D
+ * AB 4
+ * LDAP Book
+ * Collected Address Book
+ **/
+function test_order_of_address_books()
+{
+ const EXPECTED_AB_ORDER = ["Personal Address Book", "AB 1", "AB 2",
+ "AB 3", "AB 4", "LDAP Book",
+ "Collected Addresses"];
+
+ for (let i = 0; i < EXPECTED_AB_ORDER.length; i++)
+ {
+ let abName = get_name_of_address_book_element_at(i);
+ assert_equals(abName, EXPECTED_AB_ORDER[i],
+ "The address books are out of order.");
+ }
+}
+
+/* Test that the expanded and collapsed states of address books
+ * in the tree persist state when closing and re-opening the
+ * address book manager
+ */
+function test_persist_collapsed_and_expanded_states()
+{
+ // Set the state of address books 1 and 3 to expanded
+ set_address_books_expanded([addrBook1, addrBook3]);
+
+ // Set address book 2 to be collapsed
+ set_address_book_collapsed(addrBook2);
+
+ // Now close and re-open the address book
+ abController.window.close();
+ abController = open_address_book_window();
+
+ assert_true(is_address_book_collapsed(addrBook2));
+ assert_true(!is_address_book_collapsed(addrBook1));
+ assert_true(!is_address_book_collapsed(addrBook3));
+
+ // Now set the state of address books 1 and 3 to collapsed
+ // and make sure 2 is expanded
+ set_address_books_collapsed([addrBook1, addrBook3]);
+ set_address_book_expanded(addrBook2);
+
+ // Now close and re-open the address book
+ abController.window.close();
+ abController = open_address_book_window();
+
+ assert_true(!is_address_book_collapsed(addrBook2));
+ assert_true(is_address_book_collapsed(addrBook1));
+ assert_true(is_address_book_collapsed(addrBook3));
+}
+
--- a/mail/test/mozmill/mozmilltests.list
+++ b/mail/test/mozmill/mozmilltests.list
@@ -1,9 +1,10 @@
account
+addrbook
composition
content-policy
content-tabs
cookies
folder-display
folder-pane
folder-tree-modes
folder-widget
--- a/mail/test/mozmill/shared-modules/test-address-book-helpers.js
+++ b/mail/test/mozmill/shared-modules/test-address-book-helpers.js
@@ -36,68 +36,300 @@
* ***** END LICENSE BLOCK ***** */
var Ci = Components.interfaces;
var Cc = Components.classes;
var Cu = Components.utils;
const MODULE_NAME = "address-book-helpers";
const RELATIVE_ROOT = "../shared-modules";
+const MODULE_REQUIRES = ['window-helpers'];
+
+const ABMDB_PREFIX = "moz-abmdbdirectory://";
+const ABLDAP_PREFIX = "moz-abldapdirectory://";
+
+Cu.import("resource:///modules/mailServices.js");
+Cu.import("resource:///modules/Services.jsm");
var collectedAddresses;
+var abController;
+
function setupModule() {
- let abManager = Cc["@mozilla.org/abmanager;1"].getService(Ci.nsIAbManager);
// Ensure all the directories are initialised.
- abManager.directories;
- collectedAddresses = abManager.getDirectory("moz-abmdbdirectory://history.mab");
+ MailServices.ab.directories;
+ collectedAddresses = MailServices.ab
+ .getDirectory("moz-abmdbdirectory://history.mab");
}
function installInto(module) {
setupModule();
// Now copy helper functions
module.ensure_card_exists = ensure_card_exists;
module.ensure_no_card_exists = ensure_no_card_exists;
+ module.open_address_book_window = open_address_book_window;
+ module.create_mork_address_book = create_mork_address_book;
+ module.create_ldap_address_book = create_ldap_address_book;
+ module.create_contact = create_contact;
+ module.create_mailing_list = create_mailing_list;
+ module.load_contacts_into_address_book = load_contacts_into_address_book;
+ module.load_contacts_into_mailing_list = load_contacts_into_mailing_list;
+ module.get_address_book_tree_view_index = get_address_book_tree_view_index;
+ module.set_address_books_collapsed = set_address_books_collapsed;
+ module.set_address_books_expanded = set_address_books_expanded;
+ // set_address_book_collapsed and set_address_book_expanded use
+ // the same code as set_address_books_expanded/collapsed, so I just
+ // alias them here.
+ module.set_address_book_collapsed = set_address_books_collapsed;
+ module.set_address_book_expanded = set_address_books_expanded;
+
+ module.is_address_book_collapsed = is_address_book_collapsed;
+ module.is_address_book_collapsible = is_address_book_collapsible;
+ module.get_name_of_address_book_element_at = get_name_of_address_book_element_at;
+ module.select_address_book = select_address_book;
}
/**
* Make sure that there is a card for this email address
* @param emailAddress the address that should have a card
* @param displayName the display name the card should have
* @param preferDisplayName |true| if the card display name should override the
* header display name
*/
-function ensure_card_exists(emailAddress, displayName, preferDisplayName) {
+function ensure_card_exists(emailAddress, displayName, preferDisplayName)
+{
ensure_no_card_exists(emailAddress);
- let card = Cc["@mozilla.org/addressbook/cardproperty;1"]
- .createInstance(Ci.nsIAbCard);
-
- card.primaryEmail = emailAddress;
- card.displayName = displayName;
- card.setProperty("PreferDisplayName", preferDisplayName ? true : false);
+ let card = create_card(emailAddress, displayName, preferDisplayName);
collectedAddresses.addCard(card);
}
/**
* Make sure that there is no card for this email address
* @param emailAddress the address that should have no cards
*/
function ensure_no_card_exists(emailAddress)
{
- var books = Cc["@mozilla.org/abmanager;1"].getService(Ci.nsIAbManager)
- .directories;
+ var books = MailServices.ab.directories;
while (books.hasMoreElements()) {
var ab = books.getNext().QueryInterface(Ci.nsIAbDirectory);
try {
var card = ab.cardForEmailAddress(emailAddress);
if (card) {
let cardArray = Cc["@mozilla.org/array;1"]
.createInstance(Ci.nsIMutableArray);
cardArray.appendElement(card, false);
ab.deleteCards(cardArray);
}
}
catch (ex) { }
}
}
+/**
+ * Opens the address book interface
+ * @returns a controller for the address book
+ */
+function open_address_book_window()
+{
+ abController = mozmill.getAddrbkController();
+ return abController;
+}
+
+/**
+ * Creates and returns a Mork-backed address book.
+ * @param aName the name for the address book
+ * @returns the nsIAbDirectory address book
+ */
+function create_mork_address_book(aName)
+{
+ let abPrefString = MailServices.ab.newAddressBook(aName, "", 2);
+ let abURI = Services.prefs.getCharPref(abPrefString + ".filename");
+ return MailServices.ab.getDirectory(ABMDB_PREFIX + abURI);
+}
+
+/**
+ * Creates and returns an LDAP-backed address book.
+ * This function will automatically fill in a dummy
+ * LDAP URI if no URI is supplied.
+ * @param aName the name for the address book
+ * @param aURI an optional URI for the address book
+ * @returns the nsIAbDirectory address book
+ */
+function create_ldap_address_book(aName, aURI)
+{
+ if (!aURI)
+ aURI = "ldap://dummyldap/??sub?(objectclass=*)";
+ let abPrefString = MailServices.ab.newAddressBook(aName, aURI, 0);
+ return MailServices.ab.getDirectory(ABLDAP_PREFIX + abPrefString);
+}
+
+/**
+ * Creates and returns an address book contact
+ * @param aEmailAddress the e-mail address for this contact
+ * @param aDisplayName the display name for the contact
+ * @param aPreferDisplayName set to true if the card display name should
+ * override the header display name
+ */
+function create_contact(aEmailAddress, aDisplayName, aPreferDisplayName)
+{
+ let card = Cc["@mozilla.org/addressbook/cardproperty;1"]
+ .createInstance(Ci.nsIAbCard);
+ card.primaryEmail = aEmailAddress;
+ card.displayName = aDisplayName;
+ card.setProperty("PreferDisplayName", aPreferDisplayName ? true : false);
+ return card;
+}
+
+/* Creates and returns a mailing list
+ * @param aMailingListName the display name for the new mailing list
+ */
+function create_mailing_list(aMailingListName)
+{
+ var mailList = Cc["@mozilla.org/addressbook/directoryproperty;1"]
+ .createInstance(Ci.nsIAbDirectory);
+ mailList.isMailList = true;
+ mailList.dirName = aMailingListName;
+ return mailList;
+}
+
+/* Given some address book, adds a collection of contacts to that
+ * address book.
+ * @param aAddressBook an address book to add the contacts to
+ * @param aContacts a collection of contacts, where each contact has
+ * members "email" and "displayName"
+ *
+ * Example:
+ * [{email: 'test@test.com', displayName: 'Sammy Jenkis'}]
+ */
+function load_contacts_into_address_book(aAddressBook, aContacts)
+{
+ for each (contact_info in aContacts) {
+ let contact = create_contact(contact_info.email,
+ contact_info.displayName, true);
+ aAddressBook.addCard(contact);
+ }
+}
+
+/* Given some mailing list, adds a collection of contacts to that
+ * mailing list.
+ * @param aMailingList a mailing list to add the contacts to
+ * @param aContacts a collection of contacts, where each contact has
+ * members "email" and "displayName"
+ *
+ * Example:
+ * [{email: 'test@test.com', displayName: 'Sammy Jenkis'}]
+ */
+function load_contacts_into_mailing_list(aMailingList, aContacts)
+{
+ for each (contact_info in aContacts) {
+ let contact = create_contact(contact_info.email,
+ contact_info.displayName, true);
+ aMailingList.addressLists.appendElement(contact, false);
+ }
+}
+
+/* Given some address book, return the row index for that address book
+ * in the tree view. Throws an error if it cannot find the address book.
+ * @param aAddrBook an address book to search for
+ * @return the row index for that address book
+ */
+function get_address_book_tree_view_index(aAddrBook)
+{
+ let addrBooks = abController.window.gDirectoryTreeView._rowMap;
+ for (let i = 0; i < addrBooks.length; i++) {
+ if (addrBooks[i]._directory == aAddrBook) {
+ return i;
+ }
+ }
+ throw Error("Could not find the index for the address book named "
+ + aAddrbook.dirName);
+}
+
+/* Determines whether or not an address book is collapsed in
+ * the tree view.
+ * @param aAddrBook the address book to check
+ * @return true if the address book is collapsed, otherwise false
+ */
+function is_address_book_collapsed(aAddrbook)
+{
+ let aIndex = get_address_book_tree_view_index(aAddrbook);
+ return !abController.window.gDirectoryTreeView.isContainerOpen(aIndex);
+}
+
+/* Determines whether or not an address book is collapsible in
+ * the tree view.
+ * @param aAddrBook the address book to check
+ * @return true if the address book is collapsible, otherwise false
+ */
+function is_address_book_collapsible(aAddrbook)
+{
+ let aIndex = get_address_book_tree_view_index(aAddrbook);
+ return !abController.window.gDirectoryTreeView.isContainerEmpty(aIndex);
+}
+
+/* Sets one or more address books to the expanded state in the
+ * tree view. If any of the address books cannot be expanded,
+ * an error is thrown.
+ * @param aAddrBooks either a lone address book, or an array of
+ * address books
+ */
+function set_address_books_expanded(aAddrBooks)
+{
+ if (!Array.isArray(aAddrBooks))
+ aAddrBooks = [aAddrBooks];
+
+ for (let i = 0; i < aAddrBooks.length; i++)
+ {
+ let addrBook = aAddrBooks[i];
+ if (!is_address_book_collapsible(addrBook))
+ throw Error("Address book called " + addrBook.dirName
+ + " cannot be expanded.");
+ if (is_address_book_collapsed(addrBook)) {
+ let aIndex = get_address_book_tree_view_index(addrBook);
+ abController.window.gDirectoryTreeView.toggleOpenState(aIndex);
+ }
+ }
+}
+
+/* Sets one or more address books to the collapsed state in the
+ * tree view. If any of the address books cannot be collapsed,
+ * an error is thrown.
+ * @param aAddrBooks either a lone address book, or an array of
+ * address books
+ */
+function set_address_books_collapsed(aAddrBooks)
+{
+ if (!Array.isArray(aAddrBooks))
+ aAddrBooks = [aAddrBooks];
+
+ for (let i = 0; i < aAddrBooks.length; i++)
+ {
+ let addrBook = aAddrBooks[i]
+ if (!is_address_book_collapsible(addrBook))
+ throw Error("Address book called " + addrBook.dirName
+ + " cannot be collapsed.");
+ if (!is_address_book_collapsed(addrBook)) {
+ let aIndex = get_address_book_tree_view_index(addrBook);
+ abController.window.gDirectoryTreeView.toggleOpenState(aIndex);
+ }
+ }
+}
+
+/* Returns the displayed name of an address book in the tree view
+ * at a particular row index.
+ * @param aIndex the row index of the target address book
+ * @return the displayed name of the address book
+ */
+function get_name_of_address_book_element_at(aIndex)
+{
+ return abController.window.gDirectoryTreeView.getCellText(aIndex, 0);
+}
+
+/* Selects a given address book in the tree view.
+ * @param aAddrBook an address book to select
+ */
+function select_address_book(aAddrBook)
+{
+ let aIndex = get_address_book_tree_view_index(aAddrBook);
+ abController.window.gDirectoryTreeView.selection.select(aIndex);
+}
--- a/mailnews/addrbook/content/abDragDrop.js
+++ b/mailnews/addrbook/content/abDragDrop.js
@@ -17,16 +17,17 @@
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Seth Spitzer <sspitzer@netscape.com>
* Mark Banner <mark@standard8.demon.co.uk>
+ * Mike Conley <mconley@mozillamessaging.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
@@ -83,17 +84,18 @@ var abResultsPaneObserver = {
getSupportedFlavours: function ()
{
return null;
}
};
-var dragService = Components.classes["@mozilla.org/widget/dragservice;1"].getService().QueryInterface(Components.interfaces.nsIDragService);
+var dragService = Components.classes["@mozilla.org/widget/dragservice;1"]
+ .getService().QueryInterface(Components.interfaces.nsIDragService);
var abDirTreeObserver = {
/**
* canDrop - determine if the tree will accept the dropping of a item
* onto it.
*
* Note 1: We don't allow duplicate mailing list names, therefore copy
* is not allowed for mailing lists.
@@ -116,18 +118,17 @@ var abDirTreeObserver = {
* card in mailing list -> other address book = MOVE or COPY
* read only directory item -> anywhere = COPY only
*/
canDrop: function(index, orientation)
{
if (orientation != Components.interfaces.nsITreeView.DROP_ON)
return false;
- var targetResource = dirTree.builderView.getResourceAtIndex(index);
- var targetURI = targetResource.Value;
+ var targetURI = gDirectoryTreeView.getDirectoryAtIndex(index).URI;
var srcURI = GetSelectedDirectory();
// The same place case
if (targetURI == srcURI)
return false;
// determine if we dragging from a mailing list on a directory x to the parent (directory x).
@@ -218,38 +219,36 @@ var abDirTreeObserver = {
return true;
},
/**
* onDrop - we don't need to check again for correctness as the
* tree view calls canDrop just before calling onDrop.
*
*/
- onDrop: function(row, orientation)
+ onDrop: function(index, orientation)
{
var dragSession = dragService.getCurrentSession();
if (!dragSession)
return;
-
+
var trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
trans.addDataFlavor("moz/abcard");
- var targetResource = dirTree.builderView.getResourceAtIndex(row);
-
- var targetURI = targetResource.Value;
+ var targetURI = gDirectoryTreeView.getDirectoryAtIndex(index).URI;
var srcURI = GetSelectedDirectory();
for (var i = 0; i < dragSession.numDropItems; i++) {
dragSession.getData(trans, i);
var dataObj = new Object();
var flavor = new Object();
var len = new Object();
try {
trans.getAnyTransferData(flavor, dataObj, len);
- dataObj =
+ dataObj =
dataObj.value.QueryInterface(Components.interfaces.nsISupportsString);
}
catch (ex) {
continue;
}
var transData = dataObj.data.split("\n");
var rows = transData[0].split(",");
new file mode 100644
--- /dev/null
+++ b/mailnews/base/content/jsTreeView.js
@@ -0,0 +1,265 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mail tree code.
+ *
+ * The Initial Developer of the Original Code is
+ * Joey Minta <jminta@gmail.com>
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Mike Conley <mconley@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/**
+ * This file contains a prototype object designed to make the implementation of
+ * nsITreeViews in javascript simpler. This object requires that consumers
+ * override the _rebuild function. This function must set the _rowMap object to
+ * an array of objects fitting the following interface:
+ *
+ * readonly attribute string id - a unique identifier for the row/object
+ * readonly attribute integer level - the hierarchy level of the row
+ * attribute boolean open - whether or not this item's children are exposed
+ * string getText(aColName) - return the text to display for this row in the
+ * specified column
+ * void getProperties(aProps) - set the css-selectors on aProps when this is
+ * called
+ * attribute array children - return an array of child-objects also meeting this
+ * interface
+ */
+
+function PROTO_TREE_VIEW() {
+ this._tree = null;
+ this._rowMap = [];
+ this._persistOpenMap = [];
+}
+
+PROTO_TREE_VIEW.prototype = {
+ get rowCount() {
+ return this._rowMap.length;
+ },
+
+ /**
+ * CSS files will cue off of these. Note that we reach into the rowMap's
+ * items so that custom data-displays can define their own properties
+ */
+ getCellProperties: function jstv_getCellProperties(aRow, aCol, aProps) {
+ this._rowMap[aRow].getProperties(aProps, aCol);
+ },
+
+ /**
+ * The actual text to display in the tree
+ */
+ getCellText: function jstv_getCellText(aRow, aCol) {
+ return this._rowMap[aRow].getText(aCol.id);
+ },
+
+ /**
+ * The jstv items take care of assigning this when building children lists
+ */
+ getLevel: function jstv_getLevel(aIndex) {
+ return this._rowMap[aIndex].level;
+ },
+
+ /**
+ * This is easy since the jstv items assigned the _parent property when making
+ * the child lists
+ */
+ getParentIndex: function jstv_getParentIndex(aIndex) {
+ for (let i = 0; i < this._rowMap.length; i++) {
+ if (this._rowMap[i] == this._rowMap[aIndex]._parent)
+ return i;
+ }
+ return -1;
+ },
+
+ /**
+ * This is duplicative for our normal jstv views, but custom data-displays may
+ * want to do something special here
+ */
+ getRowProperties: function jstv_getRowProperties(aIndex, aProps) {
+ this._rowMap[aIndex].getProperties(aProps);
+ },
+
+ /**
+ * If an item in our list has the same level and parent as us, it's a sibling
+ */
+ hasNextSibling: function jstv_hasNextSibling(aIndex, aNextIndex) {
+ let targetLevel = this._rowMap[aIndex].level;
+ for (let i = aNextIndex + 1; i < this._rowMap.length; i++) {
+ if (this._rowMap[i].level == targetLevel)
+ return true;
+ if (this._rowMap[i].level < targetLevel)
+ return false;
+ }
+ return false;
+ },
+
+ /**
+ * If we have a child-list with at least one element, we are a container.
+ */
+ isContainer: function jstv_isContainer(aIndex) {
+ return this._rowMap[aIndex].children.length > 0;
+ },
+
+ isContainerEmpty: function jstv_isContainerEmpty(aIndex) {
+ // If the container has no children, the container is empty.
+ return !this._rowMap[aIndex].children.length;
+ },
+
+ /**
+ * Just look at the jstv item here
+ */
+ isContainerOpen: function jstv_isContainerOpen(aIndex) {
+ return this._rowMap[aIndex].open;
+ },
+
+ isEditable: function jstv_isEditable(aRow, aCol) {
+ // We don't support editing rows in the tree yet.
+ return false;
+ },
+
+ isSeparator: function jstv_isSeparator(aIndex) {
+ // There are no separators in our trees
+ return false;
+ },
+
+ isSorted: function jstv_isSorted() {
+ // We do our own customized sorting
+ return false;
+ },
+
+ setTree: function jstv_setTree(aTree) {
+ this._tree = aTree;
+ },
+
+ /**
+ * Opens or closes a container with children. The logic here is a bit hairy, so
+ * be very careful about changing anything.
+ */
+ toggleOpenState: function jstv_toggleOpenState(aIndex) {
+
+ // Ok, this is a bit tricky.
+ this._rowMap[aIndex]._open = !this._rowMap[aIndex].open;
+
+ if (!this._rowMap[aIndex].open) {
+ // We're closing the current container. Remove the children
+
+ // Note that we can't simply splice out children.length, because some of
+ // them might have children too. Find out how many items we're actually
+ // going to splice
+ let level = this._rowMap[aIndex].level;
+ let row = aIndex + 1;
+ while (row < this._rowMap.length && this._rowMap[row].level > level) {
+ row++;
+ }
+ let count = row - aIndex - 1;
+ this._rowMap.splice(aIndex + 1, count);
+
+ // Remove us from the persist map
+ let index = this._persistOpenMap.indexOf(this._rowMap[aIndex].id);
+ if (index != -1)
+ this._persistOpenMap.splice(index, 1);
+
+ // Notify the tree of changes
+ if (this._tree) {
+ this._tree.rowCountChanged(aIndex + 1, -count);
+ }
+ } else {
+ // We're opening the container. Add the children to our map
+
+ // Note that these children may have been open when we were last closed,
+ // and if they are, we also have to add those grandchildren to the map
+ let tree = this;
+ let oldCount = this._rowMap.length;
+ function recursivelyAddToMap(aChild, aNewIndex) {
+ // When we add sub-children, we're going to need to increase our index
+ // for the next add item at our own level
+ let currentCount = tree._rowMap.length;
+ if (aChild.children.length && aChild.open) {
+ for (let [i, child] in Iterator(tree._rowMap[aNewIndex].children)) {
+ let index = aNewIndex + i + 1;
+ tree._rowMap.splice(index, 0, child);
+ aNewIndex += recursivelyAddToMap(child, index);
+ }
+ }
+ return tree._rowMap.length - currentCount;
+ }
+ recursivelyAddToMap(this._rowMap[aIndex], aIndex);
+
+ // Add this container to the persist map
+ let id = this._rowMap[aIndex].id;
+ if (this._persistOpenMap.indexOf(id) == -1)
+ this._persistOpenMap.push(id);
+
+ // Notify the tree of changes
+ if (this._tree)
+ this._tree.rowCountChanged(aIndex + 1, this._rowMap.length - oldCount);
+ }
+
+ // Invalidate the toggled row, so that the open/closed marker changes
+ if (this._tree)
+ this._tree.invalidateRow(aIndex);
+ },
+
+ // We don't implement any of these at the moment
+ canDrop: function jstv_canDrop(aIndex, aOrientation) {},
+ drop: function jstv_drop(aRow, aOrientation) {},
+ performAction: function jstv_performAction(aAction) {},
+ performActionOnCell: function jstv_performActionOnCell(aAction, aRow, aCol) {},
+ performActionOnRow: function jstv_performActionOnRow(aAction, aRow) {},
+ selectionChanged: function jstv_selectionChanged() {},
+ setCellText: function jstv_setCellText(aRow, aCol, aValue) {},
+ setCellValue: function jstv_setCellValue(aRow, aCol, aValue) {},
+ getCellValue: function jstv_getCellValue(aRow, aCol) {},
+ getColumnProperties: function jstv_getColumnProperties(aCol, aProps) {},
+ getImageSrc: function jstv_getImageSrc(aRow, aCol) {},
+ getProgressMode: function jstv_getProgressMode(aRow, aCol) {},
+ cycleCell: function jstv_cycleCell(aRow, aCol) {},
+ cycleHeader: function jstv_cycleHeader(aCol) {},
+
+ _tree: null,
+
+ /**
+ * An array of jstv items, where each item corresponds to a row in the tree
+ */
+ _rowMap: null,
+
+ /**
+ * This is a javascript map of which containers we had open, so that we can
+ * persist their state over-time. It is designed to be used as a JSON object.
+ */
+ _persistOpenMap: null,
+
+ _restoreOpenStates: function jstv__restoreOpenStates() {
+ // Note that as we iterate through here, .length may grow
+ for (let i = 0; i < this._rowMap.length; i++) {
+ if (this._persistOpenMap.indexOf(this._rowMap[i].id) != -1)
+ this.toggleOpenState(i);
+ }
+ }
+};
--- a/mailnews/jar.mn
+++ b/mailnews/jar.mn
@@ -92,16 +92,17 @@ messenger.jar:
content/messenger/virtualFolderProperties.js (base/content/virtualFolderProperties.js)
content/messenger/virtualFolderListDialog.xul (base/content/virtualFolderListDialog.xul)
content/messenger/virtualFolderListDialog.js (base/content/virtualFolderListDialog.js)
content/messenger/msgPrintEngine.js (base/content/msgPrintEngine.js)
* content/messenger/junkMailInfo.xul (base/content/junkMailInfo.xul)
content/messenger/junkCommands.js (base/content/junkCommands.js)
content/messenger/junkLog.xul (base/content/junkLog.xul)
content/messenger/junkLog.js (base/content/junkLog.js)
+ content/messenger/jsTreeView.js (base/content/jsTreeView.js)
content/messenger/searchTermOverlay.js (base/search/content/searchTermOverlay.js)
content/messenger/searchTermOverlay.xul (base/search/content/searchTermOverlay.xul)
content/messenger/CustomHeaders.xul (base/search/content/CustomHeaders.xul)
content/messenger/CustomHeaders.js (base/search/content/CustomHeaders.js)
content/messenger/FilterEditor.xul (base/search/content/FilterEditor.xul)
content/messenger/FilterEditor.js (base/search/content/FilterEditor.js)
* content/messenger/searchWidgets.xml (base/search/content/searchWidgets.xml)
content/messenger/viewLog.xul (base/search/content/viewLog.xul)