new js folder pane, w/o rdf, original work by jminta, with additional work by me, r=standard8, with some review by neil, 414038
new js folder pane, w/o rdf, original work by jminta, with additional work by me, r=standard8, with some review by neil, 414038
--- a/mail/base/content/commandglue.js
+++ b/mail/base/content/commandglue.js
@@ -63,41 +63,16 @@ var MSG_FOLDER_FLAG_TRASH = 0x0100;
var MSG_FOLDER_FLAG_SENTMAIL = 0x0200;
var MSG_FOLDER_FLAG_DRAFTS = 0x0400;
var MSG_FOLDER_FLAG_QUEUE = 0x0800;
var MSG_FOLDER_FLAG_INBOX = 0x1000;
var MSG_FOLDER_FLAG_TEMPLATES = 0x400000;
var MSG_FOLDER_FLAG_JUNK = 0x40000000;
var MSG_FOLDER_FLAG_FAVORITE = 0x80000000;
-function GetMsgFolderFromResource(folderResource)
-{
- if (!folderResource)
- return null;
-
- var msgFolder = folderResource.QueryInterface(Components.interfaces.nsIMsgFolder);
- if (msgFolder && (msgFolder.parent || msgFolder.isServer))
- return msgFolder;
- else
- return null;
-}
-
-function GetServer(uri)
-{
- if (!uri) return null;
- try {
- var folder = GetMsgFolderFromUri(uri, true);
- return folder.server;
- }
- catch (ex) {
- dump("GetServer("+uri+") failed, ex="+ex+"\n");
- }
- return null;
-}
-
function setTitleFromFolder(msgfolder, subject)
{
var wintype = document.documentElement.getAttribute('windowtype');
var title;
// If we are showing the mail:3pane. Never include the subject of the selected
// message in the title. ("Inbox - My Mail - Mozilla Thunderbird")
// If we are a stand alone message window, we should show the Subject
@@ -341,17 +316,16 @@ function RerootFolder(uri, newFolder, vi
{
if (oldFolder.URI != newFolder.URI)
oldFolder.setMsgDatabase(null);
}
}
// that should have initialized gDBView, now re-root the thread pane
RerootThreadPane();
SetUpToolbarButtons(uri);
- UpdateFolderLocationPicker(gMsgFolderSelected);
UpdateStatusMessageCounts(gMsgFolderSelected);
// hook for extra toolbar items
var observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
observerService.notifyObservers(window, "mail:updateToolbarItems", null);
// this is to kick off cross-folder searches for virtual folders.
if (gSearchSession && !gVirtualFolderTerms) // another var might be better...
{
@@ -814,18 +788,17 @@ function OnMouseUpThreadAndMessagePaneSp
{
// the collapsed state is the state after we released the mouse
// so we take it as it is
ChangeMessagePaneVisibility(IsMessagePaneCollapsed());
}
function FolderPaneSelectionChange()
{
- var folderTree = GetFolderTree();
- var folderSelection = folderTree.view.selection;
+ var folderSelection = gFolderTreeView.selection;
// This prevents a folder from being loaded in the case that the user
// has right-clicked on a folder different from the one that was
// originally highlighted. On a right-click, the highlight (selection)
// of a row will be different from the value of currentIndex, thus if
// the currentIndex is not selected, it means the user right-clicked
// and we don't want to load the contents of the folder.
if (!folderSelection.isSelected(folderSelection.currentIndex))
@@ -843,17 +816,16 @@ function FolderPaneSelectionChange()
if (msgFolder == gMsgFolderSelected)
return;
// If msgFolder turns out to be a single folder saved search, a virtual folder,
// realFolder will get set to the underlying folder the
// saved search is based on.
var realFolder = msgFolder;
gPrevSelectedFolder = gMsgFolderSelected;
gMsgFolderSelected = msgFolder;
- UpdateFolderLocationPicker(gMsgFolderSelected);
var folderFlags = msgFolder.flags;
// If this is same folder, and we're not showing a virtual folder
// then do nothing.
if (msgFolder == msgWindow.openFolder &&
!(folderFlags & MSG_FOLDER_FLAG_VIRTUAL) && ! (gPrevFolderFlags & MSG_FOLDER_FLAG_VIRTUAL))
{
return;
}
@@ -1075,45 +1047,39 @@ function CreateVirtualFolder(newName, p
}
}
else
{
dump("no name or nothing selected\n");
}
}
-var searchSessionContractID = "@mozilla.org/messenger/searchSession;1";
-var gSearchView;
var gSearchSession;
-var nsIMsgFolder = Components.interfaces.nsIMsgFolder;
-var nsIMsgWindow = Components.interfaces.nsIMsgWindow;
var nsMsgSearchScope = Components.interfaces.nsMsgSearchScope;
-var gFolderDatasource;
-var gFolderPicker;
-var gStatusBar = null;
var gMessengerBundle = null;
// Datasource search listener -- made global as it has to be registered
// and unregistered in different functions.
-var gDataSourceSearchListener;
var gViewSearchListener;
function GetScopeForFolder(folder)
{
return folder.server.searchScope;
}
function setupXFVirtualFolderSearch(folderUrisToSearch, searchTerms, searchOnline)
{
- var count = new Object;
+ const Ci = Components.interfaces;
+ var count = new Object;
var i;
- gSearchSession = Components.classes[searchSessionContractID].createInstance(Components.interfaces.nsIMsgSearchSession);
+ gSearchSession = Components.classes["@mozilla.org/messenger/searchSession;1"]
+ .createInstance(Ci.nsIMsgSearchSession);
for (i in folderUrisToSearch)
{
var realFolder = GetMsgFolderFromUri(folderUrisToSearch[i]);
if (!realFolder.isServer)
gSearchSession.addScopeTerm(!searchOnline ? nsMsgSearchScope.offlineMail : GetScopeForFolder(realFolder), realFolder);
}
@@ -1121,19 +1087,22 @@ function setupXFVirtualFolderSearch(fold
const nsIMsgSearchTerm = Components.interfaces.nsIMsgSearchTerm;
for each (var term in fixIterator(termsArray, nsIMsgSearchTerm)) {
gSearchSession.appendTerm(term);
}
}
function CreateGroupedSearchTerms(searchTermsArray)
{
-
- var searchSession = gSearchSession ||
- Components.classes[searchSessionContractID].createInstance(Components.interfaces.nsIMsgSearchSession);
+ const Ci = Components.interfaces;
+ var searchSession = gSearchSession;
+ if (!searchSession) {
+ searchSession = Components.classes["@mozilla.org/messenger/searchSession;1"]
+ .createInstance(Ci.nsIMsgSearchSession);
+ }
// create a temporary isupports array to store our search terms
// since we will be modifying the terms so they work with quick search
var searchTermsArrayForQS = Components.classes["@mozilla.org/supports-array;1"].createInstance(Components.interfaces.nsISupportsArray);
var numEntries = searchTermsArray.Count();
for (var i = 0; i < numEntries; i++) {
var searchTerm = searchTermsArray.GetElementAt(i).QueryInterface(Components.interfaces.nsIMsgSearchTerm);
new file mode 100644
--- /dev/null
+++ b/mail/base/content/folderPane.js
@@ -0,0 +1,1369 @@
+/* ***** 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 folder 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):
+ *
+ * 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 ***** */
+
+Components.utils.import("resource://gre/modules/iteratorUtils.jsm");
+Components.utils.import("resource://gre/modules/folderUtils.jsm");
+
+/**
+ * This file contains the controls and functions for the folder pane.
+ * The following definitions will be useful to know:
+ *
+ * gFolderTreeView - the controller for the folder tree.
+ * ftvItem - folder tree view item, representing a row in the tree
+ * mode - folder view type, e.g., all folders, favorite folders, MRU...
+ */
+
+/**
+ * This is our controller for the folder-tree. It includes our nsITreeView
+ * implementation, as well as other control functions.
+ */
+let gFolderTreeView = {
+ /**
+ * Called when the window is initially loaded. This function initializes the
+ * folder-pane to the view last shown before the application was closed.
+ */
+ load: function ftv_load(aTree, aJSONFile) {
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+ this._treeElement = aTree;
+
+ // the folder pane can be used for other trees which may not have these elements.
+ if (document.getElementById("folderpane_splitter"))
+ document.getElementById("folderpane_splitter").collapsed = false;
+ if (document.getElementById("folderPaneBox"))
+ document.getElementById("folderPaneBox").collapsed = false;
+
+ try {
+ // Normally our tree takes care of keeping the last selected by itself.
+ // However older versions of TB stored this in a preference, which we need
+ // to migrate
+ let prefB = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ let modeIndex = prefB.getIntPref("mail.ui.folderpane.view");
+ this._mode = this.modeNames[modeIndex];
+ prefB.deleteBranch("mail.ui.folderpane");
+ } catch(ex) {
+ // This is ok. If we've already migrated we'll end up here
+ }
+
+ if (document.getElementById('folderpane-title')) {
+ let key = "folderPaneHeader_" + this.mode;
+ let string = document.getElementById("bundle_messenger").getString(key);
+ document.getElementById('folderpane-title').value = string;
+ }
+
+ 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);
+ }
+ }
+
+ // Load our data
+ this._rebuild();
+ // And actually draw the tree
+ aTree.view = this;
+
+ // Add this listener so that we can update the tree when things change
+ let session = Cc["@mozilla.org/messenger/services/session;1"]
+ .getService(Ci.nsIMsgMailSession);
+ session.AddFolderListener(this, Ci.nsIFolderListener.all);
+
+ // Listen for account creation/deletion
+ Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch2)
+ .addObserver("mail.accountmanager.", this, false);
+ },
+
+ /**
+ * Called when the window is being torn down. Here we undo everything we did
+ * onload. That means removing our listener and serializing our JSON.
+ */
+ unload: function ftv_unload(aJSONFile) {
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+
+ // Remove our listener
+ let session = Cc["@mozilla.org/messenger/services/session;1"]
+ .getService(Ci.nsIMsgMailSession);
+ session.RemoveFolderListener(this);
+
+ Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch2)
+ .removeObserver("mail.accountmanager.", this, false);
+
+ 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("folderTree.json");
+ 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();
+ }
+ },
+
+ /**
+ * This is an array of all possible modes for the folder tree. Extensions are
+ * free to add to this pane, but you must make a corresponding addition to the
+ * _mapGenerators object.
+ */
+ modeNames: ["all", "unread", "favorite", "recent"],
+
+ /**
+ * Called to move to the next/prev folder-mode in the list
+ *
+ * @param aForward whether or not we should move forward in the list
+ */
+ cycleMode: function ftv_cycleMode(aForward) {
+ let index = this.modeNames.indexOf(this.mode);
+ let offset = aForward ? 1 : this.modeNames.length - 1;
+ index = (index + offset) % this.modeNames.length;
+
+ this.mode = this.modeNames[index];
+ },
+
+ /**
+ * If the hidden pref is set, then double-clicking on a folder should open it
+ *
+ * @param event the double-click event
+ */
+ onDoubleClick: function ftv_onDoubleClick(aEvent) {
+ if (pref.getBoolPref("mailnews.reuse_thread_window2") ||
+ aEvent.button != 0 || aEvent.originalTarget.localName == "twisty" ||
+ aEvent.originalTarget.localName == "slider" ||
+ aEvent.originalTarget.localName == "scrollbarbutton")
+ return;
+
+ let row = gFolderTreeView._treeElement.treeBoxObject.getRowAt(aEvent.clientX,
+ aEvent.clientY);
+ let folderItem = gFolderTreeView._rowMap[row];
+ if (folderItem)
+ folderItem.command();
+
+ // Don't let the double-click toggle the open state of the folder here
+ aEvent.stopPropagation();
+ },
+
+ getFolderAtCoords: function ftv_getFolderAtCoords(aX, aY) {
+ let row = gFolderTreeView._treeElement.treeBoxObject.getRowAt(aX, aY);
+ return gFolderTreeView._rowMap[row]._folder;
+ },
+
+ /**
+ * A string representation for the current display-mode. Each value here must
+ * correspond to an entry in _mapGenerators
+ */
+ _mode: null,
+ get mode() {
+ if (!this._mode)
+ this._mode = this._treeElement.getAttribute("mode");
+ return this._mode;
+ },
+ set mode(aMode) {
+ this._mode = aMode;
+
+ var string;
+ try {
+ let key = "folderPaneHeader_" + aMode;
+ string = document.getElementById("bundle_messenger").getString(key);
+ } catch(ex) {
+ string = aMode;
+ }
+ document.getElementById('folderpane-title').value = string;
+
+ this._treeElement.setAttribute("mode", aMode);
+ this._rebuild();
+ },
+
+ /**
+ * Selects a given nsIMsgFolder in the tree. This function will also ensure
+ * that the folder is actually being displayed (that is, that none of its
+ * ancestors are collapsed.
+ *
+ * @param aFolderUri the nsIMsgFolder to select
+ */
+ selectFolder: function ftv_selectFolder(aFolder) {
+ // "this" inside the nested function refers to the function...
+ // Also note that openIfNot is recursive.
+ let tree = this;
+ function openIfNot(aFolderToOpen) {
+ let index = tree.getIndexOfFolder(aFolderToOpen);
+ if (index) {
+ if (!tree._rowMap[index].open)
+ tree._toggleRow(index, false);
+ return;
+ }
+
+ // not found, so open the parent
+ openIfNot(aFolderToOpen.parent);
+
+ // now our parent is open, so we can open ourselves
+ tree._toggleRow(tree.getIndexOfFolder(aFolderToOpen), false);
+ }
+ if (aFolder.parent)
+ openIfNot(aFolder.parent);
+ this.selection.select(tree.getIndexOfFolder(aFolder));
+ },
+
+ /**
+ * Returns the index of a folder in the current display.
+ *
+ * @param aFolder the folder whose index should be returned.
+ * @note If the folder is not in the display (perhaps because one of its
+ * anscetors is collapsed), this function returns null.
+ */
+ getIndexOfFolder: function ftv_getIndexOfFolder(aFolder) {
+ for (let i in this._rowMap) {
+ if (this._rowMap[i].id == aFolder.URI)
+ return i;
+ }
+ return null;
+ },
+
+ /**
+ * Returns an array of nsIMsgFolders corresponding to the current selection
+ * in the tree
+ */
+ getSelectedFolders: function ftv_getSelectedFolders() {
+ let folderArray = [];
+ let selection = this._treeElement.view.selection;
+ let rangeCount = selection.getRangeCount();
+ for (let i = 0; i < rangeCount; i++) {
+ let startIndex = {};
+ let endIndex = {};
+ selection.getRangeAt(i, startIndex, endIndex);
+ for (let j = startIndex.value; j <= endIndex.value; j++) {
+ folderArray.push(this._rowMap[j]._folder);
+ }
+ }
+ return folderArray;
+ },
+
+ // ****************** Start of nsITreeView implementation **************** //
+
+ get rowCount() {
+ return this._rowMap.length;
+ },
+
+ /**
+ * drag drop interfaces
+ */
+ canDrop: function ftv_canDrop(aRow, aOrientation) {
+ let targetFolder = gFolderTreeView._rowMap[aRow]._folder;
+ if (!targetFolder || !targetFolder.canFileMessages)
+ return false;
+ if (aOrientation != Components.interfaces.nsITreeView.DROP_ON)
+ return false;
+ return true;
+ },
+ drop: function ftv_drop(aRow, aOrientation) {
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+ let targetFolder = gFolderTreeView._rowMap[aRow]._folder;
+
+ let dt = this._currentTransfer;
+ let count = dt.mozItemCount;
+ let cs = Cc["@mozilla.org/messenger/messagecopyservice;1"]
+ .getService(Ci.nsIMsgCopyService);
+
+ // we only support drag of a single flavor at a time.
+ let types = dt.mozTypesAt(0);
+ if (Array.indexOf(types, "text/x-moz-folder") != -1) {
+ for (let i = 0; i < count; i++) {
+ let folders = new Array;
+ folders.push(dt.mozGetDataAt("text/x-moz-folder", i));
+ let array = toXPCOMArray(folders, Ci.nsIMutableArray);
+ cs.CopyFolders(array, targetFolder,
+ (folders[0].server == targetFolder.server), null,
+ msgWindow);
+ }
+ }
+ else if (Array.indexOf(types, "text/x-moz-message") != -1) {
+ let array = Cc["@mozilla.org/array;1"]
+ .createInstance(Components.interfaces.nsIMutableArray);
+ let sourceFolder;
+ let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+ for (let i = 0; i < count; i++) {
+ let msgHdr = messenger.msgHdrFromURI(dt.mozGetDataAt("text/x-moz-message", i));
+ if (!i)
+ sourceFolder = msgHdr.folder;
+ array.appendElement(msgHdr, false);
+ }
+ let prefBranch = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefService).getBranch("mail.");
+ let isMove = Cc["@mozilla.org/widget/dragservice;1"]
+ .getService(Ci.nsIDragService).getCurrentSession()
+ .dragAction == Ci.nsIDragService.DRAGDROP_ACTION_MOVE;
+
+ pref.setCharPref("last_msg_movecopy_target_uri", targetFolder.URI);
+ pref.setBoolPref("last_msg_movecopy_was_move", isMove);
+ // ### ugh, so this won't work with cross-folder views. We would
+ // really need to partition the messages by folder.
+ cs.CopyMessages(sourceFolder, array, targetFolder, isMove, null,
+ msgWindow, true);
+ }
+ else if (Array.indexOf(types, "text/x-moz-url") != -1) {
+ // This is a potential rss feed to subscribe to
+ // and there's only one, so just get the 0th element.
+ let url = dt.mozGetDataAt("text/x-moz-url", 0);
+ let uri = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService).newUri(url, null, null);
+ if (!(uri.schemeIs("http") || uri.schemeIs("https")) ||
+ targetFolder.server.type != 'rss')
+ return;
+
+ Cc["@mozilla.org/newsblog-feed-downloader;1"]
+ .getService(Ci.nsINewsBlogFeedDownloader)
+ .subscribeToFeed(url, targetFolder, msgWindow);
+ }
+ },
+
+ _onDragStart: function ftv_dragStart(aEvent) {
+ // Ugh, this is ugly but necessary
+ let view = gFolderTreeView;
+
+ if (aEvent.originalTarget.localName != "treechildren")
+ return;
+
+ let folders = view.getSelectedFolders();
+ folders = folders.filter(function(f) { return !f.isServer; });
+ for (let i in folders)
+ aEvent.dataTransfer.mozSetDataAt("text/x-moz-folder", folders[i], i);
+ aEvent.dataTransfer.effectAllowed = "copyMove";
+ aEvent.dataTransfer.addElement(aEvent.originalTarget);
+ return;
+ },
+
+ _onDragOver: function ftv_onDragOver(aEvent) {
+ this._currentTransfer = aEvent.dataTransfer;
+ },
+
+ /**
+ * 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 ftv_getCellProperties(aRow, aCol, aProps) {
+ this._rowMap[aRow].getProperties(aProps, aCol);
+ },
+
+ /**
+ * The actual text to display in the tree
+ */
+ getCellText: function ftv_getCellText(aRow, aCol) {
+ if (aCol.id == "folderNameCol")
+ return this._rowMap[aRow].text;
+ },
+
+ /**
+ * The ftvItems take care of assigning this when building children lists
+ */
+ getLevel: function ftv_getLevel(aIndex) {
+ return this._rowMap[aIndex].level;
+ },
+
+ /**
+ * This is easy since the ftv items assigned the _parent property when making
+ * the child lists
+ */
+ getParentIndex: function ftv_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 ftv views, but custom data-displays may
+ * want to do something special here
+ */
+ getRowProperties: function ftv_getRowProperties(aIndex, aProps) {
+ this._rowMap[aIndex].getProperties(aProps);
+ },
+
+ /**
+ * If the next item in our list has the same level as us, it's a sibling
+ */
+ hasNextSibling: function ftv_hasNextSibling(aIndex, aNextIndex) {
+ return this._rowMap[aIndex].level == this._rowMap[aNextIndex].level;
+ },
+
+ /**
+ * All folders are containers, so we can drag drop messages to them.
+ */
+ isContainer: function ftv_isContainer(aIndex) {
+ return true;
+ },
+
+ isContainerEmpty: function ftv_isContainerEmpty(aIndex) {
+ // If the folder has no children, the container is empty.
+ return !this._rowMap[aIndex].children.length;
+ },
+
+ /**
+ * Just look at the ftvItem here
+ */
+ isContainerOpen: function ftv_isContainerOpen(aIndex) {
+ return this._rowMap[aIndex].open;
+ },
+ isEditable: function ftv_isEditable(aRow, aCol) {
+ // We don't support editing rows in the tree yet. We may want to later as
+ // an easier way to rename folders.
+ return false;
+ },
+ isSeparator: function ftv_isSeparator(aIndex) {
+ // There are no separators in our trees
+ return false;
+ },
+ isSorted: function ftv_isSorted() {
+ // We do our own customized sorting
+ return false;
+ },
+ setTree: function ftv_setTree(aTree) {
+ this._tree = aTree;
+ },
+
+ /**
+ * Opens or closes a folder with children. The logic here is a bit hairy, so
+ * be very careful about changing anything.
+ */
+ toggleOpenState: function ftv_toggleOpenState(aIndex) {
+ this._toggleRow(aIndex, true);
+ },
+
+ _toggleRow: function toggleRow(aIndex, aExpandServer)
+ {
+ // 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 count = 0;
+ let i = aIndex + 1;
+ let row = this._rowMap[i];
+ while (row && row.level > this._rowMap[aIndex].level) {
+ count++;
+ row = this._rowMap[++i];
+ }
+ this._rowMap.splice(aIndex + 1, count);
+
+ // Remove us from the persist map
+ let index = this._persistOpenMap[this.mode]
+ .indexOf(this._rowMap[aIndex].id);
+ if (index != -1)
+ this._persistOpenMap[this.mode].splice(index, 1);
+
+ // Notify the tree of changes
+ if (this._tree) {
+ this._tree.rowCountChanged(aIndex + 1, (-1) * count);
+ this._tree.invalidateRow(aIndex);
+ }
+ } 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 count = 0;
+ if (aChild.children.length && aChild.open) {
+ for (let [i, child] in Iterator(tree._rowMap[aNewIndex].children)) {
+ count++;
+ var index = Number(aNewIndex) + Number(i) + 1;
+ tree._rowMap.splice(index, 0, child);
+ // Somehow the aNewIndex turns into a string without this
+ aNewIndex = Number(aNewIndex) + recursivelyAddToMap(child, index);
+ }
+ }
+ return count;
+ }
+ recursivelyAddToMap(this._rowMap[aIndex], aIndex);
+
+ // Add this folder to the persist map
+ if (!this._persistOpenMap[this.mode])
+ this._persistOpenMap[this.mode] = [];
+ let id = this._rowMap[aIndex].id;
+ if (this._persistOpenMap[this.mode].indexOf(id) == -1)
+ this._persistOpenMap[this.mode].push(id);
+
+ // Notify the tree of changes
+ if (this._tree)
+ this._tree.rowCountChanged(aIndex + 1, this._rowMap.length - oldCount);
+ // if this was a server that was expanded, let it update its counts
+ let folder = this._rowMap[aIndex]._folder;
+ if (aExpandServer && folder.isServer)
+ folder.server.performExpand(msgWindow);
+ }
+ },
+
+ // We don't implement any of these at the moment
+ performAction: function ftv_performAction(aAction) {},
+ performActionOnCell: function ftv_performActionOnCell(aAction, aRow, aCol) {},
+ performActionOnRow: function ftv_performActionOnRow(aAction, aRow) {},
+ selectionChanged: function ftv_selectionChanged() {},
+ setCellText: function ftv_setCellText(aRow, aCol, aValue) {},
+ setCellValue: function ftv_setCellValue(aRow, aCol, aValue) {},
+ getCellValue: function ftv_getCellValue(aRow, aCol) {},
+ getColumnProperties: function ftv_getColumnProperties(aCol, aProps) {},
+ getImageSrc: function ftv_getImageSrc(aRow, aCol) {},
+ getProgressMode: function ftv_getProgressMode(aRow, aCol) {},
+ cycleCell: function ftv_cycleCell(aRow, aCol) {},
+ cycleHeader: function ftv_cycleHeader(aCol) {},
+
+ // ****************** End of nsITreeView implementation **************** //
+
+ //
+ // WARNING: Everything below this point is considered private. Touch at your
+ // own risk (Other than plugging into mapGenerators).
+
+ /**
+ * This is a javaascript map of which folders we had open, so that we can
+ * persist their state over-time. It is designed to be used as a JSON object.
+ */
+ _persistOpenMap: {},
+
+ _restoreOpenStates: function ftv__persistOpenStates() {
+ if (!(this.mode in this._persistOpenMap))
+ return;
+
+ let curLevel = 0;
+ let tree = this;
+ function openLevel() {
+ let goOn = false;
+ // We can't use a js iterator because we're changing the array as we go.
+ // So fallback on old trick of going backwards from the end, which
+ // doesn't care when you add things at the end.
+ for (let i = tree._rowMap.length - 1; i >= 0; i--) {
+ let row = tree._rowMap[i];
+ if (row.level != curLevel)
+ continue;
+
+ let map = tree._persistOpenMap[tree.mode];
+ if (map && map.indexOf(row.id) != -1) {
+ tree._toggleRow(i, false);
+ goOn = true;
+ }
+ }
+
+ // If we opened up any new kids, we need to check their level as well.
+ curLevel++;
+ if (goOn)
+ openLevel();
+ }
+ openLevel();
+ },
+
+ _tree: null,
+
+ /**
+ * An array of ftvItems, where each item corresponds to a row in the tree
+ */
+ _rowMap: null,
+
+ /**
+ * Completely discards the current tree and rebuilds it based on current
+ * settings
+ */
+ _rebuild: function ftv__rebuild() {
+ let oldCount = this._rowMap ? this._rowMap.length : null;
+ this._rowMap = this._mapGenerators[this.mode]();
+
+ if (oldCount !== null)
+ this._tree.rowCountChanged(0, this._rowMap.length - oldCount);
+
+ this._restoreOpenStates();
+ },
+
+ /**
+ * This object holds the functions that actually build the tree for each mode.
+ * When the tree must be rebuilt, we call the function here for the current
+ * mode. That function should return an array of ftvItems that should be
+ * displayed.
+ *
+ * Extensions should feel free to plug in here!
+ */
+ _mapGenerators: {
+
+ /**
+ * The all mode returns all folders, arranged in a hierarchy
+ */
+ all: function ftv__mg_all() {
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+ let acctMgr = Cc["@mozilla.org/messenger/account-manager;1"]
+ .getService(Ci.nsIMsgAccountManager);
+ let accounts = [a for each
+ (a in fixIterator(acctMgr.accounts, Ci.nsIMsgAccount))];
+ // Bug 41133 workaround
+ accounts = accounts.filter(function fix(a) { return a.incomingServer; });
+
+ // Don't show deferred pop accounts
+ accounts = accounts.filter(function isNotDeferred(a) {
+ let server = a.incomingServer;
+ return !(server instanceof Ci.nsIPop3IncomingServer &&
+ server.deferredToAccount);
+ });
+
+ function sortAccounts(a, b) {
+ if (a.key == acctMgr.defaultAccount.key)
+ return -1;
+ if (b.key == acctMgr.defaultAccount.key)
+ return 1;
+ let aIsNews = a.incomingServer.type == "nntp";
+ let bIsNews = b.incomingServer.type == "nntp";
+ if (aIsNews && !bIsNews)
+ return 1;
+ if (bIsNews && !aIsNews)
+ return -1;
+
+ let aIsLocal = a.incomingServer.type == "none";
+ let bIsLocal = b.incomingServer.type == "none";
+ if (aIsLocal && !bIsLocal)
+ return 1;
+ if (bIsLocal && !aIsLocal)
+ return -1;
+ return 0;
+ }
+ accounts.sort(sortAccounts);
+ // force each root folder to do its local subfolder discovery.
+ for each (acct in accounts)
+ acct.incomingServer.rootFolder.subFolders;
+
+ return [new ftvItem(acct.incomingServer.rootFolder)
+ for each (acct in accounts)];
+ },
+
+ /**
+ * The unread mode returns all folders that are not root-folders and that
+ * have unread items
+ */
+ unread: function ftv__mg_unread() {
+ let map = [];
+ for each (let folder in this._enumerateFolders) {
+ if (!folder.isServer && folder.getNumUnread(false) > 0)
+ map.push(new ftvItem(folder));
+ }
+
+ // There are no children in this view!
+ for each (let folder in map) {
+ folder.__defineGetter__("children", function() []);
+ folder.useServerName = true;
+ }
+ sortFolderItems(map);
+ return map;
+ },
+
+ /**
+ * The favorites mode returns all folders whose flags are set to include
+ * the favorite flag
+ */
+ favorite: function ftv__mg_unread() {
+ let faves = [];
+ for each (let folder in this._enumerateFolders) {
+ if (folder.flags & Components.interfaces.nsMsgFolderFlags.Favorite)
+ faves.push(new ftvItem(folder));
+ }
+
+ // There are no children in this view!
+ // And we want to display the account name to distinguish folders w/
+ // the same name.
+ for each (let folder in faves) {
+ folder.__defineGetter__("children", function() []);
+ folder.useServerName = true;
+ }
+ sortFolderItems(faves);
+ return faves;
+ },
+
+ /**
+ * The recent mode is a flat view of the 15 most recently used folders
+ */
+ recent: function ftv__mg_recent() {
+ const MAXRECENT = 15;
+
+ /**
+ * Sorts our folders by their recent-times.
+ */
+ function sorter(a, b) {
+ return Number(a.getStringProperty("MRUTime")) <
+ Number(b.getStringProperty("MRUTime"));
+ }
+
+ /**
+ * This function will add a folder to the recentFolders array if it
+ * is among the 15 most recent. If we exceed 15 folders, it will pop
+ * the oldest folder, ensuring that we end up with the right number
+ *
+ * @param aFolder the folder to check
+ */
+ let recentFolders = [];
+ let oldestTime = 0;
+ function addIfRecent(aFolder) {
+ let time = Number(aFolder.getStringProperty("MRUTime")) || 0;
+ if (time <= oldestTime)
+ return;
+
+ if (recentFolders.length == MAXRECENT) {
+ recentFolders.sort(sorter);
+ recentFolders.pop();
+ let oldestFolder = recentFolders[recentFolders.length - 1];
+ oldestTime = Number(oldestFolder.getStringProperty("MRUTime"));
+ }
+ recentFolders.push(aFolder);
+ }
+
+ for each (let folder in this._enumerateFolders)
+ addIfRecent(folder);
+
+ recentFolders.sort(sorter);
+
+ let items = [new ftvItem(f) for each (f in recentFolders)];
+
+ // There are no children in this view!
+ // And we want to display the account name to distinguish folders w/
+ // the same name.
+ for each (let folder in items) {
+ folder.__defineGetter__("children", function() []);
+ folder.useServerName = true;
+ }
+
+ return items;
+ },
+
+ /**
+ * This is a helper attribute that simply returns a flat list of all folders
+ */
+ get _enumerateFolders() {
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+ let folders = [];
+
+ /**
+ * This is a recursive function to add all subfolders to the array. It
+ * assumes that the passed in folder itself has already been added.
+ *
+ * @param aFolder the folder whose subfolders should be added
+ */
+ function addSubFolders(aFolder) {
+ for each (let f in fixIterator(aFolder.subFolders, Ci.nsIMsgFolder)) {
+ folders.push(f);
+ addSubFolders(f);
+ }
+ }
+
+ let acctMgr = Cc["@mozilla.org/messenger/account-manager;1"]
+ .getService(Ci.nsIMsgAccountManager);
+ for each (let acct in fixIterator(acctMgr.accounts, Ci.nsIMsgAccount)) {
+ // Skip deferred accounts
+ if (acct.incomingServer instanceof Ci.nsIPop3IncomingServer &&
+ acct.incomingServer.deferredToAccount)
+ continue;
+ folders.push(acct.incomingServer.rootFolder);
+ addSubFolders(acct.incomingServer.rootFolder);
+ }
+ return folders;
+ }
+ },
+
+ /**
+ * This is our implementation of nsIMsgFolderListener to watch for changes
+ */
+ OnItemAdded: function ftl_add(aParentItem, aItem) {
+ // Only rebuild if we didn't know about the folder
+ if (!(aItem instanceof Components.interfaces.nsIMsgFolder) ||
+ this.getIndexOfFolder(aItem))
+ return;
+
+ let parentIndex = this.getIndexOfFolder(aParentItem);
+ let parent = this._rowMap[parentIndex];
+
+ // Getting these children might have triggered our parent to build its
+ // array just now, in which case the added item will already exist
+ let children = parent.children;
+ var newChild;
+ for each (let child in children) {
+ if (child._folder == aItem) {
+ newChild = child;
+ break;
+ }
+ }
+ if (!newChild) {
+ newChild = new ftvItem(aItem);
+ parent.children.push(newChild);
+ newChild._level = parent._level + 1;
+ newChild._parent = parent;
+ sortFolderItems(parent._children);
+ }
+
+ // If the parent is open, add the new child into the folder pane. Otherwise,
+ // just invalidate the parent row.
+ if (parent.open) {
+ let newChildIndex;
+ let newChildNum = parent._children.indexOf(newChild);
+ // only child - go right after our parent
+ if (newChildNum == 0)
+ {
+ newChildIndex = Number(parentIndex) + 1
+ }
+ // if we're not the last child, insert ourselves before the next child.
+ else if (newChildNum < parent._children.length - 1)
+ {
+ newChildIndex = this.getIndexOfFolder(parent._children[Number(newChildNum) + 1]._folder);
+ }
+ // otherwise, go after the last child
+ else
+ {
+ let lastChild = parent._children[newChildNum - 1];
+ let lastChildIndex = this.getIndexOfFolder(lastChild._folder);
+ newChildIndex = Number(lastChildIndex) + 1;
+ while (newChildIndex < this.rowCount &&
+ this._rowMap[newChildIndex].level > this._rowMap[lastChildIndex].level)
+ newChildIndex++;
+ }
+ this._rowMap.splice(newChildIndex, 0, newChild);
+ this._tree.rowCountChanged(newChildIndex, 1);
+ } else {
+ this._tree.invalidateRow(parentIndex);
+ }
+ },
+
+ OnItemRemoved: function ftl_remove(aRDFParentItem, aItem) {
+ if (!(aItem instanceof Components.interfaces.nsIMsgFolder))
+ return;
+
+ let persistMapIndex = this._persistOpenMap[this.mode].indexOf(aItem.URI);
+ if (persistMapIndex != -1)
+ this._persistOpenMap[this.mode].splice(persistMapIndex, 1);
+
+ let index = this.getIndexOfFolder(aItem);
+ if (!index)
+ return;
+ // forget our parent's children; they'll get rebuilt
+ this._rowMap[index]._parent._children = null;
+ let kidCount = 1;
+ let walker = Number(index) + 1;
+ while (walker < this.rowCount &&
+ this._rowMap[walker].level > this._rowMap[index].level) {
+ walker++;
+ kidCount++;
+ }
+ this._rowMap.splice(index, kidCount);
+ this._tree.rowCountChanged(index, -1 * kidCount);
+ this._tree.invalidateRow(index);
+ },
+
+ OnItemPropertyChanged: function(aItem, aProperty, aOld, aNew) {},
+ OnItemIntPropertyChanged: function(aItem, aProperty, aOld, aNew) {
+ if (aItem instanceof Components.interfaces.nsIMsgFolder)
+ {
+ let index = this.getIndexOfFolder(aItem);
+ if (index)
+ this._tree.invalidateRow(index);
+ }
+ },
+
+ OnItemBoolPropertyChanged: function(aItem, aProperty, aOld, aNew) {},
+ OnItemUnicharPropertyChanged: function(aItem, aProperty, aOld, aNew) {},
+ OnItemPropertyFlagChanged: function(aItem, aProperty, aOld, aNew) {},
+ OnItemEvent: function(aFolder, aEvent) {
+ let index = this.getIndexOfFolder(aFolder);
+ if (index)
+ this._tree.invalidateRow(index);
+ },
+
+ // Believe it or not, this is the simplest way to watch for account creation
+ observe: function ftv_observe(aSubject, aTopic, aPrefName) {
+ if (aPrefName != "mail.accountmanager.accounts")
+ return;
+ let view = this;
+ function wrapper() {
+ view._rebuild();
+ }
+ setTimeout(wrapper, 0);
+ }
+};
+
+/**
+ * The ftvItem object represents a single row in the tree view. Because I'm lazy
+ * I'm just going to define the expected interface here. You are free to return
+ * an alternative object in a _mapGenerator, provided that it matches this
+ * interface:
+ *
+ * id (attribute) - a unique string for this object. Must persist over sessions
+ * text (attribute) - the text to display in the tree
+ * level (attribute) - the level in the tree to display the item at
+ * open (rw, attribute) - whether or not this container is open
+ * children (attribute) - an array of child items also conforming to this spec
+ * getProperties (function) - a call from getRowProperties or getCellProperties
+ * for this item will be passed into this function
+ * command (function) - this function will be called when the item is double-
+ * clicked
+ */
+function ftvItem(aFolder) {
+ this._folder = aFolder;
+ this._level = 0;
+}
+
+ftvItem.prototype = {
+ open: false,
+ useServerName: false,
+
+ get id() {
+ return this._folder.URI;
+ },
+ get text() {
+ let text = this._folder.abbreviatedName;
+ if (this.useServerName)
+ text += " - " + this._folder.server.prettyName;
+ // Yeah, we hard-code this, but so did the old code...
+ let unread = this._folder.getNumUnread(false);
+ if (unread > 0)
+ text += " (" + unread + ")";
+ return text;
+ },
+
+ get level() {
+ return this._level;
+ },
+
+ getProperties: function ftvItem_getProperties(aProps) {
+ // From folderUtils.jsm
+ setPropertyAtoms(this._folder, aProps);
+ },
+
+ command: function fti_command() {
+ MsgOpenNewWindowForFolder(this._folder.URI, -1 /* key */);
+ },
+
+ _children: null,
+ get children() {
+ const Ci = Components.interfaces;
+ // We're caching our child list to save perf.
+ if (!this._children) {
+ let iter = fixIterator(this._folder.subFolders, Ci.nsIMsgFolder);
+ this._children = [new ftvItem(f) for each (f in iter)];
+
+ sortFolderItems(this._children);
+ // Each child is a level one below us
+ for each (let child in this._children) {
+ child._level = this._level + 1;
+ child._parent = this;
+ }
+ }
+ return this._children;
+ }
+};
+
+/**
+ * This handles the invocation of most commmands dealing with folders, based off
+ * of the current selection, or a passed in folder.
+ */
+let gFolderTreeController = {
+ /**
+ * Opens the dialog to create a new sub-folder, and creates it if the user
+ * accepts
+ *
+ * @param aParent (optional) the parent for the new subfolder
+ */
+ newFolder: function ftc_newFolder(aParent) {
+ let folder = aParent || gFolderTreeView.getSelectedFolders()[0];
+
+ // Make sure we actually can create subfolders
+ if (!folder.canCreateSubfolders) {
+ // Check if we can create them at the root
+ if (folder.rootMsgFolder.canCreateSubfolders)
+ folder = folder.rootMsgFolder;
+ else // just use the default account
+ folder = GetDefaultAccountRootFolder();
+ }
+
+ let dualUseFolders = true;
+ if (folder.server instanceof Components.interfaces.nsIImapIncomingServer)
+ dualUseFolders = folder.server.dualUseFolders;
+
+ //xxx useless param
+ function newFolderCallback(aName, aFolder) {
+ if (aName)
+ folder.createSubfolder(aName, msgWindow);
+ }
+
+ window.openDialog("chrome://messenger/content/newFolderDialog.xul",
+ "", "chrome,titlebar,modal",
+ {folder: folder, dualUseFolders: dualUseFolders,
+ okCallback: newFolderCallback});
+ },
+
+ /**
+ * Opens the dialog to edit the properties for a folder
+ *
+ * @param aTabID (optional) the tab to show in the dialog
+ * @param aFolder (optional) the folder to edit, if not the selected one
+ */
+ editFolder: function ftc_editFolder(aTabID, aFolder) {
+ let folder = aFolder || gFolderTreeView.getSelectedFolders()[0];
+
+ // If this is actually a server, send it off to that controller
+ if (folder.isServer) {
+ MsgAccountManager(null);
+ return;
+ }
+
+ if (folder.flags & Components.interfaces.nsMsgFolderFlags.Virtual) {
+ this.editVirtualFolder(folder);
+ return;
+ }
+
+ let title = document.getElementById("bundle_messenger")
+ .getString("folderProperties");
+
+ //xxx useless param
+ function editFolderCallback(aNewName, aOldName, aUri) {
+ if (aNewName != aOldName)
+ folder.rename(aNewName, msgWindow);
+ }
+
+ //xxx useless param
+ function rebuildSummary(aFolder) {
+ let folder = aFolder || gFolderTreeView.getSelectedFolders()[0];
+ if (folder.locked) {
+ folder.throwAlertMsg("operationFailedFolderBusy", msgWindow);
+ return;
+ }
+ folder.getMsgDatabase(msgWindow).summaryValid = false;
+
+ var msgDB = folder.getMsgDatabase(msgWindow);
+ msgDB.summaryValid = false;
+ try {
+ folder.closeAndBackupFolderDB("");
+ }
+ catch(e) {
+ // In a failure, proceed anyway since we're dealing with problems
+ folder.ForceDBClosed();
+ }
+ // these lines will cause the thread pane to get reloaded when the
+ // download/reparse is finished. Only do this if the selected folder is
+ // loaded (i.e., not thru the context menu on a non-loaded folder).
+ if (folder == GetLoadedMsgFolder()) {
+ gRerootOnFolderLoad = true;
+ gCurrentFolderToReroot = folder.URI;
+ }
+ folder.updateFolder(msgWindow);
+ }
+
+ window.openDialog("chrome://messenger/content/folderProps.xul", "",
+ "chrome,centerscreen,titlebar,modal",
+ {folder: folder, serverType: folder.server.type,
+ msgWindow: msgWindow, title: title,
+ okCallback: editFolderCallback,
+ tabID: aTabID, name: folder.prettyName,
+ rebuildSummaryCallback: rebuildSummary});
+ },
+
+ /**
+ * Opens the dialog to rename a particular folder, and does the renaming if
+ * the user clicks OK in that dialog
+ *
+ * @param aFolder (optional) the folder to rename, if different than the
+ * currently selected one
+ */
+ renameFolder: function ftc_rename(aFolder) {
+ let folder = aFolder || gFolderTreeView.getSelectedFolders()[0];
+
+ //xxx no need for uri now
+ let controller = this;
+ function renameCallback(aName, aUri) {
+ if (aUri != folder.URI)
+ Components.utils.reportError("got back a different folder to rename!");
+
+ controller._resetThreadPane();
+ controller._tree.view.selection.clearSelection();
+
+ // Actually do the rename
+ folder.rename(aName, msgWindow);
+ }
+ window.openDialog("chrome://messenger/content/renameFolderDialog.xul",
+ "newFolder", "chrome,titlebar,modal",
+ {preselectedURI: folder.URI,
+ okCallback: renameCallback, name: folder.prettyName});
+ },
+
+ /**
+ * Deletes a folder from its parent
+ *
+ * @param aFolder (optional) the folder to delete, if not the selected one
+ */
+ deleteFolder: function ftc_delete(aFolder) {
+ const Ci = Components.interfaces;
+ let folder = aFolder || gFolderTreeView.getSelectedFolders()[0];
+
+ const FLAGS = Ci.nsMsgFolderFlags;
+ if (folder.flags & FLAGS.Inbox || folder.flags & FLAGS.Trash)
+ return;
+
+ let prefix = "@mozilla.org/messenger/protocol/info;1?type=";
+ let info = Components.classes[prefix + folder.server.type]
+ .getService(Ci.nsIMsgProtocolInfo);
+
+ // do not allow deletion of special folders on imap accounts
+ let bundle = document.getElementById("bundle_messenger");
+ if ((folder.flags & FLAGS.Sent || folder.flags & FLAGS.Draft ||
+ folder.flags & FLAGS.Template ||
+ ((folder.flags & FLAGS.Junk) && CanRenameDeleteJunkMail(folder))) &&
+ !info.specialFoldersDeletionAllowed) {
+ let specialFolderString = getSpecialFolderString(folder);
+ let errorMessage = bundle.getFormattedString("specialFolderDeletionErr",
+ [specialFolderString]);
+ let errorTitle = bundle.getString("specialFolderDeletionErrTitle");
+ Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Ci.nsIPromptService)
+ .alert(window, errorTitle, errorMessage);
+ return;
+ }
+
+ // handle news folder specially
+ if (isNewsURI(folder.URI) && confirmUnsubscribe(folder)) {
+ Unsubscribe(folder);
+ return;
+ }
+
+ let array = toXPCOMArray([folder], Ci.nsIMutableArray);
+ if (folder.flags & FLAGS.Virtual) {
+ if (gCurrentVirtualFolderUri == folder.URI)
+ gCurrentVirtualFolderUri = null;
+ }
+
+ folder.parent.deleteSubFolders(array, msgWindow);
+ },
+
+ /**
+ * Prompts the user to confirm and empties the trash for the selected folder
+ *
+ * @param aFolder (optional) the trash folder to empty
+ * @note Calling this function on a non-trash folder will result in strange
+ * behavior!
+ */
+ emptyTrash: function ftc_emptyTrash(aFolder) {
+ let folder = aFolder || gFolderTreeView.getSelectedFolders()[0];
+
+ if (this._checkConfirmationPrompt("emptyTrash"))
+ folder.emptyTrash(msgWindow, null);
+ },
+
+ /**
+ * Deletes everything (folders and messages) in this folder
+ *
+ * @param aFolder (optional) the folder to empty
+ */
+ emptyJunk: function ftc_emptyJunk(aFolder) {
+ const Ci = Components.interfaces;
+ let folder = aFolder || gFolderTreeView.getSelectedFolders()[0];
+
+ if (!this._checkConfirmationPrompt("emptyJunk"))
+ return;
+
+ // Delete any subfolders this folder might have
+ let iter = folder.subFolders;
+ while (iter.hasMoreElements())
+ folder.propagateDelete(iter.getNext(), true, msgWindow);
+
+ // Now delete the messages
+ let iter = fixIterator(folder.getMessages(msgWindow));
+ let messages = [m for each (m in iter)];
+ let children = toXPCOMArray(messages, Ci.nsIMutableArray);
+ folder.deleteMessages(children, msgWindow, true, false, null, false);
+ },
+
+ /**
+ * Compacts either a particular folder, or all folders
+ *
+ * @param aCompactAll - whether we should compact all folders
+ * @param aFolder (optional) the folder to compact, if different than the
+ * currently selected one
+ */
+ compactFolder: function ftc_compactFolder(aCompactAll, aFolder) {
+ let folder = aFolder || gFolderTreeView.getSelectedFolders()[0];
+
+ // Can't compact folders that have just been compacted
+ if (!folder.expungedBytes && !aCompactAll)
+ return;
+
+ // reset thread pane for non-imap folders.
+ if (!folder.server.type == "imap" && (gDBView.msgFolder == folder || aCompactAll))
+ this._resetThreadPane();
+
+ if (aCompactAll)
+ folder.compactAll(null, msgWindow, null, true, null);
+ else
+ folder.compact(null, msgWindow);
+ },
+
+ /**
+ * Opens the dialog to create a new virtual folder
+ *
+ * @param aName - the default name for the new folder
+ * @param aSearchTerms - the search terms associated with the folder
+ * @param aParent - the folder to run the search terms on
+ */
+ newVirtualFolder: function ftc_newVFolder(aName, aSearchTerms, aParent) {
+ let folder = aParent || gFolderTreeView.getSelectedFolders()[0];
+ let name = folder.prettyName;
+ if (aName)
+ name += "-" + aName;
+
+ window.openDialog("chrome://messenger/content/virtualFolderProperties.xul",
+ "", "chrome,titlebar,modal,centerscreen",
+ {folder: folder, searchTerms: aSearchTerms,
+ newFolderName: name});
+ },
+
+ editVirtualFolder: function ftc_editVirtualFolder(aFolder) {
+ let folder = aFolder || gFolderTreeView.getSelectedFolders()[0];
+
+ //xxx should pass the folder object
+ function editVirtualCallback(aURI) {
+ // we need to reload the folder if it is the currently loaded folder...
+ if (gMsgFolderSelected && aURI == gMsgFolderSelected.URI) {
+ gMsgFolderSelected = null;
+ FolderPaneSelectionChange();
+ }
+ }
+ window.openDialog("chrome://messenger/content/virtualFolderProperties.xul",
+ "", "chrome,titlebar,modal,centerscreen",
+ {folder: folder, editExistingFolder: true,
+ onOKCallback: editVirtualCallback,
+ msgWindow: msgWindow});
+ },
+
+ /**
+ * For certain folder commands, the thread pane needs to be invalidated, this
+ * takes care of doing so.
+ */
+ _resetThreadPane: function ftc_resetThreadPane() {
+ if (gDBView)
+ gCurrentlyDisplayedMessage = gDBView.currentlyDisplayedMessage;
+
+ ClearThreadPaneSelection();
+ ClearThreadPane();
+ ClearMessagePane();
+ },
+
+ /**
+ * Prompts for confirmation, if the user hasn't already chosen the "don't ask
+ * again" option.
+ *
+ * @param aCommand - the command to prompt for
+ */
+ _checkConfirmationPrompt: function ftc_confirm(aCommand) {
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+ let showPrompt = true;
+ try {
+ let pref = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ showPrompt = !pref.getBoolPref("mail." + aCommand + ".dontAskAgain");
+ } catch (ex) {}
+
+ if (showPrompt) {
+ let checkbox = {value:false};
+ let promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Ci.nsIPromptService);
+ let bundle = document.getElementById("bundle_messenger");
+ let ok = promptService.confirmEx(window,
+ bundle.getString(aCommand + "Title"),
+ bundle.getString(aCommand + "Message"),
+ promptService.STD_YES_NO_BUTTONS,
+ null, null, null,
+ bundle.getString(aCommand + "DontAsk"),
+ checkbox) == 0;
+ if (checkbox.value)
+ pref.setBoolPref("mail." + aCommand + ".dontAskAgain", true);
+ if (!ok)
+ return false;
+ }
+ return true;
+ },
+
+ get _tree() {
+ let tree = document.getElementById("folderTree");
+ delete this._tree;
+ return this._tree = tree;
+ }
+};
+
+/**
+ * Sorts the passed in array of folder items using the folder sort key
+ *
+ * @param aFolders - the array of ftvItems to sort.
+ */
+function sortFolderItems (aFtvItems) {
+ function sorter(a, b) {
+ let sortKey = a._folder.compareSortKeys(b._folder);
+ if (sortKey)
+ return sortKey;
+ return a.text.toLowerCase() > b.text.toLowerCase();
+ }
+ aFtvItems.sort(sorter);
+}
--- a/mail/base/content/mail3PaneWindowCommands.js
+++ b/mail/base/content/mail3PaneWindowCommands.js
@@ -39,17 +39,17 @@
#
# ***** END LICENSE BLOCK *****
var gMessengerBundle = document.getElementById("bundle_messenger");
// Controller object for folder pane
var FolderPaneController =
{
- supportsCommand: function(command)
+ supportsCommand: function(command)
{
switch ( command )
{
case "cmd_delete":
case "cmd_shiftDelete":
case "button_delete":
// Even if the folder pane has focus, don't do a folder delete if
// we have a selected message, but do a message delete instead.
@@ -114,20 +114,28 @@ var FolderPaneController =
// really disabled. kick out if the command should be disabled.
if (!this.isCommandEnabled(command)) return;
switch ( command )
{
case "cmd_delete":
case "cmd_shiftDelete":
case "button_delete":
- MsgDeleteFolder();
+ // Even if the folder pane has focus, don't do a folder delete if
+ // we have a selected message, but delete the message instead.
+ if (GetNumSelectedMessages() == 0)
+ gFolderTreeController.deleteFolder();
+ else
+ DefaultController.doCommand(command);
+ break;
+ case "cmd_deleteFolder":
+ gFolderTreeController.deleteFolder();
break;
case "button_compact":
- MsgCompactFolder(false);
+ gFolderTreeController.compactFolder(false);
break;
}
},
onEvent: function(event)
{
}
};
@@ -585,17 +593,17 @@ var DefaultController =
break;
case "cmd_expandAllThreads":
gDBView.doCommand(nsMsgViewCommandType.expandAll);
break;
case "cmd_collapseAllThreads":
gDBView.doCommand(nsMsgViewCommandType.collapseAll);
break;
case "cmd_renameFolder":
- MsgRenameFolder();
+ gFolderTreeController.renameFolder();
return;
case "cmd_sendUnsentMsgs":
// if offline, prompt for sendUnsentMessages
if (MailOfflineMgr.isOnline())
SendUnsentMessages();
else
MailOfflineMgr.goOnlineToSendMessages(msgWindow);
return;
@@ -616,17 +624,17 @@ var DefaultController =
return;
case "cmd_saveAsTemplate":
MsgSaveAsTemplate();
return;
case "cmd_viewPageSource":
ViewPageSource(GetSelectedMessages());
return;
case "cmd_setFolderCharset":
- MsgFolderProperties();
+ gFolderTreeController.editFolder();
return;
case "cmd_reload":
ReloadMessage();
return;
case "cmd_find":
// Make sure the message pane has focus before we start a find since we
// only support searching within the message body.
SetFocusMessagePane();
@@ -643,17 +651,17 @@ var DefaultController =
// only support searching within the message body.
SetFocusMessagePane();
document.getElementById("FindToolbar").onFindAgainCommand(true);
return;
case "cmd_markReadByDate":
MsgMarkReadByDate();
return;
case "cmd_properties":
- MsgFolderProperties();
+ gFolderTreeController.editFolder();
return;
case "cmd_search":
MsgSearchMessages();
return;
case "button_mark":
case "cmd_markAsRead":
MsgMarkMsgAsRead();
return;
@@ -690,23 +698,23 @@ var DefaultController =
return;
case "cmd_runJunkControls":
filterFolderForJunk();
return;
case "cmd_deleteJunk":
deleteJunkInFolder();
return;
case "cmd_emptyTrash":
- MsgEmptyTrash();
+ gFolderTreeController.emptyTrash();
return;
case "cmd_compactFolder":
- MsgCompactFolder(true);
+ gFolderTreeController.compactFolder(true);
return;
case "button_compact":
- MsgCompactFolder(false);
+ gFolderTreeController.compactFolder(false);
return;
case "cmd_downloadFlagged":
gDBView.doCommand(nsMsgViewCommandType.downloadFlaggedForOffline);
break;
case "cmd_downloadSelected":
gDBView.doCommand(nsMsgViewCommandType.downloadSelectedForOffline);
break;
case "cmd_synchronizeOffline":
@@ -749,22 +757,17 @@ var DefaultController =
goSetMenuValue('cmd_undo', 'valueDefault');
goSetMenuValue('cmd_redo', 'valueDefault');
}
}
};
function GetNumSelectedMessages()
{
- try {
- return gDBView.numSelected;
- }
- catch (ex) {
- return 0;
- }
+ return gDBView ? gDBView.numSelected : 0;
}
var gLastFocusedElement=null;
function FocusRingUpdate_Mail()
{
// WhichPaneHasFocus() uses on top.document.commandDispatcher.focusedElement
// to determine which pane has focus
@@ -789,39 +792,35 @@ function FocusRingUpdate_Mail()
// can we optimize
// and just update cmd_delete and button_delete?
UpdateMailToolbar("focus");
}
}
function WhichPaneHasFocus()
{
- var threadTree = GetThreadTree();
- var folderTree = GetFolderTree();
- var messagePane = GetMessagePane();
-
if (top.document.commandDispatcher.focusedWindow == GetMessagePaneFrame())
- return messagePane;
+ return GetMessagePane();
var currentNode = top.document.commandDispatcher.focusedElement;
while (currentNode) {
- if (currentNode === threadTree ||
- currentNode === folderTree ||
- currentNode === messagePane)
+ if (currentNode === document.getElementById('threadTree') ||
+ currentNode === document.getElementById("folderTree") ||
+ currentNode === document.getElementById("messagepanebox"))
return currentNode;
currentNode = currentNode.parentNode;
}
return null;
}
function SetupCommandUpdateHandlers()
{
// folder pane
- var widget = GetFolderTree();
+ var widget = document.getElementById("folderTree");
if ( widget )
widget.controllers.appendController(FolderPaneController);
top.controllers.insertControllerAt(0, DefaultController);
}
function UnloadCommandUpdateHandlers()
{
@@ -875,21 +874,20 @@ function IsRenameFolderEnabled()
{
var folders = GetSelectedMsgFolders();
return folders.length == 1 && folders[0].canRename &&
isCommandEnabled("cmd_renameFolder");
}
function IsCanSearchMessagesEnabled()
{
- var folderURI = GetSelectedFolderURI();
- if (!folderURI)
+ var folder = GetSelectedMsgFolders()[0];
+ if (!folder)
return false;
- var server = GetServer(folderURI);
- return server.canSearchMessages;
+ return folder.server.canSearchMessages;
}
function IsFolderCharsetEnabled()
{
return IsFolderSelected();
}
function IsPropertiesEnabled(command)
{
@@ -918,108 +916,54 @@ function IsFolderSelected()
return folders.length == 1 && !folders[0].isServer;
}
function IsMessageDisplayedInMessagePane()
{
return (!IsMessagePaneCollapsed() && (GetNumSelectedMessages() > 0));
}
-function MsgDeleteFolder()
-{
- var folderTree = GetFolderTree();
- var selectedFolders = GetSelectedMsgFolders();
- for (var i = 0; i < selectedFolders.length; i++)
- {
- var selectedFolder = selectedFolders[i];
- var specialFolder = getSpecialFolderString(selectedFolder);
- if (specialFolder != "Inbox" && specialFolder != "Trash")
- {
- var folder = selectedFolder.QueryInterface(Components.interfaces.nsIMsgFolder);
- if (folder.flags & MSG_FOLDER_FLAG_VIRTUAL)
- {
- if (gCurrentVirtualFolderUri == selectedFolder.URI)
- gCurrentVirtualFolderUri = null;
- var array = Components.classes["@mozilla.org/array;1"]
- .createInstance(Components.interfaces.nsIMutableArray);
- array.appendElement(folder, false);
- folder.parent.deleteSubFolders(array, msgWindow);
- continue;
- }
- var protocolInfo = Components.classes["@mozilla.org/messenger/protocol/info;1?type=" + selectedFolder.server.type].getService(Components.interfaces.nsIMsgProtocolInfo);
-
- // do not allow deletion of special folders on imap accounts
- if ((specialFolder == "Sent" ||
- specialFolder == "Drafts" ||
- specialFolder == "Templates" ||
- (specialFolder == "Junk" && !CanRenameDeleteJunkMail(GetSelectedFolderURI()))) &&
- !protocolInfo.specialFoldersDeletionAllowed)
- {
- var errorMessage = gMessengerBundle.getFormattedString("specialFolderDeletionErr",
- [specialFolder]);
- var specialFolderDeletionErrTitle = gMessengerBundle.getString("specialFolderDeletionErrTitle");
- var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
- .getService(Components.interfaces.nsIPromptService);
-
- promptService.alert(window, specialFolderDeletionErrTitle, errorMessage);
- continue;
- }
- else if (isNewsURI(selectedFolder.URI))
- {
- var unsubscribe = ConfirmUnsubscribe(selectedFolder);
- if (unsubscribe)
- UnSubscribe(selectedFolder);
- }
- else
- {
- var array = Components.classes["@mozilla.org/array;1"]
- .createInstance(Components.interfaces.nsIMutableArray);
- array.appendElement(selectedFolder, false);
- selectedFolder.parent.deleteSubFolders(array, msgWindow);
- }
- }
- }
-}
-
function SetFocusThreadPaneIfNotOnMessagePane()
{
var focusedElement = WhichPaneHasFocus();
if((focusedElement != GetThreadTree()) &&
(focusedElement != GetMessagePane()))
SetFocusThreadPane();
}
// 3pane related commands. Need to go in own file. Putting here for the moment.
function SwitchPaneFocus(event)
{
- var folderTree = GetFolderTree();
+ var folderTree = document.getElementById("folderTree");
var threadTree = GetThreadTree();
var messagePane = GetMessagePane();
+ var folderPaneCollapsed = document.getElementById("folderPaneBox").collapsed;
+
var focusedElement = WhichPaneHasFocus();
if (focusedElement == null) // focus not on one of the main three panes (probably toolbar)
focusedElement = threadTree; // treat as if on thread tree
if (event && event.shiftKey)
{
// Reverse traversal: Message -> Thread -> Folder -> Message
- if (focusedElement == threadTree && !IsFolderPaneCollapsed())
+ if (focusedElement == threadTree && !folderPaneCollapsed)
folderTree.focus();
else if (focusedElement != messagePane && !IsMessagePaneCollapsed())
SetFocusMessagePane();
else
threadTree.focus();
}
else
{
// Forward traversal: Folder -> Thread -> Message -> Folder
if (focusedElement == threadTree && !IsMessagePaneCollapsed())
SetFocusMessagePane();
- else if (focusedElement != folderTree && !IsFolderPaneCollapsed())
+ else if (focusedElement != folderTree && !folderPaneCollapsed)
folderTree.focus();
else
threadTree.focus();
}
}
function SetFocusThreadPane()
{
--- a/mail/base/content/mailCommands.js
+++ b/mail/base/content/mailCommands.js
@@ -69,18 +69,17 @@ function getBestIdentity(identities, opt
// normalize case on the optional hint to improve our chances of finding a match
optionalHint = optionalHint.toLowerCase();
var id;
// iterate over all of the identities
var tempID;
var lengthOfLongestMatchingEmail = 0;
- for (id = 0; id < identitiesCount; ++id) {
- tempID = identities.GetElementAt(id).QueryInterface(Components.interfaces.nsIMsgIdentity);
+ for each (var tempID in fixIterator(identities, nsIMsgIdentity)) {
if (optionalHint.indexOf(tempID.email.toLowerCase()) >= 0) {
// Be careful, the user can have several adresses with the same
// postfix e.g. aaa.bbb@ccc.ddd and bbb@ccc.ddd. Make sure we get the
// longest match.
if (tempID.email.length > lengthOfLongestMatchingEmail) {
identity = tempID;
lengthOfLongestMatchingEmail = tempID.email.length;
}
@@ -497,16 +496,14 @@ function deleteAllInFolder(commandName)
var iter = folder.subFolders;
while (iter.hasMoreElements())
folder.propagateDelete(iter.getNext(), true, msgWindow);
var children = Components.classes["@mozilla.org/array;1"]
.createInstance(Components.interfaces.nsIMutableArray);
// Delete messages.
- iter = folder.getMessages(msgWindow);
- while (iter.hasMoreElements()) {
- children.appendElement(iter.getNext(), false);
- }
+ var messages = [m for each (m in fixIterator(folder.getMessages(msgWindow)))];
+ var children = toXPCOMArray(messages, Components.interfaces.nsISupportsArray);
folder.deleteMessages(children, msgWindow, true, false, null, false);
children.clear();
}
--- a/mail/base/content/mailContextMenus.js
+++ b/mail/base/content/mailContextMenus.js
@@ -397,17 +397,17 @@ function OpenMessageByHeader(messageHead
window.openDialog("chrome://messenger/content/messageWindow.xul",
"_blank", "all,chrome,dialog=no,status,toolbar",
messageURI, folderURI, null);
}
else
{
if (msgWindow.openFolder != folderURI)
- SelectFolder(folderURI);
+ gFolderTreeView.selectFolder(folder);
var tree = null;
var wintype = document.documentElement.getAttribute('windowtype');
if (wintype != "mail:messageWindow")
{
tree = GetThreadTree();
tree.view.selection.clearSelection();
}
@@ -486,17 +486,17 @@ function CheckForMessageIdInFolder(folde
folder.setMsgDatabase(null);
}
return messageHeader;
}
function folderPaneOnPopupHiding()
{
- RestoreSelectionWithoutContentLoad(GetFolderTree());
+ RestoreSelectionWithoutContentLoad(document.getElementById("folderTree"));
}
function fillFolderPaneContextMenu()
{
var folders = GetSelectedMsgFolders();
if (!folders.length)
return false;
--- a/mail/base/content/mailWindow.js
+++ b/mail/base/content/mailWindow.js
@@ -47,22 +47,16 @@ var msgWindow;
var msgComposeService;
var accountManager;
var gMessengerBundle;
var gBrandBundle;
var gContextMenu;
-var accountManagerDataSource;
-var folderDataSource;
-var unreadFolderDataSource;
-var favoriteFoldersDataSource;
-var recentFoldersDataSource;
-
var gAccountCentralLoaded = true;
var gAutoSyncManager;
const nsIAutoSyncMgrListener = Components.interfaces.nsIAutoSyncMgrListener;
var gAutoSyncMonitor = {
logEnabled : false,
msgWindow : null,
@@ -264,30 +258,16 @@ function CreateMailWindowGlobals()
msgComposeService = Components.classes['@mozilla.org/messengercompose;1']
.getService(Components.interfaces.nsIMsgComposeService);
accountManager = Components.classes["@mozilla.org/messenger/account-manager;1"].getService(Components.interfaces.nsIMsgAccountManager);
gMessengerBundle = document.getElementById("bundle_messenger");
gBrandBundle = document.getElementById("bundle_brand");
-
- //Create datasources
- var prefix = "@mozilla.org/rdf/datasource;1?name=";
- accountManagerDataSource = Components.classes[prefix + "msgaccountmanager"]
- .getService();
- folderDataSource = Components.classes[prefix + "mailnewsfolders"]
- .getService();
- unreadFolderDataSource = Components.classes[prefix + "mailnewsunreadfolders"]
- .getService();
- favoriteFoldersDataSource = Components.classes[prefix + "mailnewsfavefolders"]
- .getService();
- recentFoldersDataSource = Components.classes[prefix + "mailnewsrecentfolders"]
- .getService();
-
gAutoSyncManager = Components.classes["@mozilla.org/imap/autosyncmgr;1"]
.getService(Components.interfaces.nsIAutoSyncManager);
gAutoSyncMonitor.msgWindow = msgWindow;
gAutoSyncManager.addListener(gAutoSyncMonitor);
}
function InitMsgWindow()
{
@@ -301,22 +281,16 @@ function InitMsgWindow()
.AddMsgWindow(msgWindow);
getBrowser().docShell.allowAuth = false;
msgWindow.rootDocShell.allowAuth = true;
msgWindow.rootDocShell.appType = Components.interfaces.nsIDocShell.APP_TYPE_MAIL;
// Ensure we don't load xul error pages into the main window
msgWindow.rootDocShell.useErrorPages = false;
}
-function AddDataSources()
-{
- accountManagerDataSource = accountManagerDataSource.QueryInterface(Components.interfaces.nsIRDFDataSource);
- folderDataSource = folderDataSource.QueryInterface(Components.interfaces.nsIRDFDataSource);
-}
-
// We're going to implement our status feedback for the mail window in JS now.
// the following contains the implementation of our status feedback object
function nsMsgStatusFeedback()
{
}
nsMsgStatusFeedback.prototype =
@@ -492,17 +466,17 @@ nsMsgWindowCommands.prototype =
if (iid.equals(Components.interfaces.nsIMsgWindowCommands) ||
iid.equals(Components.interfaces.nsISupports))
return this;
throw Components.results.NS_NOINTERFACE;
},
selectFolder: function(folderUri)
{
- SelectFolder(folderUri);
+ gFolderTreeView.selectFolder(GetMsgFolderFromUri(folderUri));
},
selectMessage: function(messageUri)
{
SelectMessage(messageUri);
},
clearMsgPane: function()
@@ -623,44 +597,37 @@ function ObserveDisplayDeckChange(event)
if (nowSelected != gCurrentDisplayDeckId)
{
if (nowSelected == "threadPaneBox")
ShowingThreadPane();
else
HidingThreadPane();
if (nowSelected == "accountCentralBox") {
- if (!IsFolderPaneCollapsed())
- GetFolderTree().focus();
+ if (!document.getElementById("folderPaneBox").collapsed)
+ document.getElementById("folderTree").focus();
gAccountCentralLoaded = true;
} else
gAccountCentralLoaded = false;
gCurrentDisplayDeckId = nowSelected;
}
}
// Given the server, open the twisty and the set the selection
// on inbox of that server.
// prompt if offline.
function OpenInboxForServer(server)
{
- try {
- ShowThreadPane();
- var inboxFolder = GetInboxFolder(server);
- SelectFolder(inboxFolder.URI);
+ ShowThreadPane();
+ gFolderTreeView.selectFolder(GetInboxFolder(server));
- if (MailOfflineMgr.isOnline() || MailOfflineMgr.getNewMail()) {
- if (server.type != "imap")
- GetMessagesForInboxOnServer(server);
- }
- }
- catch (ex) {
- dump("Error opening inbox for server -> " + ex + "\n");
- return;
+ if (MailOfflineMgr.isOnline() || MailOfflineMgr.getNewMail()) {
+ if (server.type != "imap")
+ GetMessagesForInboxOnServer(server);
}
}
function GetSearchSession()
{
if (("gSearchSession" in top) && gSearchSession)
return gSearchSession;
else
--- a/mail/base/content/mailWindowOverlay.js
+++ b/mail/base/content/mailWindowOverlay.js
@@ -738,18 +738,19 @@ function UpdateJunkToolbarButton()
function UpdateDeleteToolbarButton()
{
var deleteButtonDeck = document.getElementById("delete-deck");
if (!deleteButtonDeck)
return;
// Never show "Undelete" in the 3-pane for folders, when delete would
// apply to the selected folder.
- if (this.WhichPaneHasFocus && WhichPaneHasFocus() == GetFolderTree()
- && GetNumSelectedMessages() == 0)
+ if (this.WhichPaneHasFocus &&
+ WhichPaneHasFocus() == document.getElementById("folderTree") &&
+ GetNumSelectedMessages() == 0)
deleteButtonDeck.selectedIndex = 0;
else
deleteButtonDeck.selectedIndex = SelectedMessagesAreDeleted() ? 1 : 0;
}
function UpdateDeleteCommand()
{
var value = "value";
var uri = GetFirstSelectedMessage();
@@ -1185,22 +1186,22 @@ function MsgSaveAsTemplate()
}
function MsgOpenNewWindowForFolder(uri, key)
{
var uriToOpen = uri;
var keyToSelect = key;
if (!uriToOpen)
- // use GetSelectedFolderURI() to find out which message to open instead of
+ // use GetSelectedMsgFolders() to find out which folder to open instead of
// GetLoadedMsgFolder().URI. This is required because on a right-click, the
// currentIndex value will be different from the actual row that is
- // highlighted. GetSelectedFolderURI() will return the message that is
+ // highlighted. GetSelectedMsgFolders() will return the folder that is
// highlighted.
- uriToOpen = GetSelectedFolderURI();
+ uriToOpen = GetSelectedMsgFolders()[0].URI;
if (uriToOpen)
window.openDialog("chrome://messenger/content/", "_blank", "chrome,all,dialog=no", uriToOpen, keyToSelect);
}
function CreateToolbarTooltip(document, event)
{
event.stopPropagation();
@@ -1237,19 +1238,16 @@ function DisplayFolderAndThreadPane(show
document.getElementById("threadpane-splitter").collapsed = collapse;
document.getElementById("folderpane_splitter").collapsed = collapse;
document.getElementById("folderPaneBox").collapsed = collapse;
try {
document.getElementById("search-container").collapsed = collapse;
} catch (ex) {}
try {
- document.getElementById("folder-location-container").collapsed = collapse;
- } catch (ex) {}
- try {
document.getElementById("mailviews-container").collapsed = collapse;
} catch (ex) {}
}
/**
* mailTabType provides both "folder" and "message" tab modes. Under the
* previous TabOwner framework, their logic was separated into two 'classes'
* which called common helper methods and had similar boilerplate logic.
@@ -1321,25 +1319,26 @@ let mailTabType = {
aTab.messenger = messenger;
aTab.msgSelectedFolder = gMsgFolderSelected;
// Clear selection, because context clicking on a folder and opening in a
// new tab needs to have SelectFolder think the selection has changed.
// We also need to clear these globals to subvert the code that prevents
// folder loads when things haven't changed.
- GetFolderTree().view.selection.clearSelection();
- GetFolderTree().view.selection.currentIndex = -1;
+ var folderTree = document.getElementById("folderTree");
+ folderTree.view.selection.clearSelection();
+ folderTree.view.selection.currentIndex = -1;
gMsgFolderSelected = null;
msgWindow.openFolder = null;
// Clear thread pane selection - otherwise, the tree tries to impose the
- // current selection on the new view.
- gDBView = null; // Clear gDBView so we won't try to close it.
- SelectFolder(aTab.uriToOpen);
+ // the current selection on the new view.
+ gDBView = null; // clear gDBView so we won't try to close it.
+ gFolderTreeView.selectFolder(GetMsgFolderFromUri(aTab.uriToOpen));
aTab.dbView = gDBView;
},
closeTab: function(aTab) {
if (aTab.dbView)
aTab.dbView.close();
if (aTab.messenger)
aTab.messenger.setWindow(null, null);
@@ -1386,29 +1385,28 @@ let mailTabType = {
// restore globals
messenger = aTab.messenger;
gDBView = aTab.dbView;
gSearchSession = aTab.searchSession;
// restore view state if we had one
if (gDBView)
{
- var folderTree = GetFolderTree();
- var row = EnsureFolderIndex(folderTree.builderView, gDBView.msgFolder);
+ var row = gFolderTreeView.getIndexOfFolder(gDBView.msgFolder);
- var folderTreeBoxObj = folderTree.treeBoxObject;
- var folderTreeSelection = folderTreeBoxObj.view.selection;
+ var treeBoxObj = document.getElementById("folderTree").treeBoxObject;
+ var folderTreeSelection = treeBoxObj.view.selection;
// make sure that row.value is valid so that it doesn't mess up
// the call to ensureRowIsVisible().
if ((row >= 0) && !folderTreeSelection.isSelected(row))
{
gMsgFolderSelected = gDBView.msgFolder;
folderTreeSelection.selectEventsSuppressed = true;
folderTreeSelection.select(row);
- folderTreeBoxObj.ensureRowIsVisible(row);
+ treeBoxObj.ensureRowIsVisible(row);
folderTreeSelection.selectEventsSuppressed = false;
}
// This sets the thread pane tree's view to the gDBView view.
UpdateSortIndicators(gDBView.sortType, gDBView.sortOrder);
RerootThreadPane();
// We need to restore the selection to what it was when we switched away
// from this tab. We need to remember the selected keys, instead of the
// selected indices, since the view might have changed. But maybe the
@@ -1457,24 +1455,24 @@ window.addEventListener("load", function
);
function MsgOpenNewTabForFolder(uri, key)
{
var uriToOpen = uri;
var keyToSelect = key;
if (!uriToOpen)
- // Use GetSelectedFolderURI() to find out which message to open instead of
+ // Use GetSelectedMsgFolders() to find out which folder to open instead of
// GetLoadedMsgFolder().URI. This is required because on a right-click, the
// currentIndex value will be different from the actual row that is
- // highlighted. GetSelectedFolderURI() will return the message that is
+ // highlighted. GetSelectedMsgFolders() will return the message that is
// highlighted.
- uriToOpen = GetSelectedFolderURI();
-
- // Set up the first tab, which was previously invisible.
+ uriToOpen = GetSelectedMsgFolders()[0].URI;
+
+ // set up the first tab, which was previously invisible.
// This assumes the first tab is always a 3-pane ui, which
// may not be right, especially if we have the ability
// to persist your tab setup.
document.getElementById('tabmail').openTab("folder", uriToOpen);
}
function MsgOpenNewTabForMessage(messageKey, folderUri)
{
@@ -1610,24 +1608,24 @@ function MsgOpenNewWindowForMessage(mess
// Use GetFirstSelectedMessage() to find out which message to open
// instead of gDBView.getURIForViewIndex(currentIndex). This is
// required because on a right-click, the currentIndex value will be
// different from the actual row that is highlighted.
// GetFirstSelectedMessage() will return the message that is
// highlighted.
messageUri = GetFirstSelectedMessage();
- if (!folderUri)
- // Use GetSelectedFolderURI() to find out which message to open
- // instead of gDBView.getURIForViewIndex(currentIndex). This is
- // required because on a right-click, the currentIndex value will be
- // different from the actual row that is highlighted.
- // GetSelectedFolderURI() will return the message that is
- // highlighted.
- folderUri = GetSelectedFolderURI();
+ if (!folderUri)
+ // Use GetSelectedMsgFolders() to find out which message to open
+ // instead of gDBView.getURIForViewIndex(currentIndex). This is
+ // required because on a right-click, the currentIndex value will be
+ // different from the actual row that is highlighted.
+ // GetSelectedMsgFolders() will return the message that is
+ // highlighted.
+ folderUri = GetSelectedMsgFolders()[0].URI;
// be sure to pass in the current view....
if (messageUri && folderUri) {
window.openDialog("chrome://messenger/content/messageWindow.xul", "_blank",
"all,chrome,dialog=no,status,toolbar",
messageUri, folderUri, gDBView);
}
}
@@ -1664,17 +1662,17 @@ function MsgMarkReadByDate()
{
window.openDialog("chrome://messenger/content/markByDate.xul","",
"chrome,modal,titlebar,centerscreen",
GetLoadedMsgFolder());
}
function MsgMarkAllRead()
{
- var folder = GetMsgFolderFromUri(GetSelectedFolderURI(), true);
+ var folder = GetSelectedMsgFolders()[0];
if (folder)
folder.markAllMessagesRead();
}
function MsgFilters(emailAddress, folder)
{
if (!folder)
@@ -1907,17 +1905,20 @@ function IsGetNextNMessagesEnabled()
}
menuItem.setAttribute("hidden","true");
return false;
}
function IsCompactFolderEnabled()
{
- var server = GetServer(GetSelectedFolderURI());
+ var folder = GetSelectedMsgFolders()[0];
+ if (!folder)
+ return;
+ let server = folder.server;
return (server &&
(server.type != 'nntp') && // compact news folder is not supported
((server.type != 'imap') || server.canCompactFoldersOnServer) &&
isCommandEnabled("cmd_compactFolder")); // checks e.g. if IMAP is offline
}
function SetUpToolbarButtons(uri)
{
--- a/mail/base/content/mailWindowOverlay.xul
+++ b/mail/base/content/mailWindowOverlay.xul
@@ -636,65 +636,65 @@
<menuitem id="folderPaneContext-newsUnsubscribe"
label="&folderContextUnsubscribe.label;"
accesskey="&folderContextUnsubscribe.accesskey;"
oncommand="MsgUnsubscribe();"/>
<menuseparator id="folderPaneContext-sep1"/>
<menuitem id="folderPaneContext-new"
label="&folderContextNew.label;"
accesskey="&folderContextNew.accesskey;"
- oncommand="MsgNewFolder(NewFolder);"/>
+ oncommand="gFolderTreeController.newFolder();"/>
<menuitem id="folderPaneContext-remove"
label="&folderContextRemove.label;"
accesskey="&folderContextRemove.accesskey;"
- oncommand="MsgDeleteFolder();"/>
+ oncommand="gFolderTreeController.deleteFolder();"/>
<menuitem id="folderPaneContext-rename"
label="&folderContextRename.label;"
accesskey="&folderContextRename.accesskey;"
- oncommand="MsgRenameFolder();"/>
+ oncommand="gFolderTreeController.renameFolder();"/>
<menuseparator id="folderPaneContext-sep2"/>
<menuitem id="folderPaneContext-compact"
label="&folderContextCompact.label;"
accesskey="&folderContextCompact.accesskey;"
- oncommand="MsgCompactFolder(false);"/>
+ oncommand="gFolderTreeController.compactFolder(false);"/>
<menuitem id="folderPaneContext-markMailFolderAllRead"
label="&folderContextMarkMailFolderRead.label;"
accesskey="&folderContextMarkMailFolderRead.accesskey;"
oncommand="MsgMarkAllRead();"/>
<menuitem id="folderPaneContext-markNewsgroupAllRead"
label="&folderContextMarkNewsgroupRead.label;"
accesskey="&folderContextMarkNewsgroupRead.accesskey;"
oncommand="MsgMarkAllRead();"/>
<menuitem id="folderPaneContext-emptyTrash"
label="&folderContextEmptyTrash.label;"
accesskey="&folderContextEmptyTrash.accesskey;"
- oncommand="MsgEmptyTrash();"/>
+ oncommand="gFolderTreeController.emptyTrash();"/>
<menuitem id="folderPaneContext-emptyJunk"
label="&folderContextEmptyJunk.label;"
accesskey="&folderContextEmptyJunk.accesskey;"
- oncommand="deleteAllInFolder('emptyJunk');"/>
+ oncommand="gFolderTreeController.emptyJunk();"/>
<menuitem id="folderPaneContext-sendUnsentMessages"
label="&folderContextSendUnsentMessages.label;"
accesskey="&folderContextSendUnsentMessages.accesskey;"
oncommand="goDoCommand('cmd_sendUnsentMsgs')"/>
<menuseparator id="folderPaneContext-sep3"/>
<menuitem id="folderPaneContext-favoriteFolder"
type="checkbox"
label="&folderContextFavoriteFolder.label;"
accesskey="&folderContextFavoriteFolder.accesskey;"
check="false"
oncommand="ToggleFavoriteFolderFlag();"/>
<menuitem id="folderPaneContext-properties"
label="&folderContextProperties.label;"
accesskey="&folderContextProperties.accesskey;"
- oncommand="MsgFolderProperties();"/>
+ oncommand="gFolderTreeController.editFolder();"/>
<menuitem id="folderPaneContext-settings"
label="&folderContextSettings.label;"
accesskey="&folderContextSettings.accesskey;"
- oncommand="MsgFolderProperties();"/>
+ oncommand="gFolderTreeController.editFolder();"/>
</popup>
<popup id="messagePaneContext"
onpopupshowing="if (event.target != this) return true; gContextMenu = new nsContextMenu(this); return fillMessagePaneContextMenu();"
onpopuphiding="if (event.target == this) gContextMenu = null;">
<menuseparator id="messagePaneContext-sep-link"/>
<menuitem id="messagePaneContext-selectall"
label="&selectAllCmd.label;"
@@ -897,20 +897,20 @@
<menupopup id="menu_FilePopup" onpopupshowing="file_init();">
<menu id="menu_New">
<menupopup id="menu_NewPopup" onpopupshowing="menu_new_init();">
<menuitem id="newNewMsgCmd" label="&newNewMsgCmd.label;"
accesskey="&newNewMsgCmd.accesskey;"
key="key_newMessage2"
oncommand="MsgNewMessage(null);"/>
<menuitem id="menu_newFolder" label="&newFolderCmd.label;"
- oncommand="MsgNewFolder(NewFolder);"
+ oncommand="gFolderTreeController.newFolder();"
accesskey="&newFolderCmd.accesskey;"/>
<menuitem id="menu_newVirtualFolder" label="&newVirtualFolderCmd.label;"
- oncommand="MsgVirtualFolderProperties(false);"
+ oncommand="gFolderTreeController.newVirtualFolder();"
accesskey="&newVirtualFolderCmd.accesskey;"/>
<menuitem id="newAccountMenuItem" label="&newAccountCmd.label;"
accesskey="&newAccountCmd.accesskey;"
oncommand="MsgAccountWizard();"/>
<menuseparator id="newPopupMenuSeparator"/>
<menuitem id="menu_newCard"/>
</menupopup>
</menu>
@@ -1092,23 +1092,27 @@
<menuseparator id="viewMenuAfterPaneVerticalSeparator"/>
<menuitem id="menu_showMessage" type="checkbox" label="&showMessageCmd.label;" key="key_toggleMessagePane"
accesskey="&showMessageCmd.accesskey;" oncommand="MsgToggleMessagePane();"/>
</menupopup>
</menu>
<menu id="menu_FolderViews" label="&folderView.label;" accesskey="&folderView.accesskey;">
<menupopup id="menu_FolderViewsPopup" onpopupshowing="InitViewFolderViewsMenu(event)">
<menuitem id="menu_allFolders" label="&allFolders.label;" accesskey="&allFolders.accesskey;"
- type="radio" name="viewmessages" oncommand="loadFolderView(0);"/>
+ type="radio" name="viewmessages"
+ oncommand="gFolderTreeView.mode = 'all';"/>
<menuitem id="menu_unreadFolders" label="&unreadFolders.label;" accesskey="&unreadFolders.accesskey;"
- type="radio" name="viewmessages" oncommand="loadFolderView(1);"/>
+ type="radio" name="viewmessages"
+ oncommand="gFolderTreeView.mode = 'unread';"/>
<menuitem id="menu_favoriteFolders" label="&favoriteFolders.label;" accesskey="&favoriteFolders.accesskey;"
- type="radio" name="viewmessages" oncommand="loadFolderView(2);"/>
+ type="radio" name="viewmessages"
+ oncommand="gFolderTreeView.mode = 'favorite';"/>
<menuitem id="menu_recentFolders" label="&recentFolders.label;" accesskey="&recentFolders.accesskey;"
- type="radio" name="viewmessages" oncommand="loadFolderView(3);"/>
+ type="radio" name="viewmessages"
+ oncommand="gFolderTreeView.mode = 'recent';"/>
</menupopup>
</menu>
<menuseparator id="viewSortMenuSeparator"/>
<menu id="viewSortMenu" accesskey="&sortMenu.accesskey;" label="&sortMenu.label;">
<menupopup id="menu_viewSortPopup" onpopupshowing="InitViewSortByMenu()">
<menuitem id="sortByDateMenuitem" type="radio" name="sortby" label="&sortByDateCmd.label;" accesskey="&sortByDateCmd.accesskey;" oncommand="MsgSortThreadPane('byDate')"/>
<menuitem id="sortByReceivedMenuitem" type="radio" name="sortby" label="&sortByReceivedCmd.label;" accesskey="&sortByReceivedCmd.accesskey;" oncommand="MsgSortThreadPane('byReceived')"/>
<menuitem id="sortByFlagMenuitem" type="radio" name="sortby" label="&sortByStarCmd.label;" accesskey="&sortByStarCmd.accesskey;" oncommand="MsgSortThreadPane('byFlagged')"/>
@@ -1885,15 +1889,15 @@
<statusbarpanel class="statusbarpanel-progress"
id="quotaPanel" hidden="true">
<stack>
<progressmeter class="progressmeter-statusbar"
id="quotaMeter"
mode="normal"
value="0" />
<label id="quotaLabel"
- onclick="MsgFolderProperties('QuotaTab');" />
+ onclick="gFolderTreeController.editFolder('QuotaTab');" />
</stack>
</statusbarpanel>
</hbox>
</statusbar>
</overlay>
--- a/mail/base/content/messenger.xul
+++ b/mail/base/content/messenger.xul
@@ -79,19 +79,19 @@
<script type="application/x-javascript" src="chrome://messenger/content/shareglue.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/msgViewNavigation.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/mailWindow.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/msgMail3PaneWindow.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/mail3PaneWindowCommands.js"/>
<script type="application/x-javascript" src="chrome://global/content/contentAreaUtils.js"/>
<script type="application/x-javascript" src="chrome://communicator/content/nsContextMenu.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/mailContextMenus.js"/>
-<script type="application/x-javascript" src="chrome://messenger/content/messengerdnd.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/accountUtils.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/searchBar.js"/>
+<script type="application/x-javascript" src="chrome://messenger/content/folderPane.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/phishingDetector.js"/>
<script type="application/x-javascript" src="chrome://communicator/content/contentAreaClick.js"/>
<script type="application/x-javascript" src="chrome://global/content/nsDragAndDrop.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/editContactOverlay.js"/>
<!-- move needed functions into a single js file -->
<script type="application/x-javascript" src="chrome://messenger/content/threadPane.js"/>
@@ -179,143 +179,42 @@
<box id="mailContent" orient="vertical" flex="1">
<box id="messengerBox" orient="horizontal" flex="1" minheight="100" height="100" persist="height">
<vbox id="folderPaneBox" minwidth="100" width="200" persist="collapsed width">
<label id="folderColumnLabel" hidden="true" value="&folderColumn.label;"/>
<sidebarheader id="folderPaneHeader" align="center">
<label id="folderpane-title"/>
<spacer flex="1"/>
<toolbarbutton id="folderview-cycler-left" class="folderview-cycler"
- onclick="CycleFolderView(false);"/>
+ onclick="gFolderTreeView.cycleMode(false);"/>
<toolbarbutton id="folderview-cycler-right" class="folderview-cycler"
- onclick="CycleFolderView(true);"/>
+ onclick="gFolderTreeView.cycleMode(true);"/>
</sidebarheader>
- <tree id="folderTree"
- class="plain focusring"
+ <tree id="folderTree" class="plain focusring" flex="1"
treelines="true"
- flex="1"
+ hidecolumnpicker="true" persist="mode" mode="all"
context="folderPaneContext"
disableKeyNavigation="true"
- datasources="rdf:null"
- statedatasource="rdf:mailnewsfolders"
- flags="dont-build-content"
- ondraggesture="BeginDragFolderTree(event);"
+ ondraggesture="gFolderTreeView._onDragStart(event);"
+ ondragover="gFolderTreeView._onDragOver(event);"
+ ondblclick="gFolderTreeView.onDoubleClick(event);"
onselect="FolderPaneSelectionChange();">
- <treechildren tooltip="folderpopup"/>
- <template>
- <rule>
- <conditions>
- <content uri="?container"/>
- <member container="?container" child="?member" />
- <triple subject="?member" predicate="http://home.netscape.com/NC-rdf#IsDeferred" object="false"/>
- </conditions>
-
- <bindings>
- <binding subject="?member"
- predicate="http://home.netscape.com/NC-rdf#FolderTreeName"
- object="?folderTreeName" />
- <binding subject="?member"
- predicate="http://home.netscape.com/NC-rdf#FolderTreeName?sort=true"
- object="?folderTreeNameSort" />
- <binding subject="?member"
- predicate="http://home.netscape.com/NC-rdf#FolderTreeSimpleName"
- object="?folderTreeSimpleName" />
- <binding subject="?member"
- predicate="http://home.netscape.com/NC-rdf#SpecialFolder"
- object="?specialFolder" />
- <binding subject="?member"
- predicate="http://home.netscape.com/NC-rdf#BiffState"
- object="?biffState" />
- <binding subject="?member"
- predicate="http://home.netscape.com/NC-rdf#IsServer"
- object="?isServer" />
- <binding subject="?member"
- predicate="http://home.netscape.com/NC-rdf#NewMessages"
- object="?newMessages" />
- <binding subject="?member"
- predicate="http://home.netscape.com/NC-rdf#HasUnreadMessages"
- object="?hasUnreadMessages" />
- <binding subject="?member"
- predicate="http://home.netscape.com/NC-rdf#SubfoldersHaveUnreadMessages"
- object="?subfoldersHaveUnreadMessages" />
- <binding subject="?member"
- predicate="http://home.netscape.com/NC-rdf#IsSecure"
- object="?isSecure" />
- <binding subject="?member"
- predicate="http://home.netscape.com/NC-rdf#ServerType"
- object="?serverType" />
- <binding subject="?member"
- predicate="http://home.netscape.com/NC-rdf#NoSelect"
- object="?noSelect" />
- <binding subject="?member"
- predicate="http://home.netscape.com/NC-rdf#ImapShared"
- object="?imapShared" />
- <binding subject="?member"
- predicate="http://home.netscape.com/NC-rdf#TotalUnreadMessages"
- object="?unreadCount" />
- <binding subject="?member"
- predicate="http://home.netscape.com/NC-rdf#TotalMessages"
- object="?totalCount" />
- <binding subject="?member"
- predicate="http://home.netscape.com/NC-rdf#FolderSize"
- object="?folderSize" />
- </bindings>
-
- <action>
- <treechildren>
- <treeitem uri="?member">
- <treerow>
- <treecell id="folderNameCell"
- label="?folderTreeName"
- properties="specialFolder-?specialFolder biffState-?biffState isServer-?isServer newMessages-?newMessages hasUnreadMessages-?hasUnreadMessages subfoldersHaveUnreadMessages-?subfoldersHaveUnreadMessages isSecure-?isSecure serverType-?serverType noSelect-?noSelect imapShared-?imapShared"/>
- <treecell label="?unreadCount"
- properties="hasUnreadMessages-?hasUnreadMessages subfoldersHaveUnreadMessages-?subfoldersHaveUnreadMessages"/>
- <treecell label="?totalCount"
- properties="hasUnreadMessages-?hasUnreadMessages subfoldersHaveUnreadMessages-?subfoldersHaveUnreadMessages"/>
- <treecell label="?folderSize"/>
- </treerow>
- </treeitem>
- </treechildren>
- </action>
- </rule>
- </template>
<treecols>
<treecol id="folderNameCol"
flex="5"
crop="center"
persist="width"
hideheader="true"
ignoreincolumnpicker="true"
primary="true"
- sort="?folderTreeNameSort"
sortActive="true"
sortDirection="ascending"/>
- <splitter class="tree-splitter"/>
- <treecol id="folderUnreadCol"
- persist="hidden width"
- hidden="true"
- flex="1"
- label="&unreadColumn.label;"
- selectable="false"/>
- <splitter class="tree-splitter"/>
- <treecol id="folderTotalCol"
- persist="hidden width"
- hidden="true"
- flex="1"
- label="&totalColumn.label;"
- selectable="false"/>
- <splitter class="tree-splitter"/>
- <treecol id="folderSizeCol"
- persist="hidden width"
- hidden="true"
- flex="1"
- label="&folderSizeColumn.label;"
- selectable="false"/>
</treecols>
+ <treechildren tooltip="folderpopup"/>
</tree>
</vbox>
<splitter id="folderpane_splitter" collapse="before" persist="state"/>
<vbox flex="1">
<box orient="vertical" id="messagesBox" flex="1">
<deck id="displayDeck" flex="1" selectedIndex="0"
@@ -399,17 +298,17 @@
label="&totalColumn.label;" tooltiptext="&totalColumn.tooltip;"/>
<splitter class="tree-splitter"/>
<treecol id="locationCol" persist="width" flex="1" hidden="true" ignoreincolumnpicker="true"
label="&locationColumn.label;" tooltiptext="&locationColumn.tooltip;"/>
<splitter class="tree-splitter"/>
<treecol id="idCol" persist="hidden ordinal width" flex="1" hidden="true"
label="&idColumn.label;" tooltiptext="&idColumn.tooltip;"/>
</treecols>
- <treechildren ondraggesture="BeginDragThreadPane(event);"/>
+ <treechildren ondraggesture="threadPaneOnDragStart(event);"/>
</tree>
</hbox>
<!-- extensions may overlay in additional panels; don't assume that there are only 2! -->
</deck> <!-- displayDeck -->
<!-- if you change this id, please change GetThreadAndMessagePaneSplitter() and MsgToggleMessagePane() -->
<splitter id="threadpane-splitter" collapse="after" persist="state" collapsed="true"
onmouseup="OnMouseUpThreadAndMessagePaneSplitter()"/>
--- a/mail/base/content/msgMail3PaneWindow.js
+++ b/mail/base/content/msgMail3PaneWindow.js
@@ -38,33 +38,34 @@
# 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 *****
+Components.utils.import("resource://gre/modules/folderUtils.jsm");
+
/* This is where functions related to the 3 pane window are kept */
// from MailNewsTypes.h
const nsMsgKey_None = 0xFFFFFFFF;
const nsMsgViewIndex_None = 0xFFFFFFFF;
const kMailCheckOncePrefName = "mail.startup.enabledMailCheckOnce";
const kStandardPaneConfig = 0;
const kWidePaneConfig = 1;
const kVerticalPaneConfig = 2;
const kNumFolderViews = 4; // total number of folder views
// from nsMsgFolderFlags.h
const MSG_FOLDER_FLAG_ELIDED = 0x10;
-var gFolderTree;
var gMessagePane;
var gThreadTree;
var gSearchInput;
var gThreadAndMessagePaneSplitter = null;
var gUnreadCount = null;
var gTotalCount = null;
var gCurrentFolderView;
@@ -166,18 +167,16 @@ var folderListener = {
OnItemRemoved: function(parentItem, item) { },
OnItemPropertyChanged: function(item, property, oldValue, newValue) { },
OnItemIntPropertyChanged: function(item, property, oldValue, newValue) {
if (item == gMsgFolderSelected) {
if(property.toString() == "TotalMessages" || property.toString() == "TotalUnreadMessages") {
UpdateStatusMessageCounts(gMsgFolderSelected);
- item = item.QueryInterface(Components.interfaces.nsIRDFResource);
- UpdateFolderLocationPicker(item);
}
}
},
OnItemBoolPropertyChanged: function(item, property, oldValue, newValue) { },
OnItemUnicharPropertyChanged: function(item, property, oldValue, newValue) { },
OnItemPropertyFlagChanged: function(item, property, oldFlag, newFlag) { },
@@ -240,17 +239,18 @@ var folderListener = {
}
}
}
if (uri == gCurrentLoadingFolderURI) {
viewDebug("uri == current loading folder uri\n");
gCurrentLoadingFolderURI = "";
// Scroll to message for virtual folders is done in
// gSearchNotificationListener.OnSearchDone (see searchBar.js).
- if (!scrolled && !(gMsgFolderSelected.flags & MSG_FOLDER_FLAG_VIRTUAL))
+ if (!scrolled && gMsgFolderSelected &&
+ !(gMsgFolderSelected.flags & MSG_FOLDER_FLAG_VIRTUAL))
ScrollToMessageAfterFolderLoad(msgFolder);
SetBusyCursor(window, false);
}
// Folder loading is over,
// now issue quick search if there is an email address.
viewDebug("in folder loaded gVirtualFolderTerms = " + gVirtualFolderTerms + "\n");
viewDebug("in folder loaded gMsgFolderSelected = " + gMsgFolderSelected.URI + "\n");
if (rerootingFolder)
@@ -326,85 +326,24 @@ var folderListener = {
else if (eventType == "AboutToCompact") {
if (gDBView)
gCurrentlyDisplayedMessage = gDBView.currentlyDisplayedMessage;
}
else if (eventType == "CompactCompleted") {
HandleCompactCompleted(folder);
}
else if (eventType == "RenameCompleted") {
- SelectFolder(folder.URI);
+ gFolderTreeView.selectFolder(folder);
}
else if (eventType == "JunkStatusChanged") {
HandleJunkStatusChanged(folder);
}
}
}
-var folderObserver = {
- canDrop: function(index, orientation)
- {
- return CanDropOnFolderTree(index, orientation);
- },
-
- onDrop: function(row, orientation)
- {
- DropOnFolderTree(row, orientation);
- },
-
- onToggleOpenState: function(index)
- {
- var folderTree = GetFolderTree();
-
- // Nothing to do when collapsing an item.
- if (folderTree.view.isContainerOpen(index))
- return;
-
- var folderResource = GetFolderResource(folderTree, index);
-
- if (folderTree.view.getLevel(index) == 0)
- {
- // (Imap/Nntp/Pop) Account item.
-
- folderResource.QueryInterface(Components.interfaces.nsIMsgFolder)
- .server.performExpand(msgWindow);
- }
- else if (folderResource instanceof Components.interfaces.nsIMsgImapMailFolder)
- {
- // Imap message folder item.
-
- folderResource.performExpand(msgWindow);
- }
- },
-
- onCycleHeader: function(colID, elt)
- {
- },
-
- onCycleCell: function(row, colID)
- {
- },
-
- onSelectionChanged: function()
- {
- },
-
- onPerformAction: function(action)
- {
- },
-
- onPerformActionOnRow: function(action, row)
- {
- },
-
- onPerformActionOnCell: function(action, row, col)
- {
- }
-}
-
function HandleDeleteOrMoveMsgFailed(folder)
{
gDBView.onDeleteCompleted(false);
if(IsCurrentLoadedFolder(folder)) {
if(gNextMessageAfterDelete) {
gNextMessageAfterDelete = null;
gNextMessageViewIndexAfterDelete = -2;
}
@@ -600,17 +539,17 @@ function ServerContainsFolder(server, fo
if (!folder || !server)
return false;
return server.equals(folder.server);
}
function SelectServer(server)
{
- SelectFolder(server.rootFolder.URI);
+ gFolderTreeView.selectFolder(server.rootFolder);
}
// we have this incoming server listener in case we need to
// alter the folder pane selection when a server is removed
// or changed (currently, when the real username or real hostname change)
var gThreePaneIncomingServerListener = {
onServerLoaded: function(server) {},
onServerUnloaded: function(server) {
@@ -682,18 +621,16 @@ function UpdateMailPaneConfig(aMsgWindow
const MailPrefObserver = {
observe: function(subject, topic, prefName) {
// verify that we're changing the mail pane config pref
if (topic == "nsPref:changed")
{
if (prefName == "mail.pane_config.dynamic")
UpdateMailPaneConfig(true);
- else if (prefName == "mail.showFolderPaneColumns")
- UpdateFolderColumnVisibility();
}
}
};
/**
* Called on startup to initialize various parts of the main window
*/
function OnLoadMessenger()
@@ -720,34 +657,32 @@ function OnLoadMessenger()
}
document.documentElement.setAttribute("width", defaultWidth);
document.documentElement.setAttribute("height", defaultHeight);
}
gPrefBranch.QueryInterface(Components.interfaces.nsIPrefBranch2);
gPrefBranch.addObserver("mail.pane_config.dynamic", MailPrefObserver, false);
- gPrefBranch.addObserver("mail.showFolderPaneColumns", MailPrefObserver, false);
MailOfflineMgr.init();
CreateMailWindowGlobals();
GetMessagePane().collapsed = true;
// verifyAccounts returns true if the callback won't be called
if (verifyAccounts(LoadPostAccountWizard))
LoadPostAccountWizard();
window.addEventListener("AppCommand", HandleAppCommandEvent, true);
}
function LoadPostAccountWizard()
{
InitMsgWindow();
messenger.setWindow(window, msgWindow);
- InitializeDataSources();
InitPanes();
MigrateAttachmentDownloadStore();
MigrateJunkMailSettings();
MigrateFolderViews();
accountManager.setSpecialFolders();
accountManager.loadVirtualFolders();
accountManager.addIncomingServerListener(gThreePaneIncomingServerListener);
@@ -851,32 +786,30 @@ function HandleAppCommandEvent(evt)
}
function OnUnloadMessenger()
{
OnLeavingFolder(gMsgFolderSelected); // mark all read in current folder
accountManager.removeIncomingServerListener(gThreePaneIncomingServerListener);
gPrefBranch.QueryInterface(Components.interfaces.nsIPrefBranch2);
gPrefBranch.removeObserver("mail.pane_config.dynamic", MailPrefObserver);
- gPrefBranch.removeObserver("mail.showFolderPaneColumns", MailPrefObserver);
document.getElementById('tabmail').closeTabs();
gPhishingDetector.shutdown();
// FIX ME - later we will be able to use onload from the overlay
OnUnloadMsgHeaderPane();
UnloadPanes();
OnMailWindowUnload();
}
function loadStartFolder(initialUri)
{
- var folderTree = GetFolderTree();
var defaultServer = null;
var startFolder;
var isLoginAtStartUpEnabled = false;
//First get default account
try
{
if(initialUri)
@@ -921,17 +854,17 @@ function loadStartFolder(initialUri)
// Perform biff on the server to check for new mail, except for imap
// or a pop3 account that is deferred or deferred to,
// or the case where initialUri is non-null (non-startup)
if (!initialUri && isLoginAtStartUpEnabled && gLoadStartFolder
&& !defaultServer.isDeferredTo &&
defaultServer.rootFolder == defaultServer.rootMsgFolder)
defaultServer.performBiff(msgWindow);
- SelectFolder(startFolder.URI);
+ gFolderTreeView.selectFolder(startFolder);
}
catch(ex)
{
// this is the case where we're trying to auto-subscribe to a folder.
if (initialUri && !startFolder.parent)
{
// hack to force display of thread pane.
ShowingThreadPane();
@@ -963,175 +896,33 @@ function AddToSession()
.getService(Components.interfaces.nsIMsgMailSession);
var nsIFolderListener = Components.interfaces.nsIFolderListener;
var notifyFlags = nsIFolderListener.intPropertyChanged | nsIFolderListener.event;
mailSession.AddFolderListener(folderListener, notifyFlags);
}
function InitPanes()
{
- OnLoadFolderPane();
+ gFolderTreeView.load(document.getElementById("folderTree"),
+ "folderTree.json");
+ var folderTree = document.getElementById("folderTree");
+ folderTree.addEventListener("click",FolderPaneOnClick,true);
+ folderTree.addEventListener("mousedown",TreeOnMouseDown,true);
+
OnLoadThreadPane();
SetupCommandUpdateHandlers();
}
function UnloadPanes()
{
- OnUnloadFolderPane();
- UnloadCommandUpdateHandlers();
-}
-
-function InitializeDataSources()
-{
- //Setup common mailwindow stuff.
- AddDataSources();
-}
-
-function OnFolderUnreadColAttrModified(event)
-{
- if (event.attrName == "hidden")
- {
- var folderNameCell = document.getElementById("folderNameCell");
- var label = {"true": "?folderTreeName", "false": "?folderTreeSimpleName"};
- folderNameCell.setAttribute("label", label[event.newValue]);
- }
-}
-
-function UpdateFolderColumnVisibility()
-{
- var folderNameCol = document.getElementById("folderNameCol");
- var showColumns = gPrefBranch.getBoolPref("mail.showFolderPaneColumns");
- var folderUnreadCol = document.getElementById("folderUnreadCol");
- var folderColumnLabel = document.getElementById("folderColumnLabel");
- if (!showColumns)
- {
- var folderTotalCol = document.getElementById("folderTotalCol");
- var folderSizeCol = document.getElementById("folderSizeCol");
- folderUnreadCol.setAttribute("hidden", "true");
- folderTotalCol.setAttribute("hidden", "true");
- folderSizeCol.setAttribute("hidden", "true");
- folderNameCol.removeAttribute("label");
- }
- else
- {
- folderNameCol.setAttribute("label", folderColumnLabel.value);
- }
-
- folderNameCol.setAttribute("hideheader", showColumns ? "false" : "true");
var folderTree = document.getElementById("folderTree");
- folderTree.setAttribute("hidecolumnpicker", showColumns ? "false" : "true");
- var hidden = folderUnreadCol.getAttribute("hidden");
- if (hidden != "true")
- {
- var folderNameCell = document.getElementById("folderNameCell");
- folderNameCell.setAttribute("label", "?folderTreeSimpleName");
- }
-}
-
-// loadFolderViewForTree -- a helper routine split away from
-// loadFolderView.
-// returns a localized string corresponding to the name of the new view
-function loadFolderViewForTree(aNewFolderView, aFolderTree)
-{
- var folderPaneHeader = document.getElementById('folderpane-title');
- var database = aFolderTree.database;
- var nsIRDFDataSource = Components.interfaces.nsIRDFDataSource;
-
- // Each folder pane view has the following properties:
- // ref-> the ref attribute for the folderTree
- // label -> the UI label associated with the folder view
- // datasources -> array of the data sources associated with the view
-
- var folderViews = [
- {ref:"msgaccounts:/", label:"folderPaneHeader", dataSources: [accountManagerDataSource.QueryInterface(nsIRDFDataSource),
- folderDataSource.QueryInterface(nsIRDFDataSource)] },
- {ref:"mailnewsunreadfolders:/", label:"folderPaneHeader_unread", dataSources: [unreadFolderDataSource.QueryInterface(nsIRDFDataSource)]},
- {ref:"mailnewsfavefolders:/", label:"folderPaneHeader_favorites", dataSources: [favoriteFoldersDataSource.QueryInterface(nsIRDFDataSource)]},
- {ref:"mailnewsrecentfolders:/", label:"folderPaneHeader_recent", dataSources: [recentFoldersDataSource.QueryInterface(nsIRDFDataSource)]},
- ];
-
- // unload the current data sources
- if (gCurrentFolderView != undefined)
- {
- var dataSourcesToUnload = folderViews[gCurrentFolderView].dataSources;
- for (index in dataSourcesToUnload)
- database.RemoveDataSource(dataSourcesToUnload[index]);
- }
-
- // add the new data sources
- var dataSourcesToAdd = folderViews[aNewFolderView].dataSources;
- for (index in dataSourcesToAdd)
- {
- database.AddDataSource(dataSourcesToAdd[index]);
- }
-
- aFolderTree.setAttribute('ref', folderViews[aNewFolderView].ref);
- return gMessengerBundle.getString(folderViews[aNewFolderView].label);
-}
-
-function loadFolderView(aNewFolderView)
-{
- if (gCurrentFolderView && (gCurrentFolderView == aNewFolderView))
- return;
-
- var folderTree = GetFolderTree();
-
- var folderPaneHeader = document.getElementById('folderpane-title');
- var folderTree = GetFolderTree();
- var database = GetFolderDatasource();
-
- // load the folder view into the folder pane
- folderPaneHeader.value = loadFolderViewForTree(aNewFolderView, GetFolderTree());
-
- // if the folder location picker is visible, load the folder view into the location
- // picker as well.
- var folderLocationPicker = document.getElementById('folder-location-container');
- if (folderLocationPicker)
- loadFolderViewForTree(aNewFolderView, document.getElementById('folderLocationPopup').tree);
-
- // now reflect the new value back into prefs
- gPrefBranch.setIntPref('mail.ui.folderpane.view', gCurrentFolderView = aNewFolderView);
-}
-
-// we can cycle the folder view forward or backwards
-function CycleFolderView(aCycleForward)
-{
- // pass the call onto loadFolderView...
- var offset = aCycleForward ? 1 : kNumFolderViews - 1;
- loadFolderView((gCurrentFolderView + offset) % kNumFolderViews);
-}
-
-function OnLoadFolderPane()
-{
- // want to start up showing the folder pane, in case we shut down
- // with the 3-pane showing a single message.
- document.getElementById("folderpane_splitter").collapsed = false;
- document.getElementById("folderPaneBox").collapsed = false;
- UpdateFolderColumnVisibility();
- var folderUnreadCol = document.getElementById("folderUnreadCol");
- folderUnreadCol.addEventListener("DOMAttrModified", OnFolderUnreadColAttrModified, false);
-
- loadFolderView(gPrefBranch.getIntPref('mail.ui.folderpane.view'));
-
- var folderTree = GetFolderTree();
- var folderTreeBuilder = folderTree.builder.QueryInterface(Components.interfaces.nsIXULTreeBuilder);
- folderTreeBuilder.addObserver(folderObserver);
- folderTree.addEventListener("click",FolderPaneOnClick,true);
- folderTree.addEventListener("mousedown",TreeOnMouseDown,true);
-}
-
-function OnUnloadFolderPane()
-{
- var folderTree = GetFolderTree();
- var folderTreeBuilder = folderTree.builder.QueryInterface(Components.interfaces.nsIXULTreeBuilder);
- folderTreeBuilder.removeObserver(folderObserver);
- var folderUnreadCol = document.getElementById("folderUnreadCol");
- folderUnreadCol.removeEventListener("DOMAttrModified", OnFolderUnreadColAttrModified, false);
folderTree.removeEventListener("click",FolderPaneOnClick,true);
folderTree.removeEventListener("mousedown",TreeOnMouseDown,true);
+ gFolderTreeView.unload("folderTree.json");
+ UnloadCommandUpdateHandlers();
}
// builds prior to 12-08-2001 did not have the labels column
// in the thread pane. so if a user ran an old build, and then
// upgraded, they get the new column, and this causes problems.
// We're trying to avoid a similar problem to bug #96979.
// to work around this, we hide the column once, using the
// "mailnews.ui.threadpane.version" pref.
@@ -1193,81 +984,17 @@ function UpgradeThreadPaneUI()
}
}
function OnLoadThreadPane()
{
UpgradeThreadPaneUI();
}
-// folderLocationPickerOnLoad can be called multiple times
-// and it can be called when the location picker isn't in the toolbar
-function folderLocationPickerOnLoad()
-{
- var folderLocationPicker = document.getElementById('folder-location-container');
- if (!folderLocationPicker)
- return;
-
- var locationTree = document.getElementById('folderLocationPopup').tree;
- locationTree.database.AddDataSource(accountManagerDataSource);
- locationTree.database.AddDataSource(folderDataSource);
- locationTree.setAttribute("ref", "msgaccounts:/");
-}
-
-function OnLocationTreeSelect(menulist)
-{
- SelectFolder(menulist.getAttribute('uri'));
-}
-
-function UpdateFolderLocationPicker(resource)
-{
- var folderLocationPicker = document.getElementById('folder-location-container');
- if (!folderLocationPicker)
- return;
-
- var tree = GetFolderTree();
- var folders = document.getElementById('locationFolders');
- var properties = ['BiffState', 'NewMessages', 'HasUnreadMessages',
- 'SpecialFolder', 'IsServer', 'IsSecure', 'ServerType', 'NoSelect'];
- var folder = resource.QueryInterface(Components.interfaces.nsIMsgFolder);
- var label = folder.prettyName;
- folders.setAttribute("label", label);
-
- var rdfService = Components.classes["@mozilla.org/rdf/rdf-service;1"]
- .getService(Components.interfaces.nsIRDFService);
- function GetFolderAttribute(tree, source, attribute) {
- var property = rdfService.GetResource("http://home.netscape.com/NC-rdf#" + attribute);
- var target = tree.database.GetTarget(source, property, true);
- if (target)
- target = target.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
- return target;
- }
- for (var i in properties)
- {
- var property = properties[i];
- var value = GetFolderAttribute(tree, resource, property);
- folders.setAttribute(property, value);
- }
- folders.setAttribute('uri', resource.Value);
-}
-
-function GetFolderDatasource()
-{
- var folderTree = GetFolderTree();
- return folderTree.database;
-}
-
/* Functions for accessing particular parts of the window*/
-function GetFolderTree()
-{
- if (!gFolderTree)
- gFolderTree = document.getElementById("folderTree");
- return gFolderTree;
-}
-
function GetSearchInput()
{
if (!gSearchInput)
gSearchInput = document.getElementById("searchInput");
return gSearchInput;
}
function GetMessagePane()
@@ -1324,21 +1051,16 @@ function GetTotalCountElement()
return gTotalCount;
}
function IsMessagePaneCollapsed()
{
return GetMessagePane().collapsed;
}
-function IsFolderPaneCollapsed()
-{
- return GetFolderTree().parentNode.collapsed;
-}
-
function ClearThreadPaneSelection()
{
try {
if (gDBView) {
var treeView = gDBView.QueryInterface(Components.interfaces.nsITreeView);
var treeSelection = treeView.selection;
if (treeSelection)
treeSelection.clearSelection();
@@ -1406,89 +1128,37 @@ function TreeOnMouseDown(event)
}
function FolderPaneOnClick(event)
{
// we only care about button 0 (left click) events
if (event.button != 0)
return;
- var folderTree = GetFolderTree();
+ var folderTree = document.getElementById("folderTree");
var row = {};
var col = {};
var elt = {};
folderTree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, elt);
if (row.value == -1) {
if (event.originalTarget.localName == "treecol")
{
// clicking on the name column in the folder pane should not sort
event.stopPropagation();
}
}
else if ((event.originalTarget.localName == "slider") ||
(event.originalTarget.localName == "scrollbarbutton")) {
event.stopPropagation();
}
- else if ((event.detail == 2) && (elt.value != "twisty") &&
- (folderTree.view.getLevel(row.value) != 0)) {
- FolderPaneDoubleClick(row.value, event);
- }
-}
-
-function FolderPaneDoubleClick(folderIndex, event)
-{
- if (!gPrefBranch.getBoolPref("mailnews.reuse_thread_window2"))
- {
- var folderResource = GetFolderResource(GetFolderTree(), folderIndex);
- // Open a new msg window only if we are double clicking on
- // folders or newsgroups.
- MsgOpenNewWindowForFolder(folderResource.Value, -1 /* key */);
-
- // double clicking should not toggle the open / close state of the
- // folder. this will happen if we don't prevent the event from
- // bubbling to the default handler in tree.xml
- event.stopPropagation();
- }
-}
-
-function ChangeSelection(tree, newIndex)
-{
- if(newIndex >= 0)
- {
- tree.view.selection.select(newIndex);
- tree.treeBoxObject.ensureRowIsVisible(newIndex);
- }
-}
-
-//xxx this function should go away
-function GetSelectedFolders()
-{
- return GetSelectedMsgFolders();
}
function GetSelectedMsgFolders()
{
- var folderArray = [];
- var folderTree = GetFolderTree();
- var rangeCount = folderTree.view.selection.getRangeCount();
-
- for (var i = 0; i < rangeCount; i++)
- {
- var startIndex = {};
- var endIndex = {};
- folderTree.view.selection.getRangeAt(i, startIndex, endIndex);
- for (var j = startIndex.value; j <= endIndex.value; j++)
- {
- var folderResource = GetFolderResource(folderTree, j);
- var msgFolder = folderResource.QueryInterface(Components.interfaces.nsIMsgFolder);
- folderArray.push(msgFolder);
- }
- }
-
- return folderArray;
+ return gFolderTreeView.getSelectedFolders();
}
function GetFirstSelectedMessage()
{
try {
return gDBView.URIForFirstSelectedMessage;
}
catch (ex) {
@@ -1581,40 +1251,16 @@ function SetNextMessageAfterDelete()
// was moved or deleted from the folder.
gThreadPaneDeleteOrMoveOccurred = true;
gNextMessageViewIndexAfterDelete = treeSelection.currentIndex - NumberOfSelectedMessagesAboveCurrentIndex(treeSelection.currentIndex);
}
else
gNextMessageViewIndexAfterDelete = treeSelection.currentIndex;
}
-function EnsureFolderIndex(builder, msgFolder)
-{
- // try to get the index of the folder in the tree
- var index = builder.getIndexOfResource(msgFolder);
- if (index == -1) {
- // if we couldn't find the folder, open the parent
- builder.toggleOpenState(EnsureFolderIndex(builder, msgFolder.parent));
- index = builder.getIndexOfResource(msgFolder);
- }
- return index;
-}
-
-function SelectFolder(folderUri)
-{
- var folderTree = GetFolderTree();
- var msgFolder = GetMsgFolderFromUri(folderUri);
-
- // Before we can select a folder, we need to make sure it is "visible"
- // in the tree. To do that, we need to ensure that all its
- // ancestors are expanded.
- var folderIndex = EnsureFolderIndex(folderTree.builderView, msgFolder);
- ChangeSelection(folderTree, folderIndex);
-}
-
function SelectMessage(messageUri)
{
var msgHdr = messenger.messageServiceFromURI(messageUri).messageURIToMsgHdr(messageUri);
if (msgHdr)
gDBView.selectMsgByKey(msgHdr.messageKey);
}
function ReloadMessage()
@@ -1622,21 +1268,16 @@ function ReloadMessage()
gDBView.reloadMessage();
}
function GetDBView()
{
return gDBView;
}
-function GetFolderResource(tree, index)
-{
- return tree.builderView.getResourceAtIndex(index);
-}
-
function LoadNavigatedToMessage(msgHdr, folder, folderUri)
{
if (IsCurrentLoadedFolder(folder))
{
gDBView.selectMsgByKey(msgHdr.messageKey);
}
else
{
@@ -1716,34 +1357,22 @@ function MigrateAttachmentDownloadStore(
if (downloadsFile && downloadsFile.exists())
downloadsFile.remove(false);
// bump the version so we don't bother doing this again.
gPrefBranch.setIntPref("mail.attachment.store.version", 1);
}
}
-/**
- * Returns a string representation of a folder's specialFolder attribute.
- *
- * @param aFolder the folder whose specialFolder attribute to return
- */
-function getSpecialFolderString(aFolder) {
- if (aFolder.flags & 0x1000) // MSG_FOLDER_FLAG_INBOX
- return "Inbox";
- else if (aFolder.flags & 0x0100) // MSG_FOLDER_FLAG_TRASH
- return "Trash";
- else if (aFolder.flags & 0x0800) // MSG_FOLDER_FLAG_QUEUE
- return "Unsent Messages";
- else if (aFolder.flags & 0x0200) // MSG_FOLDER_FLAG_SENTMAIL
- return "Sent";
- else if (aFolder.flags & 0x0400) // MSG_FOLDER_FLAG_DRAFTS
- return "Drafts";
- else if (aFolder.flags & 0x400000) // MSG_FOLDER_FLAG_TEMPLATES
- return "Templates";
- else if (aFolder.flags & 0x40000000) // MSG_FOLDER_FLAG_JUNK
- return "Junk";
- else if (aFolder.flags & 0x0020) // MSG_FOLDER_FLAG_VIRTUAL
- return "Virtual";
- else
- return "none";
+function threadPaneOnDragStart(aEvent) {
+ if (aEvent.originalTarget.localName != "treechildren")
+ return;
+
+ var messages = GetSelectedMessages();
+ if (!messages)
+ return;
+
+ SetNextMessageAfterDelete()
+ for (let i in messages)
+ aEvent.dataTransfer.mozSetDataAt("text/x-moz-message", messages[i], i);
+ aEvent.dataTransfer.effectAllowed = "copyMove";
+ aEvent.dataTransfer.addElement(aEvent.originalTarget);
}
-
--- a/mail/base/content/widgetglue.js
+++ b/mail/base/content/widgetglue.js
@@ -50,152 +50,16 @@
//NOTE: gMessengerBundle must be defined and set or this Overlay won't work
function GetSelectedFolderURI()
{
var folders = GetSelectedMsgFolders();
return folders.length == 1 ? folders[0].URI : null;
}
-function MsgRenameFolder()
-{
- var folder = GetSelectedMsgFolders()[0];
-
- var dialog = window.openDialog(
- "chrome://messenger/content/renameFolderDialog.xul",
- "newFolder",
- "chrome,titlebar,modal",
- {preselectedURI: folder.URI,
- okCallback: RenameFolder, name: folder.prettyName});
-}
-
-function RenameFolder(name,uri)
-{
- var folderTree = GetFolderTree();
- if (folderTree)
- {
- if (uri && (uri != "") && name && (name != ""))
- {
- var selectedFolder = GetMsgFolderFromUri(uri);
- if (gDBView)
- gCurrentlyDisplayedMessage = gDBView.currentlyDisplayedMessage;
-
- ClearThreadPane();
- ClearMessagePane();
- folderTree.view.selection.clearSelection();
-
- try
- {
- selectedFolder.rename(name, msgWindow);
- }
- catch(e)
- {
- SelectFolder(selectedFolder.URI); //restore selection
- throw(e); // so that the dialog does not automatically close
- dump ("Exception : RenameFolder \n");
- }
- }
- else
- {
- dump("no name or nothing selected\n");
- }
- }
- else
- {
- dump("no folder tree\n");
- }
-}
-
-function MsgEmptyTrash()
-{
- var folders = GetSelectedMsgFolders();
- if (folders.length == 1)
- {
- if (!confirmToProceed('emptyTrash'))
- return;
-
- folders[0].emptyTrash(msgWindow, null);
- }
-}
-
-function MsgCompactFolder(isAll)
-{
- // Get the selected folders.
- var selectedFolders = GetSelectedMsgFolders();
-
- if (selectedFolders.length == 1)
- {
- var msgfolder = selectedFolders[0];
-
- if (msgfolder.server.type != "imap") //can be local only
- {
- var expungedBytes = msgfolder.expungedBytes;
-
- if (expungedBytes > 0)
- {
- if (gDBView)
- {
- gCurrentlyDisplayedMessage = gDBView.currentlyDisplayedMessage;
- if (gDBView.msgFolder == msgfolder || isAll)
- {
- ClearThreadPaneSelection();
- ClearThreadPane();
- ClearMessagePane();
- }
- }
- }
- else
- {
- if (!isAll) //you have one local folder with no room to compact
- return;
- }
- }
- if (isAll)
- msgfolder.compactAll(null, msgWindow, null, true, null);
- else
- msgfolder.compact(null, msgWindow);
- }
-}
-
-function openNewVirtualFolderDialogWithArgs(defaultViewName, aSearchTerms)
-{
- var folder = GetFirstSelectedMsgFolder();
- var folderTree = GetFolderTree();
- var name = folder.prettyName
- name += "-" + defaultViewName;
-
- var dialog = window.openDialog("chrome://messenger/content/virtualFolderProperties.xul", "",
- "chrome,titlebar,modal,centerscreen",
- {folder:folder,
- searchTerms:aSearchTerms,
- newFolderName:name});
-}
-
-function MsgVirtualFolderProperties(aEditExistingVFolder)
-{
- var preselectedFolder = GetFirstSelectedMsgFolder();
-
- var dialog = window.openDialog("chrome://messenger/content/virtualFolderProperties.xul", "",
- "chrome,titlebar,modal,centerscreen",
- {folder:preselectedFolder,
- editExistingFolder: aEditExistingVFolder,
- onOKCallback:onEditVirtualFolderPropertiesCallback,
- msgWindow:msgWindow});
-}
-
-function onEditVirtualFolderPropertiesCallback(aVirtualFolderURI)
-{
- // we need to reload the folder if it is the currently loaded folder...
- if (gMsgFolderSelected && aVirtualFolderURI == gMsgFolderSelected.URI)
- {
- gMsgFolderSelected = null; // force the folder pane to reload the virtual folder
- FolderPaneSelectionChange();
- }
-}
-
/**
@param tabID initial tab
*/
function MsgFolderProperties(tabID)
{
var preselectedURI = GetSelectedFolderURI();
var msgFolder = GetMsgFolderFromUri(preselectedURI, true);
--- a/mail/base/jar.mn
+++ b/mail/base/jar.mn
@@ -30,16 +30,17 @@ messenger.jar:
* content/messenger/FilterListDialog.xul (content/FilterListDialog.xul)
* content/messenger/FilterListDialog.js (content/FilterListDialog.js)
* content/messenger/subscribe.xul (content/subscribe.xul)
content/messenger/subscribe.js (content/subscribe.js)
* content/messenger/aboutDialog.xul (content/aboutDialog.xul)
* content/messenger/aboutDialog.js (content/aboutDialog.js)
* content/messenger/defaultClientDialog.xul (content/defaultClientDialog.xul)
* content/messenger/defaultClientDialog.js (content/defaultClientDialog.js)
+ content/messenger/folderPane.js (content/folderPane.js)
* content/messenger/msgSelectOffline.xul (content/msgSelectOffline.xul)
* content/messenger/msgPrintEngine.xul (content/msgPrintEngine.xul)
* content/messenger/searchBar.js (content/searchBar.js)
* content/messenger/phishingDetector.js (content/phishingDetector.js)
* content/messenger/mail-offline.js (content/mail-offline.js)
content/messenger/about-footer.png (content/about-footer.png)
content/messenger/aboutDialog.css (content/aboutDialog.css)
* content/messenger/credits.xhtml (content/credits.xhtml)
--- a/mail/locales/en-US/chrome/messenger/messenger.properties
+++ b/mail/locales/en-US/chrome/messenger/messenger.properties
@@ -410,19 +410,19 @@ updatesItem_defaultFallback=Check for Updates…
updatesItem_downloading=Downloading %S…
updatesItem_downloadingFallback=Downloading Update…
updatesItem_resume=Resume Downloading %S…
updatesItem_resumeFallback=Resume Downloading Update…
updatesItem_pending=Apply Downloaded Update Now…
updatesItem_pendingFallback=Apply Downloaded Update Now…
# Folder Pane Header Title Strings
-folderPaneHeader=All Folders
+folderPaneHeader_all=All Folders
folderPaneHeader_unread=Unread Folders
-folderPaneHeader_favorites=Favorite Folders
+folderPaneHeader_favorite=Favorite Folders
folderPaneHeader_recent=Recent Folders
# Copy / Move to Folder Again
#LOCALIZATION NOTE %1$S is the name of the folder we will move to. moveToFolderAgainAccessKey
# should have the same value as copyToFolderAgainAccessKey as they are the same menu item in the UI
# moveToFolderAgainAccessKey should also be a letter that occurs before %1$S
moveToFolderAgain=Move to "%1$S" Again
moveToFolderAgainAccessKey=t
--- a/mailnews/base/resources/content/mailWidgets.xml
+++ b/mailnews/base/resources/content/mailWidgets.xml
@@ -1853,22 +1853,31 @@
<content>
<children>
<xul:folderSummary/>
</children>
</content>
<handlers>
<handler event="popupshowing">
<![CDATA[
- var folderTree = GetFolderTree();
- var row = folderTree.treeBoxObject.getRowAt(event.clientX, event.clientY);
- if (row == -1)
- return false;
-
- var msgFolder = GetFolderResource(folderTree, row).QueryInterface(Components.interfaces.nsIMsgFolder);
+ var msgFolder;
+ // Allow for differences between Thunderbird and SeaMonkey folder pane
+ if ("gFolderTreeView" in window) {
+ msgFolder = gFolderTreeView.getFolderAtCoords(event.clientX,
+ event.clientY);
+ if (!msgFolder)
+ return false;
+ } else {
+ var folderTree = GetFolderTree();
+ var row = folderTree.treeBoxObject.getRowAt(event.clientX,
+ event.clientY);
+ if (row == -1)
+ return false;
+ msgFolder = GetFolderResource(folderTree, row).QueryInterface(Components.interfaces.nsIMsgFolder);
+ }
if (!msgFolder || msgFolder.isServer)
return false;
var asyncResults = {};
return document.getAnonymousNodes(this)[0].parseFolder(msgFolder, null, asyncResults);
]]>
</handler>
<handler event="popuphiding">
--- a/mailnews/base/resources/content/msgAccountCentral.js
+++ b/mailnews/base/resources/content/msgAccountCentral.js
@@ -216,19 +216,17 @@ function CollapseSectionSeparators(separ
var separator = document.getElementById(separatorId);
separator.setAttribute("collapsed", true);
}
}
// From the current folder tree, return the selected server
function GetSelectedServer()
{
- var folderURI = window.parent.GetSelectedFolderURI();
- var server = GetServer(folderURI);
- return server;
+ return GetSelectedMsgFolder().server;
}
// From the current folder tree, return the selected folder
function GetSelectedMsgFolder()
{
var folderURI = window.parent.GetSelectedFolderURI();
var msgFolder = window.parent.GetMsgFolderFromUri(folderURI, true);
return msgFolder;
--- a/mailnews/base/util/Makefile.in
+++ b/mailnews/base/util/Makefile.in
@@ -110,16 +110,20 @@ EXPORTS = \
nsMsgUtils.h \
nsMsgProtocol.h \
nsMsgMailNewsUrl.h \
nsMsgTxn.h \
nsMsgI18N.h \
nsImapMoveCoalescer.h \
$(NULL)
+EXTRA_JS_MODULES = \
+ folderUtils.jsm \
+ iteratorUtils.jsm
+
ifndef MOZ_STATIC_MAIL_BUILD
EXTRA_DSO_LDOPTS = \
$(LIBS_DIR) \
$(MOZDEPTH)/rdf/util/src/internal/$(LIB_PREFIX)rdfutil_s.$(LIB_SUFFIX) \
$(MOZ_UNICHARUTIL_LIBS) \
$(MOZ_COMPONENT_LIBS) \
$(NULL)
@@ -129,12 +133,10 @@ else
# we don't want the shared lib, but we want to force the creation of a static lib.
FORCE_STATIC_LIB = 1
endif
DEFINES += -D_IMPL_NS_MSG_BASE
-EXTRA_JS_MODULES = iteratorUtils.jsm
-
include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/mailnews/base/util/folderUtils.jsm
@@ -0,0 +1,149 @@
+/* ***** 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 folder 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 helper methods for dealing with nsIMsgFolders.
+ */
+
+var EXPORTED_SYMBOLS = ["setPropertyAtoms", "getSpecialFolderString",
+ "getFolderFromUri"];
+
+/**
+ * Returns a string representation of a folder's "special" type
+ *
+ * @param aFolder the folder whose special type should be returned
+ */
+function getSpecialFolderString(aFolder) {
+ const Ci = Components.interfaces;
+ if (aFolder.flags & Ci.nsMsgFolderFlags.Inbox)
+ return "Inbox";
+ if (aFolder.flags & Ci.nsMsgFolderFlags.Trash)
+ return "Trash";
+ if (aFolder.flags & Ci.nsMsgFolderFlags.Queue)
+ return "Unsent Messages";
+ if (aFolder.flags & Ci.nsMsgFolderFlags.SentMail)
+ return "Sent";
+ if (aFolder.flags & Ci.nsMsgFolderFlags.Drafts)
+ return "Drafts";
+ if (aFolder.flags & Ci.nsMsgFolderFlags.Templates)
+ return "Templates";
+ if (aFolder.flags & Ci.nsMsgFolderFlags.Junk)
+ return "Junk";
+ if (aFolder.flags & Ci.nsMsgFolderFlags.Virtual)
+ return "Virtual";
+ return "none";
+}
+
+/**
+ * This function is meant to be used with trees. It adds atoms for all of the
+ * common properties that css styling is based off of.
+ *
+ * @param aFolder the folder whose properties should be added as atoms
+ * @param aProperties the nsIProperties object where the atoms should be added
+ */
+function setPropertyAtoms(aFolder, aProperties) {
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+ let atomSvc = Cc["@mozilla.org/atom-service;1"].getService(Ci.nsIAtomService);
+ function addAtom(aName) {
+ aProperties.AppendElement(atomSvc.getAtom(aName));
+ }
+
+ addAtom("folderNameCol");
+ if (aFolder.getNumUnread(false) > 0)
+ addAtom("hasUnreadMessages-true");
+
+ if (aFolder.isServer)
+ addAtom("isServer-true");
+
+ addAtom("serverType-" + aFolder.server.type);
+
+ // set the SpecialFolder attribute
+ addAtom("specialFolder-" + getSpecialFolderString(aFolder));
+
+ // Now set the biffState
+ switch (aFolder.biffState) {
+ case Ci.nsIMsgFolder.nsMsgBiffState_NewMail:
+ addAtom("biffState-NewMail");
+ break;
+ case Ci.nsIMsgFolder.nsMsgBiffState_NoMail:
+ addAtom("biffState-NoMail");
+ break;
+ default:
+ addAtom("biffState-UnknownMail");
+ }
+
+ // We have to work a bit for IsSecure. This sucks
+ let server = aFolder.server;
+ if (server instanceof Ci.nsINntpIncomingServer)
+ addAtom("isSecure-" + server.isSecure);
+ else {
+ // If it's not a news-server, apparently we look at the socket type
+ let sock = server.socketType;
+ let isSecure = (sock == Ci.nsIMsgIncomingServer.alwaysUseTLS ||
+ sock == Ci.nsIMsgIncomingServer.useSSL);
+ addAtom("isSecure-" + isSecure);
+ }
+
+ if (aFolder.hasNewMessages)
+ addAtom("newMessages-true");
+
+ // We only set this if we're not a server
+ if (!aFolder.isServer) {
+ let shallowUnread = aFolder.getNumUnread(false);
+ // Make sure that shallowUnread isn't negative
+ if (shallowUnread < 0)
+ shallowUnread = 0;
+ let deepUnread = aFolder.getNumUnread(true);
+ if (deepUnread - shallowUnread > 0)
+ addAtom("subfoldersHaveUnreadMessages-true");
+ }
+
+ addAtom("noSelect-" + aFolder.noSelect);
+ addAtom("imapShared-" + aFolder.imapShared);
+}
+
+/**
+ * Returns a folder for a particular uri
+ *
+ * @param aUri the rdf uri of the folder to return
+ */
+function getFolderFromUri(aUri) {
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+ return Cc["@mozilla.org/mail/folder-lookup;1"].
+ getService(Ci.nsIFolderLookupService).getFolderById(aUri);
+}
--- a/mailnews/base/util/iteratorUtils.jsm
+++ b/mailnews/base/util/iteratorUtils.jsm
@@ -34,17 +34,17 @@
*
* ***** END LICENSE BLOCK ***** */
/**
* This file contains helper methods for dealing with XPCOM iterators (arrays
* and enumerators) in js-friendly ways.
*/
-var EXPORTED_SYMBOLS = ["fixIterator", "toXPCOMIterator"];
+var EXPORTED_SYMBOLS = ["fixIterator", "toXPCOMArray"];
let Ci = Components.interfaces;
/**
* This function will take a variety of xpcom iterators designed for c++ and
* turns them into a nice JavaScript style object that can be iterated using
* for...in
*
@@ -95,27 +95,27 @@ function fixIterator(aEnum, aIface) {
while (aEnum.hasMoreElements())
yield aEnum.getNext().QueryInterface(face);
}
return { __iterator__: iter };
}
}
/**
- * This function takes a javascript Array object and returns an xpcom iterator
+ * This function takes a javascript Array object and returns an xpcom array
* of the desired type. It will *not* work if you extend Array.prototype.
*
* @param aArray the array to convert to an xpcom array
* @param aInterface the type of xpcom array to convert
*
* @note The returned array is *not* dynamically updated. Changes made to the
* js array after a call to this function will not be reflected in the
* xpcom array.
*/
-function toXPCOMIterator(aArray, aInterface) {
+function toXPCOMArray(aArray, aInterface) {
if (aInterface.equals(Ci.nsISupportsArray)) {
let supportsArray = Components.classes["@mozilla.org/supports-array;1"]
.createInstance(Ci.nsISupportsArray);
for each (let item in aArray) {
supportsArray.AppendElement(item);
}
return supportsArray;
}