/thunderbird-14.0/comm-release/mail/base/content/folderPane.js
# · JavaScript · 2481 lines · 1589 code · 223 blank · 669 comment · 405 complexity · b1d54685875b754f09ab5f97902ffd30 MD5 · raw file
Large files are truncated click here to view the full file
- /* ***** 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):
- * Siddharth Agarwal <sid.bugzilla@gmail.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * 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:///modules/folderUtils.jsm");
- Components.utils.import("resource:///modules/iteratorUtils.jsm");
- Components.utils.import("resource:///modules/MailUtils.js");
- const kDefaultMode = "all";
- var nsMsgFolderFlags = Components.interfaces.nsMsgFolderFlags;
- /**
- * 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...
- */
- /**
- * An interface that needs to be implemented in order to add a new view to the
- * folder tree. For default behavior, it is recommended that implementers
- * subclass this interface instead of relying on duck typing.
- *
- * For implementation examples, see |gFolderTreeView._modes|. For how to
- * register this mode with |gFolderTreeView|, see
- * |gFolderTreeView.registerFolderTreeMode|.
- */
- let IFolderTreeMode = {
- /**
- * Generates the folder map for this mode.
- *
- * @param aFolderTreeView The gFolderTreeView for which this mode is being
- * activated.
- *
- * @returns An array containing ftvItem instances representing the top-level
- * folders in this view.
- */
- generateMap: function IFolderTreeMode_generateMap(aFolderTreeView) {
- return null;
- },
- /**
- * Given an nsIMsgFolder, returns its parent in the map. The default behaviour
- * is to return the folder's actual parent (aFolder.parent). Folder tree modes
- * may decide to override it.
- *
- * If the parent isn't easily computable given just the folder, you may
- * consider generating the entire ftvItem tree at once and using a map from
- * folders to ftvItems.
- *
- * @returns an nsIMsgFolder representing the parent of the folder in the view,
- * or null if the folder is a top-level folder in the map. It is expected
- * that the returned parent will have the given folder as one of its
- * children.
- * @note This function need not guarantee that either the folder or its parent
- * is actually in the view.
- */
- getParentOfFolder: function IFolderTreeMode_getParentOfFolder(aFolder) {
- return aFolder.parent;
- },
- /**
- * Given an nsIMsgDBHdr, returns the folder it is considered to be contained
- * in, in this mode. This is usually just the physical folder it is contained
- * in (aMsgHdr.folder), but some modes may decide to override this. For
- * example, combined views like Smart Folders return the smart inbox for any
- * messages in any inbox.
- *
- * The folder returned doesn't need to be in the view.
- * @returns The folder the message header is considered to be contained in, in
- * this mode. The returned folder may or may not actually be in the view
- * -- however, given a valid nsIMsgDBHdr, it is expected that a) a
- * non-null folder is returned, and that b) the folder that is returned
- * actually does contain the message header.
- */
- getFolderForMsgHdr: function IFolderTreeMode_getFolderForMsgHdr(aMsgHdr) {
- return aMsgHdr.folder;
- },
- /**
- * Notified when a folder is added. The default behavior is to add it as a
- * child of the parent item, but some views may decide to override this. For
- * example, combined views like Smart Folders add any new inbox as a child of
- * the smart inbox.
- *
- * @param aParent The parent of the folder that was added.
- * @param aFolder The folder that was added.
- */
- onFolderAdded: function IFolderTreeMode_onFolderAdded(aParent, aFolder) {
- gFolderTreeView.addFolder(aParent, aFolder);
- }
- };
- /**
- * 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 string;
- if (this.mode in this._modeDisplayNames)
- string = this._modeDisplayNames[this.mode];
- else {
- let key = "folderPaneModeHeader_" + this.mode;
- 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();
- try {
- this._persistOpenMap = JSON.parse(data);
- } catch (x) {
- Components.utils.reportError(
- document.getElementById("bundle_messenger")
- .getFormattedString("failedToReadFile", [aJSONFile, x]));
- }
- }
- }
- // 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);
- },
- /**
- * 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);
- if (aJSONFile) {
- // Write out our json file...
- let data = JSON.stringify(this._persistOpenMap);
- let file = Cc["@mozilla.org/file/directory_service;1"]
- .getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
- file.append(aJSONFile);
- let foStream = Cc["@mozilla.org/network/safe-file-output-stream;1"]
- .createInstance(Ci.nsIFileOutputStream);
- foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0);
- // safe-file-output-stream appears to throw an error if it doesn't write everything at once
- // so we won't worry about looping to deal with partial writes
- foStream.write(data, data.length);
- foStream.QueryInterface(Ci.nsISafeOutputStream).finish();
- foStream.close();
- }
- },
- /**
- * Extensions can use this function to add a new mode to the folder pane.
- *
- * @param aCommonName an internal name to identify this mode. Must be unique
- * @param aMode An implementation of |IFolderTreeMode| for this mode.
- * @param aDisplayName a localized name for this mode
- */
- registerFolderTreeMode: function ftv_registerFolderTreeMode(aCommonName,
- aMode,
- aDisplayName) {
- this._modeNames.push(aCommonName);
- this._modes[aCommonName] = aMode;
- this._modeDisplayNames[aCommonName] = aDisplayName;
- },
- /**
- * Unregisters a previously registered mode. Since common-names must be unique
- * this is all that need be provided to unregister.
- * @param aCommonName the common-name with which the mode was previously
- * registered
- */
- unregisterFolderTreeMode: function ftv_unregisterFolderTreeMode(aCommonName) {
- this._modeNames.splice(this._modeNames.indexOf(aCommonName), 1);
- delete this._modes[aCommonName];
- delete this._modeDisplayNames[aCommonName];
- if (this._mode == aCommonName)
- this.mode = kDefaultMode;
- },
-
- /**
- * Retrieves a specific mode object
- * @param aCommonName the common-name with which the mode was previously
- * registered
- */
- getFolderTreeMode: function ftv_getFolderTreeMode(aCommonName) {
- return this._modes[aCommonName];
- },
- /**
- * 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 (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);
- if (row in gFolderTreeView._rowMap)
- return gFolderTreeView._rowMap[row]._folder;
- return null;
- },
- /**
- * A string representation for the current display-mode. Each value here must
- * correspond to an entry in _modes
- */
- _mode: null,
- get mode() {
- if (!this._mode) {
- this._mode = this._treeElement.getAttribute("mode");
- // this can happen when an extension is removed
- if (!(this._mode in this._modes))
- this._mode = kDefaultMode;
- }
- return this._mode;
- },
- set mode(aMode) {
- this._mode = aMode;
- let string;
- if (this._mode in this._modeDisplayNames)
- string = this._modeDisplayNames[this._mode];
- else {
- let key = "folderPaneModeHeader_" + aMode;
- string = document.getElementById("bundle_messenger").getString(key);
- }
- 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 aFolder the nsIMsgFolder to select
- * @param [aForceSelect] Whether we should switch to the default mode to
- * select the folder in case we didn't find the folder in the current
- * view. Defaults to false.
- * @returns true if the folder selection was successful, false if it failed
- * (probably because the folder isn't in the view at all)
- */
- selectFolder: function ftv_selectFolder(aFolder, aForceSelect) {
- // "this" inside the nested function refers to the function...
- // Also note that openIfNot is recursive.
- let tree = this;
- let folderTreeMode = this._modes[this._mode];
- function openIfNot(aFolderToOpen) {
- let index = tree.getIndexOfFolder(aFolderToOpen);
- if (index != null) {
- if (!tree._rowMap[index].open)
- tree._toggleRow(index, false);
- return true;
- }
- // not found, so open the parent
- let parent = folderTreeMode.getParentOfFolder(aFolderToOpen);
- if (parent && openIfNot(parent)) {
- // now our parent is open, so we can open ourselves
- index = tree.getIndexOfFolder(aFolderToOpen);
- if (index != null) {
- tree._toggleRow(index, false);
- return true;
- }
- }
- // No way we can find the folder now.
- return false;
- }
- let parent = folderTreeMode.getParentOfFolder(aFolder);
- if (parent)
- openIfNot(parent);
- let folderIndex = tree.getIndexOfFolder(aFolder);
- if (folderIndex == null) {
- if (aForceSelect) {
- // Switch to the default mode. The assumption here is that the default
- // mode can display every folder
- this.mode = kDefaultMode;
- // We don't want to get stuck in an infinite recursion, so pass in false
- return this.selectFolder(aFolder, false);
- }
- return false;
- }
- this.selection.select(folderIndex);
- this._treeElement.treeBoxObject.ensureRowIsVisible(folderIndex);
- return true;
- },
- /**
- * Returns the index of a folder in the current display.
- *
- * @param aFolder the folder whose index should be returned.
- * @returns The index of the folder in the view (a number).
- * @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 each (let [iRow, row] in Iterator(this._rowMap)) {
- if (row.id == aFolder.URI)
- return iRow;
- }
- return null;
- },
- /**
- * Returns the folder for an index in the current display.
- *
- * @param aIndex the index for which the folder should be returned.
- * @note If the index is out of bounds, this function returns null.
- */
- getFolderForIndex: function ftv_getFolderForIndex(aIndex) {
- if (aIndex < 0 || aIndex >= this._rowMap.length)
- return null;
- return this._rowMap[aIndex]._folder;
- },
- /**
- * Returns the parent of a folder in the current view. This may be, but is not
- * necessarily, the actual parent of the folder (aFolder.parent). In
- * particular, in the smart view, special folders are usually children of the
- * smart folder of that kind.
- *
- * @param aFolder The folder to get the parent of.
- * @returns The parent of the folder, or null if the parent wasn't found.
- * @note This function does not guarantee that either the folder or its parent
- * is actually in the view.
- */
- getParentOfFolder: function ftv_getParentOfFolder(aFolder) {
- return this._modes[this._mode].getParentOfFolder(aFolder);
- },
- /**
- * Given an nsIMsgDBHdr, returns the folder it is considered to be contained
- * in, in the current mode. This is usually, but not necessarily, the actual
- * folder the message is in (aMsgHdr.folder). For more details, see
- * |IFolderTreeMode.getFolderForMsgHdr|.
- */
- getFolderForMsgHdr: function ftv_getFolderForMsgHdr(aMsgHdr) {
- return this._modes[this._mode].getFolderForMsgHdr(aMsgHdr);
- },
- /**
- * Returns the |ftvItem| for an index in the current display. Intended for use
- * by folder tree mode implementers.
- *
- * @param aIndex The index for which the ftvItem should be returned.
- * @note If the index is out of bounds, this function returns null.
- */
- getFTVItemForIndex: function ftv_getFTVItemForIndex(aIndex) {
- return this._rowMap[aIndex];
- },
- /**
- * Returns an array of nsIMsgFolders corresponding to the current selection
- * in the tree
- */
- getSelectedFolders: function ftv_getSelectedFolders() {
- let selection = this.selection;
- if (!selection)
- return [];
- let folderArray = [];
- 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++) {
- if (j < this._rowMap.length)
- folderArray.push(this._rowMap[j]._folder);
- }
- }
- return folderArray;
- },
- /**
- * Adds a new child |ftvItem| to the given parent |ftvItem|. Intended for use
- * by folder tree mode implementers.
- *
- * @param aParentItem The parent ftvItem. It is assumed that this is visible
- * in the view.
- * @param aParentIndex The index of the parent ftvItem in the view.
- * @param aItem The item to add.
- */
- addChildItem: function ftv_addChildItem(aParentItem, aParentIndex, aItem) {
- this._addChildToView(aParentItem, aParentIndex, aItem);
- },
- // ****************** Start of nsITreeView implementation **************** //
- get rowCount() {
- return this._rowMap.length;
- },
- /**
- * drag drop interfaces
- */
- canDrop: function ftv_canDrop(aRow, aOrientation) {
- const Cc = Components.classes;
- const Ci = Components.interfaces;
- let targetFolder = gFolderTreeView._rowMap[aRow]._folder;
- if (!targetFolder)
- return false;
- let dt = this._currentTransfer;
- let types = dt.mozTypesAt(0);
- if (Array.indexOf(types, "text/x-moz-message") != -1) {
- if (aOrientation != Ci.nsITreeView.DROP_ON)
- return false;
- // Don't allow drop onto server itself.
- if (targetFolder.isServer)
- return false;
- // Don't allow drop into a folder that cannot take messages.
- if (!targetFolder.canFileMessages)
- return false;
- let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
- for (let i = 0; i < dt.mozItemCount; i++) {
- let msgHdr = messenger.msgHdrFromURI(dt.mozGetDataAt("text/x-moz-message", i));
- // Don't allow drop onto original folder.
- if (msgHdr.folder == targetFolder)
- return false;
- }
- return true;
- }
- else if (Array.indexOf(types, "text/x-moz-folder") != -1) {
- if (aOrientation != Ci.nsITreeView.DROP_ON)
- return false;
- // If cannot create subfolders then don't allow drop here.
- if (!targetFolder.canCreateSubfolders)
- return false;
- for (let i = 0; i < dt.mozItemCount; i++) {
- let folder = dt.mozGetDataAt("text/x-moz-folder", i)
- .QueryInterface(Ci.nsIMsgFolder);
- // Don't allow to drop on itself.
- if (targetFolder == folder)
- return false;
- // Don't copy within same server.
- if ((folder.server == targetFolder.server) &&
- (dt.dropEffect == 'copy'))
- return false;
- // Don't allow immediate child to be dropped onto its parent.
- if (targetFolder == folder.parent)
- return false;
- // Don't allow dragging of virtual folders across accounts.
- if ((folder.flags & nsMsgFolderFlags.Virtual) &&
- folder.server != targetFolder.server)
- return false;
- // Don't allow parent to be dropped on its ancestors.
- if (folder.isAncestorOf(targetFolder))
- return false;
- // If there is a folder that can't be renamed, don't allow it to be
- // dropped if it is not to "Local Folders" or is to the same account.
- if (!folder.canRename && (targetFolder.server.type != "none" ||
- folder.server == targetFolder.server))
- return false;
- }
- return true;
- }
- else if (Array.indexOf(types, "text/x-moz-newsfolder") != -1) {
- // Don't allow dragging onto element.
- if (aOrientation == Ci.nsITreeView.DROP_ON)
- return false;
- // Don't allow drop onto server itself.
- if (targetFolder.isServer)
- return false;
- for (let i = 0; i < dt.mozItemCount; i++) {
- let folder = dt.mozGetDataAt("text/x-moz-newsfolder", i)
- .QueryInterface(Ci.nsIMsgFolder);
- // Don't allow dragging newsgroup to other account.
- if (targetFolder.rootFolder != folder.rootFolder)
- return false;
- // Don't allow dragging newsgroup to before/after itself.
- if (targetFolder == folder)
- return false;
- // Don't allow dragging newsgroup to before item after or
- // after item before.
- let row = aRow + aOrientation;
- if (row in gFolderTreeView._rowMap &&
- (gFolderTreeView._rowMap[row]._folder == folder))
- return false;
- }
- return true;
- }
- // Allow subscribing to feeds by dragging an url to a feed account.
- else if (targetFolder.server.type == "rss" && dt.mozItemCount == 1)
- return FeedUtils.getFeedUriFromDataTransfer(dt) ? true : false;
- else if (Array.indexOf(types, "application/x-moz-file") != -1) {
- if (aOrientation != Ci.nsITreeView.DROP_ON)
- return false;
- // Don't allow drop onto server itself.
- if (targetFolder.isServer)
- return false;
- // Don't allow drop into a folder that cannot take messages.
- if (!targetFolder.canFileMessages)
- return false;
- for (let i = 0; i < dt.mozItemCount; i++) {
- let extFile = dt.mozGetDataAt("application/x-moz-file", i)
- .QueryInterface(Ci.nsILocalFile);
- return extFile.isFile();
- }
- }
- return false;
- },
- 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)
- .QueryInterface(Ci.nsIMsgFolder));
- 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-newsfolder") != -1) {
- // Start by getting folders into order.
- let folders = new Array;
- for (let i = 0; i < count; i++) {
- let folder = dt.mozGetDataAt("text/x-moz-newsfolder", i)
- .QueryInterface(Ci.nsIMsgFolder);
- folders[this.getIndexOfFolder(folder)] = folder;
- }
- let newsFolder = targetFolder.rootFolder
- .QueryInterface(Ci.nsIMsgNewsFolder);
- // When moving down, want to insert first one last.
- // When moving up, want to insert first one first.
- let i = (aOrientation == 1) ? folders.length - 1 : 0;
- while (i >= 0 && i < folders.length) {
- let folder = folders[i];
- if (folder) {
- newsFolder.moveFolder(folder, targetFolder, aOrientation);
- this.selection.toggleSelect(this.getIndexOfFolder(folder));
- }
- i -= aOrientation;
- }
- }
- else if (Array.indexOf(types, "text/x-moz-message") != -1) {
- let array = Cc["@mozilla.org/array;1"]
- .createInstance(Ci.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;
- if (!sourceFolder.canDeleteMessages)
- isMove = false;
- prefBranch.setCharPref("last_msg_movecopy_target_uri", targetFolder.URI);
- prefBranch.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, "application/x-moz-file") != -1) {
- for (let i = 0; i < count; i++) {
- let extFile = dt.mozGetDataAt("application/x-moz-file", i)
- .QueryInterface(Ci.nsILocalFile);
- if (extFile.isFile()) {
- let len = extFile.leafName.length;
- if (len > 4 && extFile.leafName.substr(len - 4).toLowerCase() == ".eml")
- cs.CopyFileMessage(extFile, targetFolder, null, false, 1, "", null, msgWindow);
- }
- }
- }
- if (targetFolder.server.type == "rss" && count == 1) {
- // This is a potential rss feed. A link image as well as link text url
- // should be handled; try to extract a url from non moz apps as well.
- let validUri = FeedUtils.getFeedUriFromDataTransfer(dt);
- if (validUri)
- Cc["@mozilla.org/newsblog-feed-downloader;1"]
- .getService(Ci.nsINewsBlogFeedDownloader)
- .subscribeToFeed(validUri.spec, 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) {
- let flavor = folders[i].server.type == "nntp" ? "text/x-moz-newsfolder" :
- "text/x-moz-folder";
- aEvent.dataTransfer.mozSetDataAt(flavor, 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) {
- return this._rowMap.indexOf(this._rowMap[aIndex]._parent);
- },
- /**
- * 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);
- },
- /**
- * Check whether there are any more rows with our level before the next row
- * at our parent's level
- */
- hasNextSibling: function ftv_hasNextSibling(aIndex, aNextIndex) {
- var currentLevel = this._rowMap[aIndex].level;
- for (var i = aNextIndex + 1; i < this._rowMap.length; i++) {
- if (this._rowMap[i].level == currentLevel)
- return true;
- if (this._rowMap[i].level < currentLevel)
- return false;
- }
- return false;
- },
- /**
- * 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 oldCount = this._rowMap.length;
- function recursivelyAddToMap(aChild, aNewIndex, tree) {
- // 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);
- let kidsAdded = recursivelyAddToMap(child, index, tree);
- count += kidsAdded;
- // Somehow the aNewIndex turns into a string without this
- aNewIndex = Number(aNewIndex) + kidsAdded;
- }
- }
- return count;
- }
- // work around bug 658534 by passing in "this" instead of let tree = this;
- recursivelyAddToMap(this._rowMap[aIndex], aIndex, this);
- // 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);
- this._tree.invalidateRow(aIndex);
- }
- // if this was a server that was expanded, let it update its counts
- let folder = this._rowMap[aIndex]._folder;
- if (aExpandServer) {
- if (folder.isServer)
- folder.server.performExpand(msgWindow);
- else if (folder instanceof Components.interfaces.nsIMsgImapMailFolder)
- folder.performExpand(msgWindow);
- }
- }
- },
- _subFoldersWithStringProperty: function ftv_subFoldersWithStringProperty(folder, folders, aFolderName, deep)
- {
- for each (let child in fixIterator(folder.subFolders, Components.interfaces.nsIMsgFolder)) {
- // if the folder selection is based on a string propery, use that
- if (aFolderName == getSmartFolderName(child)) {
- folders.push(child);
- // Add sub-folders if requested.
- if (deep)
- this.addSubFolders(child, folders);
- }
- else
- // if this folder doesn't have a property set, check Its children
- this._subFoldersWithStringProperty(child, folders, aFolderName, deep);
- }
- },
- _allFoldersWithStringProperty: function ftv_getAllFoldersWithProperty(accounts, aFolderName, deep)
- {
- let folders = [];
- for each (let acct in accounts) {
- let folder = acct.incomingServer.rootFolder;
- this._subFoldersWithStringProperty(folder, folders, aFolderName, deep);
- }
- return folders;
- },
- _allFoldersWithFlag: function ftv_getAllFolders(accounts, aFolderFlag, deep)
- {
- let folders = [];
- for each (let acct in accounts) {
- let foldersWithFlag = acct.incomingServer.rootFolder.getFoldersWithFlags(aFolderFlag);
- if (foldersWithFlag.length > 0) {
- for each (let folderWithFlag in fixIterator(foldersWithFlag.enumerate(),
- Components.interfaces.nsIMsgFolder)) {
- folders.push(folderWithFlag);
- // Add sub-folders of Sent and Archive to the result.
- if (deep && (aFolderFlag & (nsMsgFolderFlags.SentMail | nsMsgFolderFlags.Archive)))
- this.addSubFolders(folderWithFlag, folders);
- }
- }
- }
- return folders;
- },
- /**
- * get folders by flag or property based on the value of flag
- */
- _allSmartFolders: function ftv_allSmartFolders(accounts, flag, folderName, deep) {
- return flag ?
- gFolderTreeView._allFoldersWithFlag(accounts, flag, deep) :
- gFolderTreeView._allFoldersWithStringProperty(accounts, folderName, deep);
- },
- /**
- * Add a smart folder for folders with the passed flag set. But if there's
- * only one folder with the flag set, just put it at the top level.
- *
- * @param map array to add folder item to.
- * @param accounts array of accounts.
- * @param smartRootFolder root folder of the smart folders server
- * @param flag folder flag to create smart folders for
- * @param folderName name to give smart folder
- * @param position optional place to put folder item in map. If not specified,
- * folder item will be appended at the end of map.
- * @returns The smart folder's ftvItem if one was added, null otherwise.
- */
- _addSmartFoldersForFlag: function ftv_addSFForFlag(map, accounts, smartRootFolder,
- flag, folderName, position)
- {
- // If there's only one subFolder, just put it at the root.
- let subFolders = gFolderTreeView._allSmartFolders(accounts, flag, folderName, false);
- if (flag && subFolders.length == 1) {
- let folderItem = new ftvItem(subFolders[0]);
- folderItem._level = 0;
- if (flag & nsMsgFolderFlags.Inbox)
- folderItem.__defineGetter__("children", function() []);
- if (position == undefined)
- map.push(folderItem);
- else
- map[position] = folderItem;
- // No smart folder was added
- return null;
- }
- let smartFolder;
- try {
- let folderUri = smartRootFolder.URI + "/" + encodeURI(folderName);
- smartFolder = smartRootFolder.getChildWithURI(folderUri, false, true);
- } catch (ex) {
- smartFolder = null;
- };
- if (!smartFolder) {
- let searchFolders = gFolderTreeView._allSmartFolders(accounts, flag, folderName, true);
- let searchFolderURIs = "";
- for each (let searchFolder in searchFolders) {
- if (searchFolderURIs.length)
- searchFolderURIs += '|';
- searchFolderURIs += searchFolder.URI;
- }
- if (!searchFolderURIs.length)
- return;
- smartFolder = gFolderTreeView._createVFFolder(folderName, smartRootFolder,
- searchFolderURIs, flag);
- }
- let smartFolderItem = new ftvItem(smartFolder);
- smartFolderItem._level = 0;
- if (position == undefined)
- map.push(smartFolderItem);
- else
- map[position] = smartFolderItem;
- // Add the actual special folders as sub-folders of the saved search.
- // By setting _children directly, we bypass the normal calculation
- // of subfolders.
- smartFolderItem._children = [new ftvItem(f) for each (f in subFolders)];
- let prevChild = null;
- // Each child is a level one below the smartFolder
- for each (let child in smartFolderItem._children) {
- child._level = smartFolderItem._level + 1;
- child._parent = smartFolderItem;
- // don't show sub-folders of the inbox, but I think Archives/Sent, etc
- // should have the sub-folders.
- if (flag & nsMsgFolderFlags.Inbox)
- child.__defineGetter__("children", function() []);
- // If we have consecutive children with the same server, then both
- // should display as folder - server.
- if (prevChild && (child._folder.server == prevChild._folder.server)) {
- child.addServerName = true;
- prevChild.addServerName = true;
- prevChild.useServerNameOnly = false;
- }
- else if (flag)
- child.useServerNameOnly = true;
- else
- child.addServerName = true;
- prevChild = child;
- }
- // new custom folders from addons may contain lots of children, sort them
- if (flag == 0)
- sortFolderItems(smartFolderItem._children);
- return smartFolderItem;
- },
- _createVFFolder: function ftv_createVFFolder(newName, parentFolder,
- searchFolderURIs, folderFlag)
- {
- let newFolder;
- try {
- if (parentFolder instanceof(Components.interfaces.nsIMsgLocalMailFolder))
- newFolder = parentFolder.createLocalSubfolder(newName);
- else
- newFolder = parentFolder.addSubfolder(newName);
- newFolder.setFlag(nsMsgFolderFlags.Virtual);
- // provide a way to make the top level folder just a container, not
- // a search folder
- let type = this._modes["smart"].getSmartFolderTypeByName(newName);
- if (type[3]) { // isSearch
- let vfdb = newFolder.msgDatabase;
- let dbFolderInfo = vfdb.dBFolderInfo;
- // set the view string as a property of the db folder info
- // set the original folder name as well.
- dbFolderInfo.setCharProperty("searchStr", "ALL");
- dbFolderInfo.setCharProperty("searchFolderUri", searchFolderURIs);
- dbFolderInfo.setUint32Property("searchFolderFlag", folderFlag);
- dbFolderInfo.setBooleanProperty("searchOnline", true);
- vfdb.summaryValid = true;
- vfdb.Close(true);
- }
- parentFolder.NotifyItemAdded(newFolder);
- Components.classes["@mozilla.org/messenger/account-manager;1"]
- .getService(Components.interfaces.nsIMsgAccountManager)
- .saveVirtualFolders();
- }
- catch(e) {
- throw(e);
- dump ("Exception : creating virtual folder \n");
- }
- return newFolder;
- },
- // 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.
- /**
- * This is an array of all possible modes for the folder tree. You should not
- * modify this directly, but rather use registerFolderTreeMode.
- */
- _modeNames: ["all", "unread", "favorite", "recent", "smart"],
- _modeDisplayNames: {},
- /**
- * 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,
- selection: 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 newRowMap;
- try {
- newRowMap = this._modes[this.mode].generateMap(this);
- } catch(ex) {
- Components.classes["@mozilla.org/consoleservice;1"]
- .getService(Components.interfaces.nsIConsoleService)
- .logStringMessage("generator " + this.mode + " failed with exception: " + ex);
- this.mode = "all";
- newRowMap = this._modes[this.mode].generateMap(this);
- }
- let selectedFolders = this.getSelectedFolders();
- if (this.selection)
- this.selection.clearSelection();
- // There's a chance the call to the map generator altered this._rowMap, so
- // evaluate oldCount after calling it rather than before
- let oldCount = this._rowMap ? this._rowMap.length : null;
- this._rowMap = newRowMap;
- let evt = document.createEvent("Events");
- evt.initEvent("mapRebuild", true, false);
- this._treeElement.dispatchEvent(evt);
- if (this._tree)
- {
- if (oldCount !== null)
- this._tree.rowCountChanged(0, this._rowMap.length - oldCount);
- this._tree.invalidate();
- }
- this._restoreOpenStates();
- // restore selection.
- for (let [, folder] in Iterator(selectedFolders)) {
- if (folder) {
- let index = this.getIndexOfFolder(folder);
- if (index != null)
- this.selection.toggleSelect(index);
- }
- }
- },
- _sortedAccounts: function ftv_getSortedAccounts()
- {
- 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);
- });
- // Don't show IM accounts
- accounts = accounts.filter(function(a) a.incomingServer.type != "im");
- 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);
- return accounts;
- },
- /**
- * Contains the set of modes registered with the folder tree, initially those
- * included by default. This is a map from names of modes to their
- * implementations of |IFolderTreeMode|.
- */
- _modes: {
- /**
- * The all mode returns all folders, arranged in a hierarchy
- */
- all: {
- __proto__: IFolderTreeMode,
- generateMap: function ftv_all_generateMap(ftv) {
- let accounts = gFolderTreeView._sortedAccounts();
- // force each root folder to do its local subfolder discovery.
- MailUtils.discoverFolders();
- 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. Also always keep the currently selected folder
- * so it doesn't disappear under the user.
- */
- unread: {
- __proto__: IFolderTreeMode,
- generateMap: function ftv_unread_generateMap(ftv) {
- let map = [];
- let currentFolder = gFolderTreeView.getSelectedFolders()[0];
- const outFolderFlagMask = nsMsgFolderFlags.SentMail |
- nsMsgFolderFlags.Drafts | nsMsgFolderFlags.Queue |
- nsMsgFolderFlags.Templates;
- for each (let folder in ftv._enumerateFolders) {
- if (!folder.isSpecialFolder(outFolderFlagMask, true) &&
- (!folder.isServer && folder.getNumUnread(false) > 0) ||
- (folder == currentFolder))
- map.push(new ftvItem(folder));
- }
- // There are no children in this view!
- for each (let folder in map) {
- folder.__defineGetter__("children", function() []);
- folder.addServerName = true;
- }
- sortFolderItems(map);
- return map;
- },
- getParentOfFolder: function ftv_unread_getParentOfFolder(aFolder) {
- // This is a flat view, so no folders have parents.
- return null;
- }
- },
- /**
- * The favorites mode returns all folders whose flags are set to include
- * the favorite flag
- */
- favorite: {
- __proto__: IFolderTreeMode,
- generateMap: function ftv_favorite_generateMap(ftv) {
- let faves = [];
- for each (let folder in ftv._enumerateFolders) {…