Bug 422845 - Replace rdf-driven addressbook directory tree with js one; Patch originally by Joey Minta, updated and completed by Mike Conley. r=Standard8

This commit is contained in:
Joey Minta 2011-05-06 12:03:00 +01:00
Родитель 1bfa4fa196
Коммит e3213adec4
11 изменённых файлов: 1049 добавлений и 115 удалений

Просмотреть файл

@ -25,6 +25,7 @@
# Seth Spitzer <sspitzer@netscape.com> # Seth Spitzer <sspitzer@netscape.com>
# Mark Banner <mark@standard8.demon.co.uk> # Mark Banner <mark@standard8.demon.co.uk>
# Simon Wilkinson <simon@sxw.org.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 # 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 # either the GNU General Public License Version 2 or later (the "GPL"), or
@ -42,7 +43,7 @@
Components.utils.import("resource:///modules/mailServices.js"); Components.utils.import("resource:///modules/mailServices.js");
var dirTree = 0; var gDirTree = 0;
var abList = 0; var abList = 0;
var gAbResultsTree = null; var gAbResultsTree = null;
var gAbView = null; var gAbView = null;
@ -85,7 +86,7 @@ var DirPaneController =
switch (command) { switch (command) {
case "cmd_selectAll": case "cmd_selectAll":
// the dirTree pane // the gDirTree pane
// only handles single selection // only handles single selection
// so we forward select all to the results pane // so we forward select all to the results pane
// but if there is no gAbView // but if there is no gAbView
@ -156,7 +157,7 @@ var DirPaneController =
break; break;
case "cmd_delete": case "cmd_delete":
case "button_delete": case "button_delete":
if (dirTree) if (gDirTree)
AbDeleteSelectedDirectory(); AbDeleteSelectedDirectory();
break; break;
case "button_edit": case "button_edit":
@ -203,7 +204,7 @@ function AbNewAddressBook()
function AbEditSelectedDirectory() function AbEditSelectedDirectory()
{ {
if (dirTree.view.selection.count == 1) { if (gDirTree.view.selection.count == 1) {
var selecteduri = GetSelectedDirectory(); var selecteduri = GetSelectedDirectory();
var directory = GetDirectoryFromURI(selecteduri); var directory = GetDirectoryFromURI(selecteduri);
if (directory.isMailList) { if (directory.isMailList) {
@ -288,7 +289,7 @@ function GetParentRow(aTree, aRow)
function InitCommonJS() function InitCommonJS()
{ {
dirTree = document.getElementById("dirTree"); gDirTree = document.getElementById("dirTree");
abList = document.getElementById("addressbookList"); abList = document.getElementById("addressbookList");
gAddressBookBundle = document.getElementById("bundle_addressBook"); gAddressBookBundle = document.getElementById("bundle_addressBook");
} }
@ -396,9 +397,8 @@ function GetSelectedAddressesFromDirTree()
{ {
var addresses = ""; var addresses = "";
if (dirTree.currentIndex >= 0) { if (gDirTree.currentIndex >= 0) {
var selectedResource = dirTree.builderView.getResourceAtIndex(dirTree.currentIndex); var directory = gDirectoryTreeView.getDirectoryAtIndex(gDirTree.currentIndex);
var directory = GetDirectoryFromURI(selectedResource.Value);
if (directory.isMailList) { if (directory.isMailList) {
var listCardsCount = directory.addressLists.length; var listCardsCount = directory.addressLists.length;
var cards = new Array(listCardsCount); var cards = new Array(listCardsCount);
@ -435,7 +435,7 @@ function GetAddressesForCards(cards)
function SelectFirstAddressBook() function SelectFirstAddressBook()
{ {
dirTree.view.selection.select(0); gDirTree.view.selection.select(0);
ChangeDirectoryByURI(GetSelectedDirectory()); ChangeDirectoryByURI(GetSelectedDirectory());
gAbResultsTree.focus(); gAbResultsTree.focus();
@ -460,13 +460,13 @@ function DirPaneDoubleClick(event)
if (event.button != 0) if (event.button != 0)
return; return;
var row = dirTree.treeBoxObject.getRowAt(event.clientX, event.clientY); var row = gDirTree.treeBoxObject.getRowAt(event.clientX, event.clientY);
if (row == -1 || row > dirTree.view.rowCount-1) { if (row == -1 || row > gDirTree.view.rowCount-1) {
// double clicking on a non valid row should not open the dir properties dialog // double clicking on a non valid row should not open the dir properties dialog
return; return;
} }
if (dirTree && dirTree.view.selection && dirTree.view.selection.count == 1) if (gDirTree && gDirTree.view.selection && gDirTree.view.selection.count == 1)
AbEditSelectedDirectory(); AbEditSelectedDirectory();
} }
@ -474,8 +474,8 @@ function DirPaneSelectionChange()
{ {
// clear out the search box when changing folders... // clear out the search box when changing folders...
onAbClearSearch(); onAbClearSearch();
if (dirTree && dirTree.view.selection && dirTree.view.selection.count == 1) { if (gDirTree && gDirTree.view.selection && gDirTree.view.selection.count == 1) {
gPreviousDirTreeIndex = dirTree.currentIndex; gPreviousDirTreeIndex = gDirTree.currentIndex;
ChangeDirectoryByURI(GetSelectedDirectory()); ChangeDirectoryByURI(GetSelectedDirectory());
} }
goUpdateCommand('cmd_newlist'); goUpdateCommand('cmd_newlist');
@ -607,7 +607,7 @@ function GetParentDirectoryFromMailingListURI(abURI)
function DirPaneHasFocus() function DirPaneHasFocus()
{ {
// returns true if diectory pane has the focus. Returns false, otherwise. // 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() function GetSelectedDirectory()
@ -615,10 +615,9 @@ function GetSelectedDirectory()
if (abList) if (abList)
return abList.value; return abList.value;
else { else {
if (dirTree.currentIndex < 0) if (gDirTree.currentIndex < 0)
return null; return null;
var selected = dirTree.builderView.getResourceAtIndex(dirTree.currentIndex) return gDirectoryTreeView.getDirectoryAtIndex(gDirTree.currentIndex).URI;
return selected.Value;
} }
} }

Просмотреть файл

@ -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();

Просмотреть файл

@ -25,6 +25,7 @@
# Contributor(s): # Contributor(s):
# Seth Spitzer <sspitzer@netscape.com> # Seth Spitzer <sspitzer@netscape.com>
# Mark Banner <mark@standard8.demon.co.uk> # 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 # 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 # either the GNU General Public License Version 2 or later (the "GPL"), or
@ -42,16 +43,17 @@
// Ensure the activity modules are loaded for this window. // Ensure the activity modules are loaded for this window.
Components.utils.import("resource:///modules/activity/activityModules.js"); Components.utils.import("resource:///modules/activity/activityModules.js");
Components.utils.import("resource:///modules/mailServices.js");
const nsIAbListener = Components.interfaces.nsIAbListener; const nsIAbListener = Components.interfaces.nsIAbListener;
const kPrefMailAddrBookLastNameFirst = "mail.addr_book.lastnamefirst"; const kPrefMailAddrBookLastNameFirst = "mail.addr_book.lastnamefirst";
const kPersistCollapseMapStorage = "directoryTree.json";
var cvPrefs = 0; var cvPrefs = 0;
var gSearchTimer = null; var gSearchTimer = null;
var gStatusText = null; var gStatusText = null;
var gQueryURIFormat = null; var gQueryURIFormat = null;
var gSearchInput; var gSearchInput;
var gDirTree;
var gCardViewBox; var gCardViewBox;
var gCardViewBoxEmail1; var gCardViewBoxEmail1;
var gPreviousDirTreeIndex = -1; var gPreviousDirTreeIndex = -1;
@ -86,7 +88,7 @@ var gAddressBookAbListener = {
// Don't reselect if we already have a valid selection, this may be // 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, // the case if items are being removed via other methods, e.g. sidebar,
// LDAP preference pane etc. // LDAP preference pane etc.
if (dirTree.currentIndex == -1) { if (gDirTree.currentIndex == -1) {
var directory = item.QueryInterface(Components.interfaces.nsIAbDirectory); var directory = item.QueryInterface(Components.interfaces.nsIAbDirectory);
// If we are a mail list, move the selection up the list before // If we are a mail list, move the selection up the list before
@ -99,7 +101,7 @@ var gAddressBookAbListener = {
--gPreviousDirTreeIndex; --gPreviousDirTreeIndex;
// Now get the parent of the row. // 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 // if we have no parent (i.e. we are an address book), use the
// previous index. // previous index.
@ -107,11 +109,11 @@ var gAddressBookAbListener = {
newRow = gPreviousDirTreeIndex; newRow = gPreviousDirTreeIndex;
// Fall back to the first adddress book if we're not in a valid range // 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; newRow = 0;
// Now select the new item. // Now select the new item.
dirTree.view.selection.select(newRow); gDirTree.view.selection.select(newRow);
} }
} }
} }
@ -125,9 +127,12 @@ var gAddressBookAbListener = {
function OnUnloadAddressBook() function OnUnloadAddressBook()
{ {
Components.classes["@mozilla.org/abmanager;1"] MailServices.ab.removeAddressBookListener(gAddressBookAbListener);
.getService(Components.interfaces.nsIAbManager) MailServices.ab.removeAddressBookListener(gDirectoryTreeView);
.removeAddressBookListener(gAddressBookAbListener);
// 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"] Components.classes["@mozilla.org/messenger/services/session;1"]
.getService(Components.interfaces.nsIMsgMailSession) .getService(Components.interfaces.nsIMsgMailSession)
@ -188,8 +193,11 @@ function delayedOnLoadAddressBook()
// FIX ME - later we will be able to use onload from the overlay // FIX ME - later we will be able to use onload from the overlay
OnLoadCardView(); OnLoadCardView();
//workaround - add setTimeout to make sure dynamic overlays get loaded first // Initialize the Address Book tree view
setTimeout('OnLoadDirTree()', 0); gDirectoryTreeView.init(gDirTree,
kPersistCollapseMapStorage);
SelectFirstAddressBook();
// if the pref is locked disable the menuitem New->LDAP directory // if the pref is locked disable the menuitem New->LDAP directory
if (gPrefs.prefIsLocked("ldap_2.disable_button_add")) if (gPrefs.prefIsLocked("ldap_2.disable_button_add"))
@ -200,15 +208,13 @@ function delayedOnLoadAddressBook()
// directory item is/are removed. In the case of directory items, we are // 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 // only really interested in mailing list changes and not cards but we have
// to have both. // to have both.
Components.classes["@mozilla.org/abmanager;1"] MailServices.ab.addAddressBookListener(gAddressBookAbListener,
.getService(Components.interfaces.nsIAbManager) nsIAbListener.directoryRemoved |
.addAddressBookListener(gAddressBookAbListener, nsIAbListener.directoryItemRemoved);
nsIAbListener.directoryRemoved | MailServices.ab.addAddressBookListener(gDirectoryTreeView, nsIAbListener.all);
nsIAbListener.directoryItemRemoved);
var dirTree = GetDirTree();
dirTree.addEventListener("click",DirPaneClick,true); gDirTree.controllers.appendController(DirPaneController);
dirTree.controllers.appendController(DirPaneController);
// initialize the customizeDone method on the customizeable toolbar // initialize the customizeDone method on the customizeable toolbar
var toolbox = document.getElementById("ab-toolbox"); var toolbox = document.getElementById("ab-toolbox");
@ -228,12 +234,6 @@ function delayedOnLoadAddressBook()
.AddMsgWindow(msgWindow); .AddMsgWindow(msgWindow);
} }
function OnLoadDirTree() {
var treeBuilder = dirTree.builder.QueryInterface(Components.interfaces.nsIXULTreeBuilder);
treeBuilder.addObserver(abDirTreeObserver);
SelectFirstAddressBook();
}
function GetCurrentPrefs() function GetCurrentPrefs()
{ {
@ -435,9 +435,7 @@ function AbExport()
if (!selectedABURI) return; if (!selectedABURI) return;
var directory = GetDirectoryFromURI(selectedABURI); var directory = GetDirectoryFromURI(selectedABURI);
Components.classes["@mozilla.org/abmanager;1"] MailServices.ab.exportAddressBook(window, directory);
.getService(Components.interfaces.nsIAbManager)
.exportAddressBook(window, directory);
} }
catch (ex) { catch (ex) {
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService); var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
@ -740,13 +738,10 @@ function AbOSXAddressBookExists()
function AbShowHideOSXAddressBook() function AbShowHideOSXAddressBook()
{ {
var abMgr = Components.classes["@mozilla.org/abmanager;1"]
.getService(Components.interfaces.nsIAbManager);
if (AbOSXAddressBookExists()) if (AbOSXAddressBookExists())
abMgr.deleteAddressBook(kOSXDirectoryURI); MailServices.ab.deleteAddressBook(kOSXDirectoryURI);
else { else {
abMgr.newAddressBook( MailServices.ab.newAddressBook(
gAddressBookBundle.getString(kOSXPrefBase + ".description"), gAddressBookBundle.getString(kOSXPrefBase + ".description"),
kOSXDirectoryURI, 3, kOSXPrefBase); kOSXDirectoryURI, 3, kOSXPrefBase);
} }

Просмотреть файл

@ -71,6 +71,8 @@
<stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/> <stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
</stringbundleset> </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/accountUtils.js"/>
<script type="application/javascript" src="chrome://messenger/content/widgetglue.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/mailCore.js"/>
@ -610,56 +612,18 @@
<!-- FIX ME - remove document.commandDispatcher.updateCommands() when tree selection calls this automatically --> <!-- 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" <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" hidecolumnpicker="true"
context="dirTreeContext" context="dirTreeContext"
onselect="DirPaneSelectionChange(); document.commandDispatcher.updateCommands('addrbook-select');" onselect="DirPaneSelectionChange(); document.commandDispatcher.updateCommands('addrbook-select');"
ondblclick="DirPaneDoubleClick(event);" ondblclick="DirPaneDoubleClick(event);"
onclick="DirPaneClick(event);"
onblur="goOnEvent(this,'blur')"> 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"/>
</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> <treecols>
<treechildren> <treecol id="DirCol" flex="1" primary="true" hideheader="true"
<treeitem uri="?member" persist="sortDirection sortColumn open"> crop="center" persist="width" ignoreincolumnpicker="true"/>
<treerow> </treecols>
<treecell label="?DirName" properties="IsMailList-?IsMailList IsRemote-?IsRemote IsSecure-?IsSecure"/> <treechildren/>
</treerow>
</treeitem>
</treechildren>
</action>
</rule>
</template>
</tree> </tree>
</vbox> </vbox>

Просмотреть файл

@ -9,3 +9,4 @@ messenger.jar:
* content/messenger/addressbook/abMailListDialog.xul (content/abMailListDialog.xul) * content/messenger/addressbook/abMailListDialog.xul (content/abMailListDialog.xul)
* content/messenger/addressbook/abContactsPanel.xul (content/abContactsPanel.xul) * content/messenger/addressbook/abContactsPanel.xul (content/abContactsPanel.xul)
* content/messenger/addressbook/abContactsPanel.js (content/abContactsPanel.js) * content/messenger/addressbook/abContactsPanel.js (content/abContactsPanel.js)
content/messenger/addressbook/abTrees.js (content/abTrees.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));
}

Просмотреть файл

@ -1,4 +1,5 @@
account account
addrbook
composition composition
content-policy content-policy
content-tabs content-tabs

Просмотреть файл

@ -41,14 +41,23 @@ var Cu = Components.utils;
const MODULE_NAME = "address-book-helpers"; const MODULE_NAME = "address-book-helpers";
const RELATIVE_ROOT = "../shared-modules"; 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 collectedAddresses;
var abController;
function setupModule() { function setupModule() {
let abManager = Cc["@mozilla.org/abmanager;1"].getService(Ci.nsIAbManager);
// Ensure all the directories are initialised. // Ensure all the directories are initialised.
abManager.directories; MailServices.ab.directories;
collectedAddresses = abManager.getDirectory("moz-abmdbdirectory://history.mab"); collectedAddresses = MailServices.ab
.getDirectory("moz-abmdbdirectory://history.mab");
} }
function installInto(module) { function installInto(module) {
@ -57,6 +66,26 @@ function installInto(module) {
// Now copy helper functions // Now copy helper functions
module.ensure_card_exists = ensure_card_exists; module.ensure_card_exists = ensure_card_exists;
module.ensure_no_card_exists = ensure_no_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;
} }
/** /**
@ -66,14 +95,10 @@ function installInto(module) {
* @param preferDisplayName |true| if the card display name should override the * @param preferDisplayName |true| if the card display name should override the
* header display name * header display name
*/ */
function ensure_card_exists(emailAddress, displayName, preferDisplayName) { function ensure_card_exists(emailAddress, displayName, preferDisplayName)
{
ensure_no_card_exists(emailAddress); ensure_no_card_exists(emailAddress);
let card = Cc["@mozilla.org/addressbook/cardproperty;1"] let card = create_card(emailAddress, displayName, preferDisplayName);
.createInstance(Ci.nsIAbCard);
card.primaryEmail = emailAddress;
card.displayName = displayName;
card.setProperty("PreferDisplayName", preferDisplayName ? true : false);
collectedAddresses.addCard(card); collectedAddresses.addCard(card);
} }
@ -83,8 +108,7 @@ function ensure_card_exists(emailAddress, displayName, preferDisplayName) {
*/ */
function ensure_no_card_exists(emailAddress) function ensure_no_card_exists(emailAddress)
{ {
var books = Cc["@mozilla.org/abmanager;1"].getService(Ci.nsIAbManager) var books = MailServices.ab.directories;
.directories;
while (books.hasMoreElements()) { while (books.hasMoreElements()) {
var ab = books.getNext().QueryInterface(Ci.nsIAbDirectory); var ab = books.getNext().QueryInterface(Ci.nsIAbDirectory);
@ -101,3 +125,211 @@ function ensure_no_card_exists(emailAddress)
} }
} }
/**
* 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);
}

Просмотреть файл

@ -22,6 +22,7 @@
* Contributor(s): * Contributor(s):
* Seth Spitzer <sspitzer@netscape.com> * Seth Spitzer <sspitzer@netscape.com>
* Mark Banner <mark@standard8.demon.co.uk> * 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 * 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"), * either of the GNU General Public License Version 2 or later (the "GPL"),
@ -88,7 +89,8 @@ var abResultsPaneObserver = {
}; };
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 = { var abDirTreeObserver = {
/** /**
@ -121,8 +123,7 @@ var abDirTreeObserver = {
if (orientation != Components.interfaces.nsITreeView.DROP_ON) if (orientation != Components.interfaces.nsITreeView.DROP_ON)
return false; return false;
var targetResource = dirTree.builderView.getResourceAtIndex(index); var targetURI = gDirectoryTreeView.getDirectoryAtIndex(index).URI;
var targetURI = targetResource.Value;
var srcURI = GetSelectedDirectory(); var srcURI = GetSelectedDirectory();
@ -223,18 +224,16 @@ var abDirTreeObserver = {
* tree view calls canDrop just before calling onDrop. * tree view calls canDrop just before calling onDrop.
* *
*/ */
onDrop: function(row, orientation) onDrop: function(index, orientation)
{ {
var dragSession = dragService.getCurrentSession(); var dragSession = dragService.getCurrentSession();
if (!dragSession) if (!dragSession)
return; return;
var trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable); var trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
trans.addDataFlavor("moz/abcard"); trans.addDataFlavor("moz/abcard");
var targetResource = dirTree.builderView.getResourceAtIndex(row); var targetURI = gDirectoryTreeView.getDirectoryAtIndex(index).URI;
var targetURI = targetResource.Value;
var srcURI = GetSelectedDirectory(); var srcURI = GetSelectedDirectory();
for (var i = 0; i < dragSession.numDropItems; i++) { for (var i = 0; i < dragSession.numDropItems; i++) {
@ -244,7 +243,7 @@ var abDirTreeObserver = {
var len = new Object(); var len = new Object();
try { try {
trans.getAnyTransferData(flavor, dataObj, len); trans.getAnyTransferData(flavor, dataObj, len);
dataObj = dataObj =
dataObj.value.QueryInterface(Components.interfaces.nsISupportsString); dataObj.value.QueryInterface(Components.interfaces.nsISupportsString);
} }
catch (ex) { catch (ex) {

Просмотреть файл

@ -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);
}
}
};

Просмотреть файл

@ -97,6 +97,7 @@ messenger.jar:
content/messenger/junkCommands.js (base/content/junkCommands.js) content/messenger/junkCommands.js (base/content/junkCommands.js)
content/messenger/junkLog.xul (base/content/junkLog.xul) content/messenger/junkLog.xul (base/content/junkLog.xul)
content/messenger/junkLog.js (base/content/junkLog.js) 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.js (base/search/content/searchTermOverlay.js)
content/messenger/searchTermOverlay.xul (base/search/content/searchTermOverlay.xul) content/messenger/searchTermOverlay.xul (base/search/content/searchTermOverlay.xul)
content/messenger/CustomHeaders.xul (base/search/content/CustomHeaders.xul) content/messenger/CustomHeaders.xul (base/search/content/CustomHeaders.xul)