/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

  1. /* ***** BEGIN LICENSE BLOCK *****
  2. * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3. *
  4. * The contents of this file are subject to the Mozilla Public License Version
  5. * 1.1 (the "License"); you may not use this file except in compliance with
  6. * the License. You may obtain a copy of the License at
  7. * http://www.mozilla.org/MPL/
  8. *
  9. * Software distributed under the License is distributed on an "AS IS" basis,
  10. * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11. * for the specific language governing rights and limitations under the
  12. * License.
  13. *
  14. * The Original Code is mail folder tree code.
  15. *
  16. * The Initial Developer of the Original Code is
  17. * Joey Minta <jminta@gmail.com>
  18. * Portions created by the Initial Developer are Copyright (C) 2008
  19. * the Initial Developer. All Rights Reserved.
  20. *
  21. * Contributor(s):
  22. * Siddharth Agarwal <sid.bugzilla@gmail.com>
  23. *
  24. * Alternatively, the contents of this file may be used under the terms of
  25. * either the GNU General Public License Version 2 or later (the "GPL"), or
  26. * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27. * in which case the provisions of the GPL or the LGPL are applicable instead
  28. * of those above. If you wish to allow use of your version of this file only
  29. * under the terms of either the GPL or the LGPL, and not to allow others to
  30. * use your version of this file under the terms of the MPL, indicate your
  31. * decision by deleting the provisions above and replace them with the notice
  32. * and other provisions required by the GPL or the LGPL. If you do not delete
  33. * the provisions above, a recipient may use your version of this file under
  34. * the terms of any one of the MPL, the GPL or the LGPL.
  35. *
  36. * ***** END LICENSE BLOCK ***** */
  37. Components.utils.import("resource:///modules/folderUtils.jsm");
  38. Components.utils.import("resource:///modules/iteratorUtils.jsm");
  39. Components.utils.import("resource:///modules/MailUtils.js");
  40. const kDefaultMode = "all";
  41. var nsMsgFolderFlags = Components.interfaces.nsMsgFolderFlags;
  42. /**
  43. * This file contains the controls and functions for the folder pane.
  44. * The following definitions will be useful to know:
  45. *
  46. * gFolderTreeView - the controller for the folder tree.
  47. * ftvItem - folder tree view item, representing a row in the tree
  48. * mode - folder view type, e.g., all folders, favorite folders, MRU...
  49. */
  50. /**
  51. * An interface that needs to be implemented in order to add a new view to the
  52. * folder tree. For default behavior, it is recommended that implementers
  53. * subclass this interface instead of relying on duck typing.
  54. *
  55. * For implementation examples, see |gFolderTreeView._modes|. For how to
  56. * register this mode with |gFolderTreeView|, see
  57. * |gFolderTreeView.registerFolderTreeMode|.
  58. */
  59. let IFolderTreeMode = {
  60. /**
  61. * Generates the folder map for this mode.
  62. *
  63. * @param aFolderTreeView The gFolderTreeView for which this mode is being
  64. * activated.
  65. *
  66. * @returns An array containing ftvItem instances representing the top-level
  67. * folders in this view.
  68. */
  69. generateMap: function IFolderTreeMode_generateMap(aFolderTreeView) {
  70. return null;
  71. },
  72. /**
  73. * Given an nsIMsgFolder, returns its parent in the map. The default behaviour
  74. * is to return the folder's actual parent (aFolder.parent). Folder tree modes
  75. * may decide to override it.
  76. *
  77. * If the parent isn't easily computable given just the folder, you may
  78. * consider generating the entire ftvItem tree at once and using a map from
  79. * folders to ftvItems.
  80. *
  81. * @returns an nsIMsgFolder representing the parent of the folder in the view,
  82. * or null if the folder is a top-level folder in the map. It is expected
  83. * that the returned parent will have the given folder as one of its
  84. * children.
  85. * @note This function need not guarantee that either the folder or its parent
  86. * is actually in the view.
  87. */
  88. getParentOfFolder: function IFolderTreeMode_getParentOfFolder(aFolder) {
  89. return aFolder.parent;
  90. },
  91. /**
  92. * Given an nsIMsgDBHdr, returns the folder it is considered to be contained
  93. * in, in this mode. This is usually just the physical folder it is contained
  94. * in (aMsgHdr.folder), but some modes may decide to override this. For
  95. * example, combined views like Smart Folders return the smart inbox for any
  96. * messages in any inbox.
  97. *
  98. * The folder returned doesn't need to be in the view.
  99. * @returns The folder the message header is considered to be contained in, in
  100. * this mode. The returned folder may or may not actually be in the view
  101. * -- however, given a valid nsIMsgDBHdr, it is expected that a) a
  102. * non-null folder is returned, and that b) the folder that is returned
  103. * actually does contain the message header.
  104. */
  105. getFolderForMsgHdr: function IFolderTreeMode_getFolderForMsgHdr(aMsgHdr) {
  106. return aMsgHdr.folder;
  107. },
  108. /**
  109. * Notified when a folder is added. The default behavior is to add it as a
  110. * child of the parent item, but some views may decide to override this. For
  111. * example, combined views like Smart Folders add any new inbox as a child of
  112. * the smart inbox.
  113. *
  114. * @param aParent The parent of the folder that was added.
  115. * @param aFolder The folder that was added.
  116. */
  117. onFolderAdded: function IFolderTreeMode_onFolderAdded(aParent, aFolder) {
  118. gFolderTreeView.addFolder(aParent, aFolder);
  119. }
  120. };
  121. /**
  122. * This is our controller for the folder-tree. It includes our nsITreeView
  123. * implementation, as well as other control functions.
  124. */
  125. let gFolderTreeView = {
  126. /**
  127. * Called when the window is initially loaded. This function initializes the
  128. * folder-pane to the view last shown before the application was closed.
  129. */
  130. load: function ftv_load(aTree, aJSONFile) {
  131. const Cc = Components.classes;
  132. const Ci = Components.interfaces;
  133. this._treeElement = aTree;
  134. // the folder pane can be used for other trees which may not have these elements.
  135. if (document.getElementById("folderpane_splitter"))
  136. document.getElementById("folderpane_splitter").collapsed = false;
  137. if (document.getElementById("folderPaneBox"))
  138. document.getElementById("folderPaneBox").collapsed = false;
  139. try {
  140. // Normally our tree takes care of keeping the last selected by itself.
  141. // However older versions of TB stored this in a preference, which we need
  142. // to migrate
  143. let prefB = Cc["@mozilla.org/preferences-service;1"]
  144. .getService(Ci.nsIPrefBranch);
  145. let modeIndex = prefB.getIntPref("mail.ui.folderpane.view");
  146. this._mode = this._modeNames[modeIndex];
  147. prefB.deleteBranch("mail.ui.folderpane");
  148. } catch(ex) {
  149. // This is ok. If we've already migrated we'll end up here
  150. }
  151. if (document.getElementById('folderpane-title')) {
  152. let string;
  153. if (this.mode in this._modeDisplayNames)
  154. string = this._modeDisplayNames[this.mode];
  155. else {
  156. let key = "folderPaneModeHeader_" + this.mode;
  157. string = document.getElementById("bundle_messenger").getString(key);
  158. }
  159. document.getElementById('folderpane-title').value = string;
  160. }
  161. if (aJSONFile) {
  162. // Parse our persistent-open-state json file
  163. let file = Cc["@mozilla.org/file/directory_service;1"]
  164. .getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
  165. file.append(aJSONFile);
  166. if (file.exists()) {
  167. let data = "";
  168. let fstream = Cc["@mozilla.org/network/file-input-stream;1"]
  169. .createInstance(Ci.nsIFileInputStream);
  170. let sstream = Cc["@mozilla.org/scriptableinputstream;1"]
  171. .createInstance(Ci.nsIScriptableInputStream);
  172. fstream.init(file, -1, 0, 0);
  173. sstream.init(fstream);
  174. while (sstream.available())
  175. data += sstream.read(4096);
  176. sstream.close();
  177. fstream.close();
  178. try {
  179. this._persistOpenMap = JSON.parse(data);
  180. } catch (x) {
  181. Components.utils.reportError(
  182. document.getElementById("bundle_messenger")
  183. .getFormattedString("failedToReadFile", [aJSONFile, x]));
  184. }
  185. }
  186. }
  187. // Load our data
  188. this._rebuild();
  189. // And actually draw the tree
  190. aTree.view = this;
  191. // Add this listener so that we can update the tree when things change
  192. let session = Cc["@mozilla.org/messenger/services/session;1"]
  193. .getService(Ci.nsIMsgMailSession);
  194. session.AddFolderListener(this, Ci.nsIFolderListener.all);
  195. },
  196. /**
  197. * Called when the window is being torn down. Here we undo everything we did
  198. * onload. That means removing our listener and serializing our JSON.
  199. */
  200. unload: function ftv_unload(aJSONFile) {
  201. const Cc = Components.classes;
  202. const Ci = Components.interfaces;
  203. // Remove our listener
  204. let session = Cc["@mozilla.org/messenger/services/session;1"]
  205. .getService(Ci.nsIMsgMailSession);
  206. session.RemoveFolderListener(this);
  207. if (aJSONFile) {
  208. // Write out our json file...
  209. let data = JSON.stringify(this._persistOpenMap);
  210. let file = Cc["@mozilla.org/file/directory_service;1"]
  211. .getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
  212. file.append(aJSONFile);
  213. let foStream = Cc["@mozilla.org/network/safe-file-output-stream;1"]
  214. .createInstance(Ci.nsIFileOutputStream);
  215. foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0);
  216. // safe-file-output-stream appears to throw an error if it doesn't write everything at once
  217. // so we won't worry about looping to deal with partial writes
  218. foStream.write(data, data.length);
  219. foStream.QueryInterface(Ci.nsISafeOutputStream).finish();
  220. foStream.close();
  221. }
  222. },
  223. /**
  224. * Extensions can use this function to add a new mode to the folder pane.
  225. *
  226. * @param aCommonName an internal name to identify this mode. Must be unique
  227. * @param aMode An implementation of |IFolderTreeMode| for this mode.
  228. * @param aDisplayName a localized name for this mode
  229. */
  230. registerFolderTreeMode: function ftv_registerFolderTreeMode(aCommonName,
  231. aMode,
  232. aDisplayName) {
  233. this._modeNames.push(aCommonName);
  234. this._modes[aCommonName] = aMode;
  235. this._modeDisplayNames[aCommonName] = aDisplayName;
  236. },
  237. /**
  238. * Unregisters a previously registered mode. Since common-names must be unique
  239. * this is all that need be provided to unregister.
  240. * @param aCommonName the common-name with which the mode was previously
  241. * registered
  242. */
  243. unregisterFolderTreeMode: function ftv_unregisterFolderTreeMode(aCommonName) {
  244. this._modeNames.splice(this._modeNames.indexOf(aCommonName), 1);
  245. delete this._modes[aCommonName];
  246. delete this._modeDisplayNames[aCommonName];
  247. if (this._mode == aCommonName)
  248. this.mode = kDefaultMode;
  249. },
  250. /**
  251. * Retrieves a specific mode object
  252. * @param aCommonName the common-name with which the mode was previously
  253. * registered
  254. */
  255. getFolderTreeMode: function ftv_getFolderTreeMode(aCommonName) {
  256. return this._modes[aCommonName];
  257. },
  258. /**
  259. * Called to move to the next/prev folder-mode in the list
  260. *
  261. * @param aForward whether or not we should move forward in the list
  262. */
  263. cycleMode: function ftv_cycleMode(aForward) {
  264. let index = this._modeNames.indexOf(this.mode);
  265. let offset = aForward ? 1 : this._modeNames.length - 1;
  266. index = (index + offset) % this._modeNames.length;
  267. this.mode = this._modeNames[index];
  268. },
  269. /**
  270. * If the hidden pref is set, then double-clicking on a folder should open it
  271. *
  272. * @param event the double-click event
  273. */
  274. onDoubleClick: function ftv_onDoubleClick(aEvent) {
  275. if (aEvent.button != 0 || aEvent.originalTarget.localName == "twisty" ||
  276. aEvent.originalTarget.localName == "slider" ||
  277. aEvent.originalTarget.localName == "scrollbarbutton")
  278. return;
  279. let row = gFolderTreeView._treeElement.treeBoxObject.getRowAt(aEvent.clientX,
  280. aEvent.clientY);
  281. let folderItem = gFolderTreeView._rowMap[row];
  282. if (folderItem)
  283. folderItem.command();
  284. // Don't let the double-click toggle the open state of the folder here
  285. aEvent.stopPropagation();
  286. },
  287. getFolderAtCoords: function ftv_getFolderAtCoords(aX, aY) {
  288. let row = gFolderTreeView._treeElement.treeBoxObject.getRowAt(aX, aY);
  289. if (row in gFolderTreeView._rowMap)
  290. return gFolderTreeView._rowMap[row]._folder;
  291. return null;
  292. },
  293. /**
  294. * A string representation for the current display-mode. Each value here must
  295. * correspond to an entry in _modes
  296. */
  297. _mode: null,
  298. get mode() {
  299. if (!this._mode) {
  300. this._mode = this._treeElement.getAttribute("mode");
  301. // this can happen when an extension is removed
  302. if (!(this._mode in this._modes))
  303. this._mode = kDefaultMode;
  304. }
  305. return this._mode;
  306. },
  307. set mode(aMode) {
  308. this._mode = aMode;
  309. let string;
  310. if (this._mode in this._modeDisplayNames)
  311. string = this._modeDisplayNames[this._mode];
  312. else {
  313. let key = "folderPaneModeHeader_" + aMode;
  314. string = document.getElementById("bundle_messenger").getString(key);
  315. }
  316. document.getElementById('folderpane-title').value = string;
  317. this._treeElement.setAttribute("mode", aMode);
  318. this._rebuild();
  319. },
  320. /**
  321. * Selects a given nsIMsgFolder in the tree. This function will also ensure
  322. * that the folder is actually being displayed (that is, that none of its
  323. * ancestors are collapsed.
  324. *
  325. * @param aFolder the nsIMsgFolder to select
  326. * @param [aForceSelect] Whether we should switch to the default mode to
  327. * select the folder in case we didn't find the folder in the current
  328. * view. Defaults to false.
  329. * @returns true if the folder selection was successful, false if it failed
  330. * (probably because the folder isn't in the view at all)
  331. */
  332. selectFolder: function ftv_selectFolder(aFolder, aForceSelect) {
  333. // "this" inside the nested function refers to the function...
  334. // Also note that openIfNot is recursive.
  335. let tree = this;
  336. let folderTreeMode = this._modes[this._mode];
  337. function openIfNot(aFolderToOpen) {
  338. let index = tree.getIndexOfFolder(aFolderToOpen);
  339. if (index != null) {
  340. if (!tree._rowMap[index].open)
  341. tree._toggleRow(index, false);
  342. return true;
  343. }
  344. // not found, so open the parent
  345. let parent = folderTreeMode.getParentOfFolder(aFolderToOpen);
  346. if (parent && openIfNot(parent)) {
  347. // now our parent is open, so we can open ourselves
  348. index = tree.getIndexOfFolder(aFolderToOpen);
  349. if (index != null) {
  350. tree._toggleRow(index, false);
  351. return true;
  352. }
  353. }
  354. // No way we can find the folder now.
  355. return false;
  356. }
  357. let parent = folderTreeMode.getParentOfFolder(aFolder);
  358. if (parent)
  359. openIfNot(parent);
  360. let folderIndex = tree.getIndexOfFolder(aFolder);
  361. if (folderIndex == null) {
  362. if (aForceSelect) {
  363. // Switch to the default mode. The assumption here is that the default
  364. // mode can display every folder
  365. this.mode = kDefaultMode;
  366. // We don't want to get stuck in an infinite recursion, so pass in false
  367. return this.selectFolder(aFolder, false);
  368. }
  369. return false;
  370. }
  371. this.selection.select(folderIndex);
  372. this._treeElement.treeBoxObject.ensureRowIsVisible(folderIndex);
  373. return true;
  374. },
  375. /**
  376. * Returns the index of a folder in the current display.
  377. *
  378. * @param aFolder the folder whose index should be returned.
  379. * @returns The index of the folder in the view (a number).
  380. * @note If the folder is not in the display (perhaps because one of its
  381. * anscetors is collapsed), this function returns null.
  382. */
  383. getIndexOfFolder: function ftv_getIndexOfFolder(aFolder) {
  384. for each (let [iRow, row] in Iterator(this._rowMap)) {
  385. if (row.id == aFolder.URI)
  386. return iRow;
  387. }
  388. return null;
  389. },
  390. /**
  391. * Returns the folder for an index in the current display.
  392. *
  393. * @param aIndex the index for which the folder should be returned.
  394. * @note If the index is out of bounds, this function returns null.
  395. */
  396. getFolderForIndex: function ftv_getFolderForIndex(aIndex) {
  397. if (aIndex < 0 || aIndex >= this._rowMap.length)
  398. return null;
  399. return this._rowMap[aIndex]._folder;
  400. },
  401. /**
  402. * Returns the parent of a folder in the current view. This may be, but is not
  403. * necessarily, the actual parent of the folder (aFolder.parent). In
  404. * particular, in the smart view, special folders are usually children of the
  405. * smart folder of that kind.
  406. *
  407. * @param aFolder The folder to get the parent of.
  408. * @returns The parent of the folder, or null if the parent wasn't found.
  409. * @note This function does not guarantee that either the folder or its parent
  410. * is actually in the view.
  411. */
  412. getParentOfFolder: function ftv_getParentOfFolder(aFolder) {
  413. return this._modes[this._mode].getParentOfFolder(aFolder);
  414. },
  415. /**
  416. * Given an nsIMsgDBHdr, returns the folder it is considered to be contained
  417. * in, in the current mode. This is usually, but not necessarily, the actual
  418. * folder the message is in (aMsgHdr.folder). For more details, see
  419. * |IFolderTreeMode.getFolderForMsgHdr|.
  420. */
  421. getFolderForMsgHdr: function ftv_getFolderForMsgHdr(aMsgHdr) {
  422. return this._modes[this._mode].getFolderForMsgHdr(aMsgHdr);
  423. },
  424. /**
  425. * Returns the |ftvItem| for an index in the current display. Intended for use
  426. * by folder tree mode implementers.
  427. *
  428. * @param aIndex The index for which the ftvItem should be returned.
  429. * @note If the index is out of bounds, this function returns null.
  430. */
  431. getFTVItemForIndex: function ftv_getFTVItemForIndex(aIndex) {
  432. return this._rowMap[aIndex];
  433. },
  434. /**
  435. * Returns an array of nsIMsgFolders corresponding to the current selection
  436. * in the tree
  437. */
  438. getSelectedFolders: function ftv_getSelectedFolders() {
  439. let selection = this.selection;
  440. if (!selection)
  441. return [];
  442. let folderArray = [];
  443. let rangeCount = selection.getRangeCount();
  444. for (let i = 0; i < rangeCount; i++) {
  445. let startIndex = {};
  446. let endIndex = {};
  447. selection.getRangeAt(i, startIndex, endIndex);
  448. for (let j = startIndex.value; j <= endIndex.value; j++) {
  449. if (j < this._rowMap.length)
  450. folderArray.push(this._rowMap[j]._folder);
  451. }
  452. }
  453. return folderArray;
  454. },
  455. /**
  456. * Adds a new child |ftvItem| to the given parent |ftvItem|. Intended for use
  457. * by folder tree mode implementers.
  458. *
  459. * @param aParentItem The parent ftvItem. It is assumed that this is visible
  460. * in the view.
  461. * @param aParentIndex The index of the parent ftvItem in the view.
  462. * @param aItem The item to add.
  463. */
  464. addChildItem: function ftv_addChildItem(aParentItem, aParentIndex, aItem) {
  465. this._addChildToView(aParentItem, aParentIndex, aItem);
  466. },
  467. // ****************** Start of nsITreeView implementation **************** //
  468. get rowCount() {
  469. return this._rowMap.length;
  470. },
  471. /**
  472. * drag drop interfaces
  473. */
  474. canDrop: function ftv_canDrop(aRow, aOrientation) {
  475. const Cc = Components.classes;
  476. const Ci = Components.interfaces;
  477. let targetFolder = gFolderTreeView._rowMap[aRow]._folder;
  478. if (!targetFolder)
  479. return false;
  480. let dt = this._currentTransfer;
  481. let types = dt.mozTypesAt(0);
  482. if (Array.indexOf(types, "text/x-moz-message") != -1) {
  483. if (aOrientation != Ci.nsITreeView.DROP_ON)
  484. return false;
  485. // Don't allow drop onto server itself.
  486. if (targetFolder.isServer)
  487. return false;
  488. // Don't allow drop into a folder that cannot take messages.
  489. if (!targetFolder.canFileMessages)
  490. return false;
  491. let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
  492. for (let i = 0; i < dt.mozItemCount; i++) {
  493. let msgHdr = messenger.msgHdrFromURI(dt.mozGetDataAt("text/x-moz-message", i));
  494. // Don't allow drop onto original folder.
  495. if (msgHdr.folder == targetFolder)
  496. return false;
  497. }
  498. return true;
  499. }
  500. else if (Array.indexOf(types, "text/x-moz-folder") != -1) {
  501. if (aOrientation != Ci.nsITreeView.DROP_ON)
  502. return false;
  503. // If cannot create subfolders then don't allow drop here.
  504. if (!targetFolder.canCreateSubfolders)
  505. return false;
  506. for (let i = 0; i < dt.mozItemCount; i++) {
  507. let folder = dt.mozGetDataAt("text/x-moz-folder", i)
  508. .QueryInterface(Ci.nsIMsgFolder);
  509. // Don't allow to drop on itself.
  510. if (targetFolder == folder)
  511. return false;
  512. // Don't copy within same server.
  513. if ((folder.server == targetFolder.server) &&
  514. (dt.dropEffect == 'copy'))
  515. return false;
  516. // Don't allow immediate child to be dropped onto its parent.
  517. if (targetFolder == folder.parent)
  518. return false;
  519. // Don't allow dragging of virtual folders across accounts.
  520. if ((folder.flags & nsMsgFolderFlags.Virtual) &&
  521. folder.server != targetFolder.server)
  522. return false;
  523. // Don't allow parent to be dropped on its ancestors.
  524. if (folder.isAncestorOf(targetFolder))
  525. return false;
  526. // If there is a folder that can't be renamed, don't allow it to be
  527. // dropped if it is not to "Local Folders" or is to the same account.
  528. if (!folder.canRename && (targetFolder.server.type != "none" ||
  529. folder.server == targetFolder.server))
  530. return false;
  531. }
  532. return true;
  533. }
  534. else if (Array.indexOf(types, "text/x-moz-newsfolder") != -1) {
  535. // Don't allow dragging onto element.
  536. if (aOrientation == Ci.nsITreeView.DROP_ON)
  537. return false;
  538. // Don't allow drop onto server itself.
  539. if (targetFolder.isServer)
  540. return false;
  541. for (let i = 0; i < dt.mozItemCount; i++) {
  542. let folder = dt.mozGetDataAt("text/x-moz-newsfolder", i)
  543. .QueryInterface(Ci.nsIMsgFolder);
  544. // Don't allow dragging newsgroup to other account.
  545. if (targetFolder.rootFolder != folder.rootFolder)
  546. return false;
  547. // Don't allow dragging newsgroup to before/after itself.
  548. if (targetFolder == folder)
  549. return false;
  550. // Don't allow dragging newsgroup to before item after or
  551. // after item before.
  552. let row = aRow + aOrientation;
  553. if (row in gFolderTreeView._rowMap &&
  554. (gFolderTreeView._rowMap[row]._folder == folder))
  555. return false;
  556. }
  557. return true;
  558. }
  559. // Allow subscribing to feeds by dragging an url to a feed account.
  560. else if (targetFolder.server.type == "rss" && dt.mozItemCount == 1)
  561. return FeedUtils.getFeedUriFromDataTransfer(dt) ? true : false;
  562. else if (Array.indexOf(types, "application/x-moz-file") != -1) {
  563. if (aOrientation != Ci.nsITreeView.DROP_ON)
  564. return false;
  565. // Don't allow drop onto server itself.
  566. if (targetFolder.isServer)
  567. return false;
  568. // Don't allow drop into a folder that cannot take messages.
  569. if (!targetFolder.canFileMessages)
  570. return false;
  571. for (let i = 0; i < dt.mozItemCount; i++) {
  572. let extFile = dt.mozGetDataAt("application/x-moz-file", i)
  573. .QueryInterface(Ci.nsILocalFile);
  574. return extFile.isFile();
  575. }
  576. }
  577. return false;
  578. },
  579. drop: function ftv_drop(aRow, aOrientation) {
  580. const Cc = Components.classes;
  581. const Ci = Components.interfaces;
  582. let targetFolder = gFolderTreeView._rowMap[aRow]._folder;
  583. let dt = this._currentTransfer;
  584. let count = dt.mozItemCount;
  585. let cs = Cc["@mozilla.org/messenger/messagecopyservice;1"]
  586. .getService(Ci.nsIMsgCopyService);
  587. // we only support drag of a single flavor at a time.
  588. let types = dt.mozTypesAt(0);
  589. if (Array.indexOf(types, "text/x-moz-folder") != -1) {
  590. for (let i = 0; i < count; i++) {
  591. let folders = new Array;
  592. folders.push(dt.mozGetDataAt("text/x-moz-folder", i)
  593. .QueryInterface(Ci.nsIMsgFolder));
  594. let array = toXPCOMArray(folders, Ci.nsIMutableArray);
  595. cs.CopyFolders(array, targetFolder,
  596. (folders[0].server == targetFolder.server), null,
  597. msgWindow);
  598. }
  599. }
  600. else if (Array.indexOf(types, "text/x-moz-newsfolder") != -1) {
  601. // Start by getting folders into order.
  602. let folders = new Array;
  603. for (let i = 0; i < count; i++) {
  604. let folder = dt.mozGetDataAt("text/x-moz-newsfolder", i)
  605. .QueryInterface(Ci.nsIMsgFolder);
  606. folders[this.getIndexOfFolder(folder)] = folder;
  607. }
  608. let newsFolder = targetFolder.rootFolder
  609. .QueryInterface(Ci.nsIMsgNewsFolder);
  610. // When moving down, want to insert first one last.
  611. // When moving up, want to insert first one first.
  612. let i = (aOrientation == 1) ? folders.length - 1 : 0;
  613. while (i >= 0 && i < folders.length) {
  614. let folder = folders[i];
  615. if (folder) {
  616. newsFolder.moveFolder(folder, targetFolder, aOrientation);
  617. this.selection.toggleSelect(this.getIndexOfFolder(folder));
  618. }
  619. i -= aOrientation;
  620. }
  621. }
  622. else if (Array.indexOf(types, "text/x-moz-message") != -1) {
  623. let array = Cc["@mozilla.org/array;1"]
  624. .createInstance(Ci.nsIMutableArray);
  625. let sourceFolder;
  626. let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
  627. for (let i = 0; i < count; i++) {
  628. let msgHdr = messenger.msgHdrFromURI(dt.mozGetDataAt("text/x-moz-message", i));
  629. if (!i)
  630. sourceFolder = msgHdr.folder;
  631. array.appendElement(msgHdr, false);
  632. }
  633. let prefBranch = Cc["@mozilla.org/preferences-service;1"]
  634. .getService(Ci.nsIPrefService).getBranch("mail.");
  635. let isMove = Cc["@mozilla.org/widget/dragservice;1"]
  636. .getService(Ci.nsIDragService).getCurrentSession()
  637. .dragAction == Ci.nsIDragService.DRAGDROP_ACTION_MOVE;
  638. if (!sourceFolder.canDeleteMessages)
  639. isMove = false;
  640. prefBranch.setCharPref("last_msg_movecopy_target_uri", targetFolder.URI);
  641. prefBranch.setBoolPref("last_msg_movecopy_was_move", isMove);
  642. // ### ugh, so this won't work with cross-folder views. We would
  643. // really need to partition the messages by folder.
  644. cs.CopyMessages(sourceFolder, array, targetFolder, isMove, null,
  645. msgWindow, true);
  646. }
  647. else if (Array.indexOf(types, "application/x-moz-file") != -1) {
  648. for (let i = 0; i < count; i++) {
  649. let extFile = dt.mozGetDataAt("application/x-moz-file", i)
  650. .QueryInterface(Ci.nsILocalFile);
  651. if (extFile.isFile()) {
  652. let len = extFile.leafName.length;
  653. if (len > 4 && extFile.leafName.substr(len - 4).toLowerCase() == ".eml")
  654. cs.CopyFileMessage(extFile, targetFolder, null, false, 1, "", null, msgWindow);
  655. }
  656. }
  657. }
  658. if (targetFolder.server.type == "rss" && count == 1) {
  659. // This is a potential rss feed. A link image as well as link text url
  660. // should be handled; try to extract a url from non moz apps as well.
  661. let validUri = FeedUtils.getFeedUriFromDataTransfer(dt);
  662. if (validUri)
  663. Cc["@mozilla.org/newsblog-feed-downloader;1"]
  664. .getService(Ci.nsINewsBlogFeedDownloader)
  665. .subscribeToFeed(validUri.spec, targetFolder, msgWindow);
  666. }
  667. },
  668. _onDragStart: function ftv_dragStart(aEvent) {
  669. // Ugh, this is ugly but necessary
  670. let view = gFolderTreeView;
  671. if (aEvent.originalTarget.localName != "treechildren")
  672. return;
  673. let folders = view.getSelectedFolders();
  674. folders = folders.filter(function(f) { return !f.isServer; });
  675. for (let i in folders) {
  676. let flavor = folders[i].server.type == "nntp" ? "text/x-moz-newsfolder" :
  677. "text/x-moz-folder";
  678. aEvent.dataTransfer.mozSetDataAt(flavor, folders[i], i);
  679. }
  680. aEvent.dataTransfer.effectAllowed = "copyMove";
  681. aEvent.dataTransfer.addElement(aEvent.originalTarget);
  682. return;
  683. },
  684. _onDragOver: function ftv_onDragOver(aEvent) {
  685. this._currentTransfer = aEvent.dataTransfer;
  686. },
  687. /**
  688. * CSS files will cue off of these. Note that we reach into the rowMap's
  689. * items so that custom data-displays can define their own properties
  690. */
  691. getCellProperties: function ftv_getCellProperties(aRow, aCol, aProps) {
  692. this._rowMap[aRow].getProperties(aProps, aCol);
  693. },
  694. /**
  695. * The actual text to display in the tree
  696. */
  697. getCellText: function ftv_getCellText(aRow, aCol) {
  698. if (aCol.id == "folderNameCol")
  699. return this._rowMap[aRow].text;
  700. },
  701. /**
  702. * The ftvItems take care of assigning this when building children lists
  703. */
  704. getLevel: function ftv_getLevel(aIndex) {
  705. return this._rowMap[aIndex].level;
  706. },
  707. /**
  708. * This is easy since the ftv items assigned the _parent property when making
  709. * the child lists
  710. */
  711. getParentIndex: function ftv_getParentIndex(aIndex) {
  712. return this._rowMap.indexOf(this._rowMap[aIndex]._parent);
  713. },
  714. /**
  715. * This is duplicative for our normal ftv views, but custom data-displays may
  716. * want to do something special here
  717. */
  718. getRowProperties: function ftv_getRowProperties(aIndex, aProps) {
  719. this._rowMap[aIndex].getProperties(aProps);
  720. },
  721. /**
  722. * Check whether there are any more rows with our level before the next row
  723. * at our parent's level
  724. */
  725. hasNextSibling: function ftv_hasNextSibling(aIndex, aNextIndex) {
  726. var currentLevel = this._rowMap[aIndex].level;
  727. for (var i = aNextIndex + 1; i < this._rowMap.length; i++) {
  728. if (this._rowMap[i].level == currentLevel)
  729. return true;
  730. if (this._rowMap[i].level < currentLevel)
  731. return false;
  732. }
  733. return false;
  734. },
  735. /**
  736. * All folders are containers, so we can drag drop messages to them.
  737. */
  738. isContainer: function ftv_isContainer(aIndex) {
  739. return true;
  740. },
  741. isContainerEmpty: function ftv_isContainerEmpty(aIndex) {
  742. // If the folder has no children, the container is empty.
  743. return !this._rowMap[aIndex].children.length;
  744. },
  745. /**
  746. * Just look at the ftvItem here
  747. */
  748. isContainerOpen: function ftv_isContainerOpen(aIndex) {
  749. return this._rowMap[aIndex].open;
  750. },
  751. isEditable: function ftv_isEditable(aRow, aCol) {
  752. // We don't support editing rows in the tree yet. We may want to later as
  753. // an easier way to rename folders.
  754. return false;
  755. },
  756. isSeparator: function ftv_isSeparator(aIndex) {
  757. // There are no separators in our trees
  758. return false;
  759. },
  760. isSorted: function ftv_isSorted() {
  761. // We do our own customized sorting
  762. return false;
  763. },
  764. setTree: function ftv_setTree(aTree) {
  765. this._tree = aTree;
  766. },
  767. /**
  768. * Opens or closes a folder with children. The logic here is a bit hairy, so
  769. * be very careful about changing anything.
  770. */
  771. toggleOpenState: function ftv_toggleOpenState(aIndex) {
  772. this._toggleRow(aIndex, true);
  773. },
  774. _toggleRow: function toggleRow(aIndex, aExpandServer)
  775. {
  776. // Ok, this is a bit tricky.
  777. this._rowMap[aIndex].open = !this._rowMap[aIndex].open;
  778. if (!this._rowMap[aIndex].open) {
  779. // We're closing the current container. Remove the children
  780. // Note that we can't simply splice out children.length, because some of
  781. // them might have children too. Find out how many items we're actually
  782. // going to splice
  783. let count = 0;
  784. let i = aIndex + 1;
  785. let row = this._rowMap[i];
  786. while (row && row.level > this._rowMap[aIndex].level) {
  787. count++;
  788. row = this._rowMap[++i];
  789. }
  790. this._rowMap.splice(aIndex + 1, count);
  791. // Remove us from the persist map
  792. let index = this._persistOpenMap[this.mode]
  793. .indexOf(this._rowMap[aIndex].id);
  794. if (index != -1)
  795. this._persistOpenMap[this.mode].splice(index, 1);
  796. // Notify the tree of changes
  797. if (this._tree) {
  798. this._tree.rowCountChanged(aIndex + 1, (-1) * count);
  799. this._tree.invalidateRow(aIndex);
  800. }
  801. } else {
  802. // We're opening the container. Add the children to our map
  803. // Note that these children may have been open when we were last closed,
  804. // and if they are, we also have to add those grandchildren to the map
  805. let oldCount = this._rowMap.length;
  806. function recursivelyAddToMap(aChild, aNewIndex, tree) {
  807. // When we add sub-children, we're going to need to increase our index
  808. // for the next add item at our own level
  809. let count = 0;
  810. if (aChild.children.length && aChild.open) {
  811. for (let [i, child] in Iterator(tree._rowMap[aNewIndex].children)) {
  812. count++;
  813. var index = Number(aNewIndex) + Number(i) + 1;
  814. tree._rowMap.splice(index, 0, child);
  815. let kidsAdded = recursivelyAddToMap(child, index, tree);
  816. count += kidsAdded;
  817. // Somehow the aNewIndex turns into a string without this
  818. aNewIndex = Number(aNewIndex) + kidsAdded;
  819. }
  820. }
  821. return count;
  822. }
  823. // work around bug 658534 by passing in "this" instead of let tree = this;
  824. recursivelyAddToMap(this._rowMap[aIndex], aIndex, this);
  825. // Add this folder to the persist map
  826. if (!this._persistOpenMap[this.mode])
  827. this._persistOpenMap[this.mode] = [];
  828. let id = this._rowMap[aIndex].id;
  829. if (this._persistOpenMap[this.mode].indexOf(id) == -1)
  830. this._persistOpenMap[this.mode].push(id);
  831. // Notify the tree of changes
  832. if (this._tree) {
  833. this._tree.rowCountChanged(aIndex + 1, this._rowMap.length - oldCount);
  834. this._tree.invalidateRow(aIndex);
  835. }
  836. // if this was a server that was expanded, let it update its counts
  837. let folder = this._rowMap[aIndex]._folder;
  838. if (aExpandServer) {
  839. if (folder.isServer)
  840. folder.server.performExpand(msgWindow);
  841. else if (folder instanceof Components.interfaces.nsIMsgImapMailFolder)
  842. folder.performExpand(msgWindow);
  843. }
  844. }
  845. },
  846. _subFoldersWithStringProperty: function ftv_subFoldersWithStringProperty(folder, folders, aFolderName, deep)
  847. {
  848. for each (let child in fixIterator(folder.subFolders, Components.interfaces.nsIMsgFolder)) {
  849. // if the folder selection is based on a string propery, use that
  850. if (aFolderName == getSmartFolderName(child)) {
  851. folders.push(child);
  852. // Add sub-folders if requested.
  853. if (deep)
  854. this.addSubFolders(child, folders);
  855. }
  856. else
  857. // if this folder doesn't have a property set, check Its children
  858. this._subFoldersWithStringProperty(child, folders, aFolderName, deep);
  859. }
  860. },
  861. _allFoldersWithStringProperty: function ftv_getAllFoldersWithProperty(accounts, aFolderName, deep)
  862. {
  863. let folders = [];
  864. for each (let acct in accounts) {
  865. let folder = acct.incomingServer.rootFolder;
  866. this._subFoldersWithStringProperty(folder, folders, aFolderName, deep);
  867. }
  868. return folders;
  869. },
  870. _allFoldersWithFlag: function ftv_getAllFolders(accounts, aFolderFlag, deep)
  871. {
  872. let folders = [];
  873. for each (let acct in accounts) {
  874. let foldersWithFlag = acct.incomingServer.rootFolder.getFoldersWithFlags(aFolderFlag);
  875. if (foldersWithFlag.length > 0) {
  876. for each (let folderWithFlag in fixIterator(foldersWithFlag.enumerate(),
  877. Components.interfaces.nsIMsgFolder)) {
  878. folders.push(folderWithFlag);
  879. // Add sub-folders of Sent and Archive to the result.
  880. if (deep && (aFolderFlag & (nsMsgFolderFlags.SentMail | nsMsgFolderFlags.Archive)))
  881. this.addSubFolders(folderWithFlag, folders);
  882. }
  883. }
  884. }
  885. return folders;
  886. },
  887. /**
  888. * get folders by flag or property based on the value of flag
  889. */
  890. _allSmartFolders: function ftv_allSmartFolders(accounts, flag, folderName, deep) {
  891. return flag ?
  892. gFolderTreeView._allFoldersWithFlag(accounts, flag, deep) :
  893. gFolderTreeView._allFoldersWithStringProperty(accounts, folderName, deep);
  894. },
  895. /**
  896. * Add a smart folder for folders with the passed flag set. But if there's
  897. * only one folder with the flag set, just put it at the top level.
  898. *
  899. * @param map array to add folder item to.
  900. * @param accounts array of accounts.
  901. * @param smartRootFolder root folder of the smart folders server
  902. * @param flag folder flag to create smart folders for
  903. * @param folderName name to give smart folder
  904. * @param position optional place to put folder item in map. If not specified,
  905. * folder item will be appended at the end of map.
  906. * @returns The smart folder's ftvItem if one was added, null otherwise.
  907. */
  908. _addSmartFoldersForFlag: function ftv_addSFForFlag(map, accounts, smartRootFolder,
  909. flag, folderName, position)
  910. {
  911. // If there's only one subFolder, just put it at the root.
  912. let subFolders = gFolderTreeView._allSmartFolders(accounts, flag, folderName, false);
  913. if (flag && subFolders.length == 1) {
  914. let folderItem = new ftvItem(subFolders[0]);
  915. folderItem._level = 0;
  916. if (flag & nsMsgFolderFlags.Inbox)
  917. folderItem.__defineGetter__("children", function() []);
  918. if (position == undefined)
  919. map.push(folderItem);
  920. else
  921. map[position] = folderItem;
  922. // No smart folder was added
  923. return null;
  924. }
  925. let smartFolder;
  926. try {
  927. let folderUri = smartRootFolder.URI + "/" + encodeURI(folderName);
  928. smartFolder = smartRootFolder.getChildWithURI(folderUri, false, true);
  929. } catch (ex) {
  930. smartFolder = null;
  931. };
  932. if (!smartFolder) {
  933. let searchFolders = gFolderTreeView._allSmartFolders(accounts, flag, folderName, true);
  934. let searchFolderURIs = "";
  935. for each (let searchFolder in searchFolders) {
  936. if (searchFolderURIs.length)
  937. searchFolderURIs += '|';
  938. searchFolderURIs += searchFolder.URI;
  939. }
  940. if (!searchFolderURIs.length)
  941. return;
  942. smartFolder = gFolderTreeView._createVFFolder(folderName, smartRootFolder,
  943. searchFolderURIs, flag);
  944. }
  945. let smartFolderItem = new ftvItem(smartFolder);
  946. smartFolderItem._level = 0;
  947. if (position == undefined)
  948. map.push(smartFolderItem);
  949. else
  950. map[position] = smartFolderItem;
  951. // Add the actual special folders as sub-folders of the saved search.
  952. // By setting _children directly, we bypass the normal calculation
  953. // of subfolders.
  954. smartFolderItem._children = [new ftvItem(f) for each (f in subFolders)];
  955. let prevChild = null;
  956. // Each child is a level one below the smartFolder
  957. for each (let child in smartFolderItem._children) {
  958. child._level = smartFolderItem._level + 1;
  959. child._parent = smartFolderItem;
  960. // don't show sub-folders of the inbox, but I think Archives/Sent, etc
  961. // should have the sub-folders.
  962. if (flag & nsMsgFolderFlags.Inbox)
  963. child.__defineGetter__("children", function() []);
  964. // If we have consecutive children with the same server, then both
  965. // should display as folder - server.
  966. if (prevChild && (child._folder.server == prevChild._folder.server)) {
  967. child.addServerName = true;
  968. prevChild.addServerName = true;
  969. prevChild.useServerNameOnly = false;
  970. }
  971. else if (flag)
  972. child.useServerNameOnly = true;
  973. else
  974. child.addServerName = true;
  975. prevChild = child;
  976. }
  977. // new custom folders from addons may contain lots of children, sort them
  978. if (flag == 0)
  979. sortFolderItems(smartFolderItem._children);
  980. return smartFolderItem;
  981. },
  982. _createVFFolder: function ftv_createVFFolder(newName, parentFolder,
  983. searchFolderURIs, folderFlag)
  984. {
  985. let newFolder;
  986. try {
  987. if (parentFolder instanceof(Components.interfaces.nsIMsgLocalMailFolder))
  988. newFolder = parentFolder.createLocalSubfolder(newName);
  989. else
  990. newFolder = parentFolder.addSubfolder(newName);
  991. newFolder.setFlag(nsMsgFolderFlags.Virtual);
  992. // provide a way to make the top level folder just a container, not
  993. // a search folder
  994. let type = this._modes["smart"].getSmartFolderTypeByName(newName);
  995. if (type[3]) { // isSearch
  996. let vfdb = newFolder.msgDatabase;
  997. let dbFolderInfo = vfdb.dBFolderInfo;
  998. // set the view string as a property of the db folder info
  999. // set the original folder name as well.
  1000. dbFolderInfo.setCharProperty("searchStr", "ALL");
  1001. dbFolderInfo.setCharProperty("searchFolderUri", searchFolderURIs);
  1002. dbFolderInfo.setUint32Property("searchFolderFlag", folderFlag);
  1003. dbFolderInfo.setBooleanProperty("searchOnline", true);
  1004. vfdb.summaryValid = true;
  1005. vfdb.Close(true);
  1006. }
  1007. parentFolder.NotifyItemAdded(newFolder);
  1008. Components.classes["@mozilla.org/messenger/account-manager;1"]
  1009. .getService(Components.interfaces.nsIMsgAccountManager)
  1010. .saveVirtualFolders();
  1011. }
  1012. catch(e) {
  1013. throw(e);
  1014. dump ("Exception : creating virtual folder \n");
  1015. }
  1016. return newFolder;
  1017. },
  1018. // We don't implement any of these at the moment
  1019. performAction: function ftv_performAction(aAction) {},
  1020. performActionOnCell: function ftv_performActionOnCell(aAction, aRow, aCol) {},
  1021. performActionOnRow: function ftv_performActionOnRow(aAction, aRow) {},
  1022. selectionChanged: function ftv_selectionChanged() {},
  1023. setCellText: function ftv_setCellText(aRow, aCol, aValue) {},
  1024. setCellValue: function ftv_setCellValue(aRow, aCol, aValue) {},
  1025. getCellValue: function ftv_getCellValue(aRow, aCol) {},
  1026. getColumnProperties: function ftv_getColumnProperties(aCol, aProps) {},
  1027. getImageSrc: function ftv_getImageSrc(aRow, aCol) {},
  1028. getProgressMode: function ftv_getProgressMode(aRow, aCol) {},
  1029. cycleCell: function ftv_cycleCell(aRow, aCol) {},
  1030. cycleHeader: function ftv_cycleHeader(aCol) {},
  1031. // ****************** End of nsITreeView implementation **************** //
  1032. //
  1033. // WARNING: Everything below this point is considered private. Touch at your
  1034. // own risk.
  1035. /**
  1036. * This is an array of all possible modes for the folder tree. You should not
  1037. * modify this directly, but rather use registerFolderTreeMode.
  1038. */
  1039. _modeNames: ["all", "unread", "favorite", "recent", "smart"],
  1040. _modeDisplayNames: {},
  1041. /**
  1042. * This is a javaascript map of which folders we had open, so that we can
  1043. * persist their state over-time. It is designed to be used as a JSON object.
  1044. */
  1045. _persistOpenMap: {},
  1046. _restoreOpenStates: function ftv__persistOpenStates() {
  1047. if (!(this.mode in this._persistOpenMap))
  1048. return;
  1049. let curLevel = 0;
  1050. let tree = this;
  1051. function openLevel() {
  1052. let goOn = false;
  1053. // We can't use a js iterator because we're changing the array as we go.
  1054. // So fallback on old trick of going backwards from the end, which
  1055. // doesn't care when you add things at the end.
  1056. for (let i = tree._rowMap.length - 1; i >= 0; i--) {
  1057. let row = tree._rowMap[i];
  1058. if (row.level != curLevel)
  1059. continue;
  1060. let map = tree._persistOpenMap[tree.mode];
  1061. if (map && map.indexOf(row.id) != -1) {
  1062. tree._toggleRow(i, false);
  1063. goOn = true;
  1064. }
  1065. }
  1066. // If we opened up any new kids, we need to check their level as well.
  1067. curLevel++;
  1068. if (goOn)
  1069. openLevel();
  1070. }
  1071. openLevel();
  1072. },
  1073. _tree: null,
  1074. selection: null,
  1075. /**
  1076. * An array of ftvItems, where each item corresponds to a row in the tree
  1077. */
  1078. _rowMap: null,
  1079. /**
  1080. * Completely discards the current tree and rebuilds it based on current
  1081. * settings
  1082. */
  1083. _rebuild: function ftv__rebuild() {
  1084. let newRowMap;
  1085. try {
  1086. newRowMap = this._modes[this.mode].generateMap(this);
  1087. } catch(ex) {
  1088. Components.classes["@mozilla.org/consoleservice;1"]
  1089. .getService(Components.interfaces.nsIConsoleService)
  1090. .logStringMessage("generator " + this.mode + " failed with exception: " + ex);
  1091. this.mode = "all";
  1092. newRowMap = this._modes[this.mode].generateMap(this);
  1093. }
  1094. let selectedFolders = this.getSelectedFolders();
  1095. if (this.selection)
  1096. this.selection.clearSelection();
  1097. // There's a chance the call to the map generator altered this._rowMap, so
  1098. // evaluate oldCount after calling it rather than before
  1099. let oldCount = this._rowMap ? this._rowMap.length : null;
  1100. this._rowMap = newRowMap;
  1101. let evt = document.createEvent("Events");
  1102. evt.initEvent("mapRebuild", true, false);
  1103. this._treeElement.dispatchEvent(evt);
  1104. if (this._tree)
  1105. {
  1106. if (oldCount !== null)
  1107. this._tree.rowCountChanged(0, this._rowMap.length - oldCount);
  1108. this._tree.invalidate();
  1109. }
  1110. this._restoreOpenStates();
  1111. // restore selection.
  1112. for (let [, folder] in Iterator(selectedFolders)) {
  1113. if (folder) {
  1114. let index = this.getIndexOfFolder(folder);
  1115. if (index != null)
  1116. this.selection.toggleSelect(index);
  1117. }
  1118. }
  1119. },
  1120. _sortedAccounts: function ftv_getSortedAccounts()
  1121. {
  1122. const Cc = Components.classes;
  1123. const Ci = Components.interfaces;
  1124. let acctMgr = Cc["@mozilla.org/messenger/account-manager;1"]
  1125. .getService(Ci.nsIMsgAccountManager);
  1126. let accounts = [a for each
  1127. (a in fixIterator(acctMgr.accounts, Ci.nsIMsgAccount))];
  1128. // Bug 41133 workaround
  1129. accounts = accounts.filter(function fix(a) { return a.incomingServer; });
  1130. // Don't show deferred pop accounts
  1131. accounts = accounts.filter(function isNotDeferred(a) {
  1132. let server = a.incomingServer;
  1133. return !(server instanceof Ci.nsIPop3IncomingServer &&
  1134. server.deferredToAccount);
  1135. });
  1136. // Don't show IM accounts
  1137. accounts = accounts.filter(function(a) a.incomingServer.type != "im");
  1138. function sortAccounts(a, b) {
  1139. if (a.key == acctMgr.defaultAccount.key)
  1140. return -1;
  1141. if (b.key == acctMgr.defaultAccount.key)
  1142. return 1;
  1143. let aIsNews = a.incomingServer.type == "nntp";
  1144. let bIsNews = b.incomingServer.type == "nntp";
  1145. if (aIsNews && !bIsNews)
  1146. return 1;
  1147. if (bIsNews && !aIsNews)
  1148. return -1;
  1149. let aIsLocal = a.incomingServer.type == "none";
  1150. let bIsLocal = b.incomingServer.type == "none";
  1151. if (aIsLocal && !bIsLocal)
  1152. return 1;
  1153. if (bIsLocal && !aIsLocal)
  1154. return -1;
  1155. return 0;
  1156. }
  1157. accounts.sort(sortAccounts);
  1158. return accounts;
  1159. },
  1160. /**
  1161. * Contains the set of modes registered with the folder tree, initially those
  1162. * included by default. This is a map from names of modes to their
  1163. * implementations of |IFolderTreeMode|.
  1164. */
  1165. _modes: {
  1166. /**
  1167. * The all mode returns all folders, arranged in a hierarchy
  1168. */
  1169. all: {
  1170. __proto__: IFolderTreeMode,
  1171. generateMap: function ftv_all_generateMap(ftv) {
  1172. let accounts = gFolderTreeView._sortedAccounts();
  1173. // force each root folder to do its local subfolder discovery.
  1174. MailUtils.discoverFolders();
  1175. return [new ftvItem(acct.incomingServer.rootFolder)
  1176. for each (acct in accounts)];
  1177. }
  1178. },
  1179. /**
  1180. * The unread mode returns all folders that are not root-folders and that
  1181. * have unread items. Also always keep the currently selected folder
  1182. * so it doesn't disappear under the user.
  1183. */
  1184. unread: {
  1185. __proto__: IFolderTreeMode,
  1186. generateMap: function ftv_unread_generateMap(ftv) {
  1187. let map = [];
  1188. let currentFolder = gFolderTreeView.getSelectedFolders()[0];
  1189. const outFolderFlagMask = nsMsgFolderFlags.SentMail |
  1190. nsMsgFolderFlags.Drafts | nsMsgFolderFlags.Queue |
  1191. nsMsgFolderFlags.Templates;
  1192. for each (let folder in ftv._enumerateFolders) {
  1193. if (!folder.isSpecialFolder(outFolderFlagMask, true) &&
  1194. (!folder.isServer && folder.getNumUnread(false) > 0) ||
  1195. (folder == currentFolder))
  1196. map.push(new ftvItem(folder));
  1197. }
  1198. // There are no children in this view!
  1199. for each (let folder in map) {
  1200. folder.__defineGetter__("children", function() []);
  1201. folder.addServerName = true;
  1202. }
  1203. sortFolderItems(map);
  1204. return map;
  1205. },
  1206. getParentOfFolder: function ftv_unread_getParentOfFolder(aFolder) {
  1207. // This is a flat view, so no folders have parents.
  1208. return null;
  1209. }
  1210. },
  1211. /**
  1212. * The favorites mode returns all folders whose flags are set to include
  1213. * the favorite flag
  1214. */
  1215. favorite: {
  1216. __proto__: IFolderTreeMode,
  1217. generateMap: function ftv_favorite_generateMap(ftv) {
  1218. let faves = [];
  1219. for each (let folder in ftv._enumerateFolders) {