Bug 464973 - add back folder columns with count of messages and folder size to the folder pane. r=mkmelin, r=rkent
authoraceman <acelists@atlas.sk>
Fri, 20 Feb 2015 22:05:31 +0100
changeset 17525 6e2b817f4dcc021e62ae9de5a1ac38c7244fa9a5
parent 17524 efc5c45e0ffc4ba58eb4a9bb4515657cf7b2599e
child 17526 bd2fc52626fce556d8a5e2f25658a616090424ed
push id10788
push userarchaeopteryx@coole-files.de
push dateFri, 20 Feb 2015 21:05:55 +0000
treeherdercomm-central@6e2b817f4dcc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmkmelin, rkent
bugs464973
Bug 464973 - add back folder columns with count of messages and folder size to the folder pane. r=mkmelin, r=rkent
mail/app/profile/all-thunderbird.js
mail/base/content/folderPane.js
mail/base/content/mail3PaneWindowCommands.js
mail/base/content/mailContextMenus.js
mail/base/content/mailTabs.js
mail/base/content/mailWindowOverlay.js
mail/base/content/mailWindowOverlay.xul
mail/base/content/messageWindow.js
mail/base/content/messenger.xul
mail/locales/en-US/chrome/messenger/messenger.dtd
mail/locales/en-US/chrome/messenger/messenger.properties
mailnews/base/util/nsMsgDBFolder.cpp
mailnews/imap/src/nsImapMailFolder.cpp
mailnews/local/src/nsLocalMailFolder.cpp
mailnews/news/src/nsNewsFolder.cpp
--- a/mail/app/profile/all-thunderbird.js
+++ b/mail/app/profile/all-thunderbird.js
@@ -220,16 +220,24 @@ pref("general.autoScroll", false);
 pref("general.autoScroll", true);
 #endif
 
 pref("mail.shell.checkDefaultClient", true);
 pref("mail.spellcheck.inline", true);
 
 pref("mail.folder.views.version", 0);
 
+pref("mail.folderpane.showColumns", false);
+// Force the unit shown for the size of all folders. If empty, the unit
+// is determined automatically for each folder. Allowed values: KB/MB/<empty string>
+pref("mail.folderpane.sizeUnits", "");
+// Summarize messages count and size of subfolders into a collapsed parent?
+// Allowed values: true/false
+pref("mail.folderpane.sumSubfolders", true);
+
 // target folder URI used for the last move or copy
 pref("mail.last_msg_movecopy_target_uri", "");
 // last move or copy operation was a move
 pref("mail.last_msg_movecopy_was_move", true);
 
 //Set the font color for links to something lighter
 pref("browser.anchor_color", "#0B6CDA");
 
--- a/mail/base/content/folderPane.js
+++ b/mail/base/content/folderPane.js
@@ -112,24 +112,27 @@ let IFolderTreeMode = {
   }
 };
 
 /**
  * This is our controller for the folder-tree. It includes our nsITreeView
  * implementation, as well as other control functions.
  */
 let gFolderTreeView = {
+  messengerBundle: null,
+
   /**
    * 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;
+    this.messengerBundle = document.getElementById("bundle_messenger");
 
     // 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 {
@@ -144,17 +147,17 @@ let gFolderTreeView = {
     }
 
     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);
+          string = this.messengerBundle.getString(key);
         }
       document.getElementById('folderpane-title').value = string;
     }
 
     if (aJSONFile) {
       // Parse our persistent-open-state json file
       let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
       file.append(aJSONFile);
@@ -172,28 +175,31 @@ let gFolderTreeView = {
           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]));
+            gFolderTreeView.messengerBundle
+                           .getFormattedString("failedToReadFile", [aJSONFile, x]));
         }
       }
     }
 
     // Load our data
     this._updateCompactState(this.mode);
     this._rebuild();
     // And actually draw the tree
     aTree.view = this;
 
+    this.toggleCols(true);
+    gFolderStatsHelpers.init();
+
     // Add this listener so that we can update the tree when things change
     MailServices.mailSession.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.
    */
@@ -296,16 +302,45 @@ let gFolderTreeView = {
   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;
   },
 
   /**
+   * Toggle displaying the headers of columns in the folder pane.
+   * @param aSetup  Set to true if the columns should be set up according
+   *                to the pref, not toggle them.
+   */
+  toggleCols: function(aSetup = false) {
+    let hide = Services.prefs.getBoolPref("mail.folderpane.showColumns");
+    if (aSetup)
+      hide = !hide;
+    this._treeElement.setAttribute("hidecolumnpicker", hide ? "true" : "false");
+    for (let columnName of ["folderNameCol", "folderUnreadCol",
+                            "folderTotalCol", "folderSizeCol"])
+    {
+      let column = document.getElementById(columnName);
+      if (hide) {
+        column.setAttribute("hideheader", "true");
+        column.removeAttribute("label");
+        if (columnName != "folderNameCol")
+          column.setAttribute("hidden", "true");
+      } else {
+        column.setAttribute("label", column.getAttribute("label2"));
+        column.setAttribute("hideheader", "false");
+      }
+    }
+
+    if (!aSetup)
+      Services.prefs.setBoolPref("mail.folderpane.showColumns", !hide);
+  },
+
+  /**
    * Toggles the compact view of the current mode.
    *
    * @param aCompact  Boolean telling whether compact view should be enabled.
    */
   toggleCompact: function(aCompact) {
     let targetMode = this.fullMode(this.baseMode(), aCompact);
     this.mode = targetMode;
   },
@@ -387,17 +422,17 @@ let gFolderTreeView = {
     this._updateCompactState(this._mode);
 
     // Accept the mode and set up labels.
     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);
+      string = gFolderTreeView.messengerBundle.getString(key);
     }
     document.getElementById('folderpane-title').value = string;
 
     // Store current mode and actually build the folder pane.
     this._treeElement.setAttribute("mode", this._mode);
     this._rebuild();
   },
 
@@ -827,18 +862,21 @@ let gFolderTreeView = {
   getCellProperties: function ftv_getCellProperties(aRow, aCol) {
     return this._rowMap[aRow].getProperties(aCol);
   },
 
   /**
    * The actual text to display in the tree
    */
   getCellText: function ftv_getCellText(aRow, aCol) {
-    if (aCol.id == "folderNameCol")
-      return this._rowMap[aRow].text;
+    if ((aCol.id == "folderNameCol") ||
+        (aCol.id == "folderUnreadCol") ||
+        (aCol.id == "folderTotalCol") ||
+        (aCol.id == "folderSizeCol"))
+      return this._rowMap[aRow].getText(aCol.id);
     return "";
   },
 
   /**
    * For feed folders get, cache, and return a favicon. Otherwise return "" to
    * let css set the image per nsITreeView requirements.
    */
   getImageSrc: function(aRow, aCol) {
@@ -1759,18 +1797,18 @@ let gFolderTreeView = {
         return smartRoot.getChildWithURI(smartRoot.URI + "/" + encodeURI(aName), false,
                                          true);
       },
 
       generateMap: function ftv_smart_generateMap(ftv) {
         let map = [];
         let accounts = gFolderTreeView._sortedAccounts();
         let smartServer = this._smartServer;
-        smartServer.prettyName = document.getElementById("bundle_messenger")
-                                         .getString("unifiedAccountName");
+        smartServer.prettyName = gFolderTreeView.messengerBundle
+                                                .getString("unifiedAccountName");
         smartServer.canHaveFilters = false;
 
         let smartRoot = smartServer.rootFolder;
         let smartChildren = [];
         for (let [flag, name,,] of this._flagNameList) {
           gFolderTreeView._addSmartFoldersForFlag(smartChildren, accounts,
                                                   smartRoot, flag, name);
         }
@@ -2158,37 +2196,93 @@ ftvItem.prototype = {
   open: false,
   addServerName: false,
   useServerNameOnly: false,
 
   get id() {
     return this._folder.URI;
   },
   get text() {
-    let text;
-    if (this.useServerNameOnly) {
-      text = this._folder.server.prettyName;
+    return this.getText("folderNameCol");
+  },
+
+  getText(aColName) {
+    // Only show counts / total size of subtree if the pref is set,
+    // we are in "All folders" mode and this folder row is not expanded.
+    gFolderStatsHelpers.sumSubfolders = gFolderStatsHelpers.sumSubfoldersPref &&
+                          (gFolderTreeView.mode == kDefaultMode) &&
+                          this._folder.hasSubFolders && !this.open;
+
+    switch (aColName) {
+      case "folderNameCol":
+        let text;
+        if (this.useServerNameOnly)
+          text = this._folder.server.prettyName;
+        else {
+          text = this._folder.abbreviatedName;
+          if (this.addServerName)
+            text = gFolderTreeView.messengerBundle.getFormattedString(
+              "folderWithAccount", [text, this._folder.server.prettyName]);
+        }
+
+        // If the unread column is shown, we don't need to add the count
+        // to the name.
+        if (!document.getElementById("folderUnreadCol").hidden)
+          return text;
+
+        let unread = this._folder.getNumUnread(gFolderStatsHelpers.sumSubfolders);
+        if (unread > 0)
+          text = gFolderTreeView.messengerBundle
+            .getFormattedString("folderWithUnreadMsgs",
+                                [text, gFolderStatsHelpers.addSummarizedPrefix(unread)]);
+        return text;
+
+      case "folderUnreadCol":
+        return gFolderStatsHelpers
+                 .fixNum(this._folder.getNumUnread(gFolderStatsHelpers.sumSubfolders));
+
+      case "folderTotalCol":
+        return gFolderStatsHelpers
+                 .fixNum(this._folder.getTotalMessages(gFolderStatsHelpers.sumSubfolders));
+
+      case "folderSizeCol":
+        let size = gFolderStatsHelpers.getFolderSize(this._folder);
+        if (size == 0)
+          return "";
+        if (size == gFolderStatsHelpers.kUnknownSize)
+          return size;
+
+        // If size is non-zero try to show it in a unit that fits in 3 digits,
+        // but if user specified a fixed unit, use that.
+        size = Math.round(size / 1024);
+        let units = gFolderStatsHelpers.kiloUnit;
+        if (gFolderStatsHelpers.sizeUnits != "KB" &&
+            (size > 999 || gFolderStatsHelpers.sizeUnits == "MB")) {
+          size = Math.round(size / 1024);
+          units = gFolderStatsHelpers.megaUnit;
+        }
+
+        // This needs to be updated if the "%.*f" placeholder string
+        // in "*ByteAbbreviation2" in messenger.properties changes.
+        return gFolderStatsHelpers
+                 .addSummarizedPrefix(units.replace("%.*f", size).replace(" ",""));
+
+        default:
+        return "";
     }
-    else {
-      text = this._folder.abbreviatedName;
-      if (this.addServerName)
-        text += " - " + this._folder.server.prettyName;
-    }
-    // Yeah, we hard-code this, but so did the old code...
-    let unread = this._folder.getNumUnread(!this.open);
-    if (unread > 0)
-      text += " (" + unread + ")";
-    return text;
   },
 
   get level() {
     return this._level;
   },
 
-  getProperties: function ftvItem_getProperties() {
+  getProperties: function (aColumn) {
+    if (aColumn && aColumn.id != "folderNameCol")
+      return "";
+
     // From folderUtils.jsm
     let properties = getFolderProperties(this._folder, this.open);
     if (this._folder.getFlag(nsMsgFolderFlags.Virtual)) {
       properties += " specialFolder-Smart";
       // a second possibility for customized smart folders
       properties += " specialFolder-" + this._folder.name.replace(' ','');
     }
     // if there is a smartFolder name property, add it
@@ -2300,18 +2394,18 @@ let gFolderTreeController = {
       return;
     }
 
     if (folder.flags & nsMsgFolderFlags.Virtual) {
       this.editVirtualFolder(folder);
       return;
     }
 
-    let title = document.getElementById("bundle_messenger")
-                        .getString("folderProperties");
+    let title = gFolderTreeView.messengerBundle
+                               .getString("folderProperties");
 
     //xxx useless param
     function editFolderCallback(aNewName, aOldName, aUri) {
       if (aNewName != aOldName)
         folder.rename(aNewName, msgWindow);
     }
 
     //xxx useless param
@@ -2405,20 +2499,20 @@ let gFolderTreeController = {
 
     var canDelete = (folder.isSpecialFolder(nsMsgFolderFlags.Junk, false)) ?
       CanRenameDeleteJunkMail(folder.URI) : folder.deletable;
 
     if (!canDelete)
       throw new Error("Can't delete folder: " + folder.name);
 
     if (folder.flags & nsMsgFolderFlags.Virtual) {
-      let confirmation = document.getElementById("bundle_messenger")
-                                 .getString("confirmSavedSearchDeleteMessage");
-      let title = document.getElementById("bundle_messenger")
-                          .getString("confirmSavedSearchTitle");
+      let confirmation = gFolderTreeView.messengerBundle
+                                        .getString("confirmSavedSearchDeleteMessage");
+      let title = gFolderTreeView.messengerBundle
+                                 .getString("confirmSavedSearchTitle");
       if (Services.prompt
             .confirmEx(window, title, confirmation,
                        Services.prompt.STD_YES_NO_BUTTONS + Services.prompt.BUTTON_POS_1_DEFAULT,
                        "", "", "", "", {}) != 0) /* the yes button is in position 0 */
         return;
     }
 
     let array = toXPCOMArray([folder], Ci.nsIMutableArray);
@@ -2584,25 +2678,25 @@ let gFolderTreeController = {
 
     let showPrompt = true;
     try {
       showPrompt = !Services.prefs.getBoolPref("mailnews." + aCommand + ".dontAskAgain");
     } catch (ex) {}
 
     if (showPrompt) {
       let checkbox = {value:false};
-      let bundle = document.getElementById("bundle_messenger");
-      let title = bundle.getFormattedString(aCommand + "FolderTitle", [aFolder.prettyName]);
-      let msg = bundle.getString(aCommand + "FolderMessage");
+      let title = gFolderTreeView.messengerBundle
+        .getFormattedString(aCommand + "FolderTitle", [aFolder.prettyName]);
+      let msg = gFolderTreeView.messengerBundle.getString(aCommand + "FolderMessage");
       let ok = Services.prompt.confirmEx(window,
                                          title,
                                          msg,
                                          Services.prompt.STD_YES_NO_BUTTONS,
                                          null, null, null,
-                                         bundle.getString(aCommand + "DontAsk"),
+                                         gFolderTreeView.messengerBundle.getString(aCommand + "DontAsk"),
                                          checkbox) == 0;
       if (checkbox.value)
         Services.prefs.setBoolPref("mailnews." + aCommand + ".dontAskAgain", true);
       if (!ok)
         return false;
     }
     return true;
   },
@@ -2673,8 +2767,79 @@ function getSmartFolderName(aFolder) {
   try {
     return aFolder.getStringProperty("smartFolderName");
   } catch (ex) {
     Components.utils.reportError(ex);
     return null;
   }
 }
 
+var gFolderStatsHelpers = {
+    kUnknownSize: "-",
+    sumSubfoldersPref: false,
+    sumSubfolders: false,
+    sizeUnits: "",
+    kiloUnit: "KB",
+    megaUnit: "MB",
+
+    init: function() {
+      // We cache these values because the cells in the folder pane columns
+      // using these helpers can be redrawn often.
+      this.sumSubfoldersPref = Services.prefs.getBoolPref("mail.folderpane.sumSubfolders");
+      this.sizeUnits = Services.prefs.getCharPref("mail.folderpane.sizeUnits");
+      this.kiloUnit = gFolderTreeView.messengerBundle.getString("kiloByteAbbreviation2");
+      this.megaUnit = gFolderTreeView.messengerBundle.getString("megaByteAbbreviation2");
+    },
+
+    /**
+     * Add a prefix to denote the value is actually a sum of all the subfolders.
+     * The prefix is useful as this sum may not always be the exact sum of individual
+     * folders when they are shown expanded (due to rounding to a unit).
+     * E.g. folder1 600bytes -> 1KB, folder2 700bytes -> 1KB
+     * summarized at parent folder: 1300bytes -> 1KB
+     */
+    addSummarizedPrefix: function(aValue) {
+      if (!this.sumSubfolders)
+        return aValue;
+
+      return gFolderTreeView.messengerBundle
+        .getFormattedString("folderSummarizedValue", [aValue]);
+    },
+
+    /**
+     * nsIMsgFolder uses -1 as a magic number to mean "I don't know". In those
+     * cases we indicate it to the user. The user has to open the folder
+     * so that the property is initialized from the DB.
+     */
+    fixNum: function(aNumber) {
+      if (aNumber < 0)
+        return this.kUnknownSize;
+
+      return (aNumber == 0 ? "" : this.addSummarizedPrefix(aNumber));
+    },
+
+    /**
+     * Recursively get the size of specified folder and all its subfolders.
+     */
+    getFolderSize: function(aFolder) {
+      let size = 0;
+      try {
+        size = aFolder.sizeOnDisk;
+        if (size < 0)
+          return this.kUnknownSize;
+      } catch(ex) {
+        return this.kUnknownSize;
+      }
+      if (this.sumSubfolders && aFolder.hasSubFolders) {
+        let subFolders = aFolder.subFolders;
+        while (subFolders.hasMoreElements()) {
+          let subFolder = subFolders.getNext()
+            .QueryInterface(Components.interfaces.nsIMsgFolder);
+          let subSize = this.getFolderSize(subFolder);
+          if (subSize == this.kUnknownSize)
+            return subSize;
+
+          size += subSize;
+        }
+      }
+      return size;
+    }
+};
--- a/mail/base/content/mail3PaneWindowCommands.js
+++ b/mail/base/content/mail3PaneWindowCommands.js
@@ -158,16 +158,17 @@ var DefaultController =
       case "cmd_goForward":
       case "cmd_goBack":
       case "cmd_goStartPage":
       case "cmd_undoCloseTab":
       case "cmd_viewClassicMailLayout":
       case "cmd_viewWideMailLayout":
       case "cmd_viewVerticalMailLayout":
       case "cmd_toggleFolderPane":
+      case "cmd_toggleFolderPaneCols":
       case "cmd_toggleMessagePane":
       case "cmd_viewAllMsgs":
       case "cmd_viewUnreadMsgs":
       case "cmd_viewThreadsWithUnread":
       case "cmd_viewWatchedThreadsWithUnread":
       case "cmd_viewIgnoredThreads":
       case "cmd_undo":
       case "cmd_redo":
@@ -464,16 +465,17 @@ var DefaultController =
         return gFolderDisplay.view.showThreaded;
       case "cmd_nextFlaggedMsg":
       case "cmd_previousFlaggedMsg":
         return IsViewNavigationItemEnabled();
       case "cmd_viewClassicMailLayout":
       case "cmd_viewWideMailLayout":
       case "cmd_viewVerticalMailLayout":
       case "cmd_toggleFolderPane":
+      case "cmd_toggleFolderPaneCols":
       case "cmd_toggleMessagePane":
         // this is overridden per-mail tab
         return true;
       case "cmd_viewAllMsgs":
       case "cmd_viewIgnoredThreads":
         return gDBView;
       case "cmd_viewUnreadMsgs":
       case "cmd_viewThreadsWithUnread":
@@ -726,16 +728,19 @@ var DefaultController =
       case "cmd_viewClassicMailLayout":
       case "cmd_viewWideMailLayout":
       case "cmd_viewVerticalMailLayout":
         ChangeMailLayoutForCommand(command);
         break;
       case "cmd_toggleFolderPane":
         MsgToggleFolderPane();
         break;
+      case "cmd_toggleFolderPaneCols":
+        gFolderTreeView.toggleCols();
+        break;
       case "cmd_toggleMessagePane":
         MsgToggleMessagePane();
         break;
       case "cmd_viewAllMsgs":
       case "cmd_viewThreadsWithUnread":
       case "cmd_viewWatchedThreadsWithUnread":
       case "cmd_viewUnreadMsgs":
       case "cmd_viewIgnoredThreads":
--- a/mail/base/content/mailContextMenus.js
+++ b/mail/base/content/mailContextMenus.js
@@ -284,18 +284,39 @@ function CheckForMessageIdInFolder(folde
   return messageHeader;
 }
 
 function folderPaneOnPopupHiding()
 {
   RestoreSelectionWithoutContentLoad(document.getElementById("folderTree"));
 }
 
-function fillFolderPaneContextMenu()
+function fillFolderPaneContextMenu(aEvent)
 {
+  let target = document.popupNode;
+  // If a column header was clicked, show the column picker.
+  if (target.localName == "treecol") {
+    let treecols = target.parentNode;
+    let nodeList = document.getAnonymousNodes(treecols);
+    let treeColPicker = null;
+    for (let i = 0; i < nodeList.length; i++) {
+      if (nodeList.item(i).localName == "treecolpicker") {
+        treeColPicker = nodeList.item(i);
+        break;
+      }
+    }
+    if (treeColPicker) {
+      let popup = document.getAnonymousElementByAttribute(treeColPicker, "anonid", "popup");
+      treeColPicker.buildPopup(popup);
+      popup.openPopup(target, "after_start", 0, 0, true);
+    }
+    return false;
+  }
+
+  // Do not show menu if rows are selected.
   var bundle = document.getElementById("bundle_messenger");
   var folders = gFolderTreeView.getSelectedFolders();
   if (!folders.length)
     return false;
 
   var numSelected = folders.length;
 
   function checkIsVirtualFolder(folder) {
@@ -501,17 +522,17 @@ function fillFolderPaneContextMenu()
   }
 
   // Hide / Show our menu separators based on the menu items we are showing.
   ShowMenuItem("folderPaneContext-sep1", selectedServers.length == 0);
   hideIfAppropriate("folderPaneContext-sep1");
   hideIfAppropriate("folderPaneContext-sep2");
   hideIfAppropriate("folderPaneContext-sep3");
 
-  return(true);
+  return true;
 }
 
 function ShowMenuItem(id, showItem)
 {
   document.getElementById(id).hidden = !showItem;
 }
 
 function EnableMenuItem(id, enableItem)
--- a/mail/base/content/mailTabs.js
+++ b/mail/base/content/mailTabs.js
@@ -746,31 +746,33 @@ let mailTabType = {
   // nsIController implementation
 
   supportsCommand: function mailTabType_supportsCommand(aCommand, aTab) {
     switch (aCommand) {
       case "cmd_viewClassicMailLayout":
       case "cmd_viewWideMailLayout":
       case "cmd_viewVerticalMailLayout":
       case "cmd_toggleFolderPane":
+      case "cmd_toggleFolderPaneCols":
       case "cmd_toggleMessagePane":
         return true;
 
       default:
         return DefaultController.supportsCommand(aCommand);
     }
   },
 
   // We only depend on what's illegal
   isCommandEnabled: function mailTabType_isCommandEnabled(aCommand, aTab) {
     switch (aCommand) {
       case "cmd_viewClassicMailLayout":
       case "cmd_viewWideMailLayout":
       case "cmd_viewVerticalMailLayout":
       case "cmd_toggleFolderPane":
+      case "cmd_toggleFolderPaneCols":
       case "cmd_toggleMessagePane":
         // If the thread pane is illegal, these are all disabled
         if (!aTab.mode.legalPanes.thread)
           return false;
         // else fall through
 
       default:
         return DefaultController.isCommandEnabled(aCommand);
--- a/mail/base/content/mailWindowOverlay.js
+++ b/mail/base/content/mailWindowOverlay.js
@@ -188,16 +188,27 @@ function view_init()
     folderPaneMenuItem.setAttribute("checked", gFolderDisplay.folderPaneVisible);
   }
 
   let folderPaneAppMenuItem = document.getElementById("appmenu_showFolderPane");
   if (!folderPaneAppMenuItem.hidden) { // Hidden in the standalone msg window.
     folderPaneAppMenuItem.setAttribute("checked", gFolderDisplay.folderPaneVisible);
   }
 
+  let colsEnabled = Services.prefs.getBoolPref("mail.folderpane.showColumns");
+  let folderPaneColsMenuItem = document.getElementById("menu_showFolderPaneCols");
+  if (!folderPaneColsMenuItem.hidden) { // Hidden in the standalone msg window.
+    folderPaneColsMenuItem.setAttribute("checked", colsEnabled);
+  }
+
+  folderPaneColsMenuItem = document.getElementById("appmenu_showFolderPaneCols");
+  if (!folderPaneColsMenuItem.hidden) { // Hidden in the standalone msg window.
+    folderPaneColsMenuItem.setAttribute("checked", colsEnabled);
+  }
+
   // Disable some menus if account manager is showing
   document.getElementById("viewSortMenu").disabled = accountCentralDisplayed;
 
   let appmenuViewSort = document.getElementById("appmenu_viewSortMenu");
   if (appmenuViewSort)
     appmenuViewSort.disabled = accountCentralDisplayed;
 
   document.getElementById("viewMessageViewMenu").disabled = accountCentralDisplayed;
--- a/mail/base/content/mailWindowOverlay.xul
+++ b/mail/base/content/mailWindowOverlay.xul
@@ -127,16 +127,17 @@
    <command id="cmd_setFolderCharset" oncommand="goDoCommand('cmd_setFolderCharset')" />
 
    <command id="cmd_expandAllThreads" oncommand="goDoCommand('cmd_expandAllThreads')" disabled="true"/>
    <command id="cmd_collapseAllThreads" oncommand="goDoCommand('cmd_collapseAllThreads')" disabled="true"/>
    <command id="cmd_viewClassicMailLayout" oncommand="goDoCommand('cmd_viewClassicMailLayout')" disabled="true"/>
    <command id="cmd_viewWideMailLayout" oncommand="goDoCommand('cmd_viewWideMailLayout')" disabled="true"/>
    <command id="cmd_viewVerticalMailLayout" oncommand="goDoCommand('cmd_viewVerticalMailLayout')" disabled="true"/>
    <command id="cmd_toggleFolderPane" oncommand="goDoCommand('cmd_toggleFolderPane')" disabled="true"/>
+   <command id="cmd_toggleFolderPaneCols" oncommand="goDoCommand('cmd_toggleFolderPaneCols')" disabled="true"/>
    <command id="cmd_toggleMessagePane" oncommand="goDoCommand('cmd_toggleMessagePane')" disabled="true"/>
    <command id="cmd_viewAllMsgs" oncommand="goDoCommand('cmd_viewAllMsgs')" disabled="true"/>
    <command id="cmd_viewUnreadMsgs" oncommand="goDoCommand('cmd_viewUnreadMsgs')" disabled="true"/>
    <command id="cmd_viewThreadsWithUnread" oncommand="goDoCommand('cmd_viewThreadsWithUnread')" disabled="true"/>
    <command id="cmd_viewWatchedThreadsWithUnread" oncommand="goDoCommand('cmd_viewWatchedThreadsWithUnread')" disabled="true"/>
    <command id="cmd_viewIgnoredThreads" oncommand="goDoCommand('cmd_viewIgnoredThreads')" disabled="true"/>
    <commandset id="viewZoomCommands"
                commandupdater="true"
@@ -896,17 +897,17 @@
     <menuitem id="mailContext-reportPhishingURL"
               label="&reportPhishingURL.label;"
               accesskey="&reportPhishingURL.accesskey;"
               oncommand="gPhishingDetector.reportPhishingURL(gContextMenu.linkURL);"/>
 
   </menupopup>
 
   <menupopup id="folderPaneContext"
-             onpopupshowing="return fillFolderPaneContextMenu();"
+             onpopupshowing="return fillFolderPaneContextMenu(event);"
              onpopuphiding="if (event.target == this) folderPaneOnPopupHiding();">
     <menuitem id="folderPaneContext-getMessages"
               label="&folderContextGetMessages.label;"
               accesskey="&folderContextGetMessages.accesskey;"
               oncommand="MsgGetMessage();"/>
     <menuitem id="folderPaneContext-openNewTab"
               label="&folderContextOpenNewTab.label;"
               accesskey="&folderContextOpenNewTab.accesskey;"
@@ -1341,16 +1342,20 @@
                           label="&messagePaneVertical.label;"
                           name="viewlayoutgroup"
                           command="cmd_viewVerticalMailLayout"/>
                 <menuseparator id="appmenu_viewMenuAfterPaneVerticalSeparator"/>
                 <menuitem id="appmenu_showFolderPane"
                           type="checkbox"
                           label="&showFolderPaneCmd.label;"
                           command="cmd_toggleFolderPane"/>
+                <menuitem id="appmenu_showFolderPaneCols"
+                          type="checkbox"
+                          label="&showFolderPaneColsCmd.label;"
+                          command="cmd_toggleFolderPaneCols"/>
                 <menuitem id="appmenu_showMessage"
                           type="checkbox"
                           label="&showMessageCmd.label;"
                           key="key_toggleMessagePane"
                           command="cmd_toggleMessagePane"/>
               </menupopup>
             </menu>
           </menupopup>
@@ -2363,16 +2368,18 @@
                       accesskey="&messagePaneClassic.accesskey;" command="cmd_viewClassicMailLayout"/>
             <menuitem id="messagePaneWide" type="radio" label="&messagePaneWide.label;" name="viewlayoutgroup"
                       accesskey="&messagePaneWide.accesskey;" command="cmd_viewWideMailLayout"/>
             <menuitem id="messagePaneVertical" type="radio" label="&messagePaneVertical.label;" name="viewlayoutgroup"
                       accesskey="&messagePaneVertical.accesskey;" command="cmd_viewVerticalMailLayout"/>
             <menuseparator id="viewMenuAfterPaneVerticalSeparator"/>
             <menuitem id="menu_showFolderPane" type="checkbox" label="&showFolderPaneCmd.label;"
                       accesskey="&showFolderPaneCmd.accesskey;" command="cmd_toggleFolderPane"/>
+            <menuitem id="menu_showFolderPaneCols" type="checkbox" label="&showFolderPaneColsCmd.label;"
+                      accesskey="&showFolderPaneColsCmd.accesskey;" command="cmd_toggleFolderPaneCols"/>
             <menuitem id="menu_showMessage" type="checkbox" label="&showMessageCmd.label;" key="key_toggleMessagePane"
                       accesskey="&showMessageCmd.accesskey;" command="cmd_toggleMessagePane"/>
           </menupopup>
         </menu>
         <menu id="menu_FolderViews" label="&folderView.label;" accesskey="&folderView.accesskey;">
           <menupopup id="menu_FolderViewsPopup"
                      onpopupshowing="InitViewFolderViewsMenu(event)">
             <menuitem id="menu_allFolders" value="all"
--- a/mail/base/content/messageWindow.js
+++ b/mail/base/content/messageWindow.js
@@ -542,16 +542,24 @@ function HideMenus()
   var folderPane_menuitem=document.getElementById('menu_showFolderPane');
   if (folderPane_menuitem)
     folderPane_menuitem.setAttribute("hidden", "true");
 
   folderPane_menuitem = document.getElementById('appmenu_showFolderPane');
   if (folderPane_menuitem)
     folderPane_menuitem.setAttribute("hidden", "true");
 
+  let folderPaneCols_menuitem = document.getElementById("menu_showFolderPaneCols");
+  if (folderPaneCols_menuitem)
+    folderPaneCols_menuitem.setAttribute("hidden", "true");
+
+  folderPaneCols_menuitem = document.getElementById("appmenu_showFolderPaneCols");
+  if (folderPaneCols_menuitem)
+    folderPaneCols_menuitem.setAttribute("hidden", "true");
+
   var showSearch_showMessage_Separator = document.getElementById('menu_showSearch_showMessage_Separator');
   if (showSearch_showMessage_Separator)
     showSearch_showMessage_Separator.setAttribute("hidden", "true");
 
   var expandOrCollapseMenu = document.getElementById('menu_expandOrCollapse');
   if (expandOrCollapseMenu)
     expandOrCollapseMenu.setAttribute("hidden", "true");
 
--- a/mail/base/content/messenger.xul
+++ b/mail/base/content/messenger.xul
@@ -357,17 +357,17 @@
              the folder pane next to the thread view, with the message pane/reader
              beneath both of them. -->
         <box id="mailContent" orient="vertical" flex="1">
           <!-- mail-toolbox is provided by mailWindowOverlay.xul -->
           <toolbox id="mail-toolbox" />
 
           <box id="messengerBox" orient="horizontal" flex="1" minheight="100" height="100" persist="height">
             <vbox id="folderPaneBox" minwidth="125" width="200" persist="width">
-              <label id="folderColumnLabel" hidden="true" value="&folderColumn.label;"/>
+              <label id="folderColumnLabel" hidden="true" value="&folderNameColumn.label;"/>
               <sidebarheader id="folderPaneHeader" hidden="true" align="center">
                 <label id="folderpane-title" crop="end" flex="1"/>
                 <toolbarbutton id="folderview-cycler-prev"
                                dir="prev"
                                class="folderview-cycler"
                                onclick="gFolderTreeView.cycleMode(false);"/>
                 <toolbarbutton id="folderview-cycler-next"
                                dir="next"
@@ -381,24 +381,50 @@
                     context="folderPaneContext"
                     disableKeyNavigation="true"
                     ondraggesture="gFolderTreeView._onDragStart(event);"
                     ondragover="gFolderTreeView._onDragOver(event);"
                     ondblclick="gFolderTreeView.onDoubleClick(event);"
                     onselect="FolderPaneSelectionChange();">
                 <treecols id="folderPaneCols">
                   <treecol id="folderNameCol"
+                           label2="&folderNameColumn.label;"
                            flex="5"
                            crop="center"
                            persist="width"
                            hideheader="true"
                            ignoreincolumnpicker="true"
-                           primary="true"
-                           sortActive="true"
-                           sortDirection="ascending"/>
+                           primary="true"/>
+                  <splitter class="tree-splitter"/>
+                  <treecol id="folderUnreadCol"
+                           label2="&folderUnreadColumn.label;"
+                           tooltiptext="&folderUnreadColumn.label;"
+                           hideheader="true"
+                           hidden="true"
+                           persist="width hidden"
+                           width="50"
+                           selectable="false"/>
+                  <splitter class="tree-splitter"/>
+                  <treecol id="folderTotalCol"
+                           label2="&folderTotalColumn.label;"
+                           tooltiptext="&folderTotalColumn.label;"
+                           hideheader="true"
+                           hidden="true"
+                           persist="width hidden"
+                           width="50"
+                           selectable="false"/>
+                  <splitter class="tree-splitter"/>
+                  <treecol id="folderSizeCol"
+                           label2="&folderSizeColumn.label;"
+                           tooltiptext="&folderSizeColumn.label;"
+                           hideheader="true"
+                           hidden="true"
+                           persist="width hidden"
+                           width="50"
+                           selectable="false"/>
                 </treecols>
                 <treechildren tooltip="folderpopup"
                               onoverflow="document.getElementById('folderPaneHeader').setAttribute('overflowing', 'true');"
                               onunderflow="document.getElementById('folderPaneHeader').setAttribute('overflowing', 'false');"/>
               </tree>
             </vbox>
 
             <splitter id="folderpane_splitter" collapse="before"/>
--- a/mail/locales/en-US/chrome/messenger/messenger.dtd
+++ b/mail/locales/en-US/chrome/messenger/messenger.dtd
@@ -140,16 +140,18 @@
 <!ENTITY messagePaneClassic.label "Classic View">
 <!ENTITY messagePaneClassic.accesskey "C">
 <!ENTITY messagePaneWide.label "Wide View">
 <!ENTITY messagePaneWide.accesskey "W">
 <!ENTITY messagePaneVertical.label "Vertical View">
 <!ENTITY messagePaneVertical.accesskey "V">
 <!ENTITY showFolderPaneCmd.label "Folder Pane">
 <!ENTITY showFolderPaneCmd.accesskey "F">
+<!ENTITY showFolderPaneColsCmd.label "Folder Pane Columns">
+<!ENTITY showFolderPaneColsCmd.accesskey "P">
 <!ENTITY showMessageCmd.label "Message Pane">
 <!ENTITY showMessageCmd.accesskey "M">
 
 <!ENTITY folderView.label "Folders">
 <!ENTITY folderView.accesskey "F">
 <!ENTITY unifiedFolders.label "Unified">
 <!ENTITY unifiedFolders.accesskey "n">
 <!ENTITY allFolders.label "All">
@@ -588,17 +590,19 @@
 
 <!-- Tags Menu Popup -->
 <!ENTITY addNewTag.label "New Tag…">
 <!ENTITY addNewTag.accesskey "N">
 <!ENTITY manageTags.label "Manage Tags…">
 <!ENTITY manageTags.accesskey "M">
 
 <!-- Folder Pane -->
-<!ENTITY folderColumn.label "Name">
+<!ENTITY folderNameColumn.label "Name">
+<!ENTITY folderUnreadColumn.label "Unread">
+<!ENTITY folderTotalColumn.label "Total">
 <!ENTITY folderSizeColumn.label "Size">
 
 <!-- Folder Pane Context Menu -->
 <!ENTITY folderContextGetMessages.label "Get Messages">
 <!ENTITY folderContextGetMessages.accesskey "G">
 <!ENTITY folderContextOpenInNewWindow.label "Open in New Window">
 <!ENTITY folderContextOpenInNewWindow.accesskey "O">
 <!ENTITY folderContextOpenNewTab.label "Open in New Tab">
--- a/mail/locales/en-US/chrome/messenger/messenger.properties
+++ b/mail/locales/en-US/chrome/messenger/messenger.properties
@@ -477,16 +477,32 @@ alertNoSearchFoldersSelected=You must ch
 
 # These are displayed in the message and folder pane windows
 # LOCALIZATION NOTE %.*f is the abbreviated size in the appropriate units
 byteAbbreviation2=%.*f bytes
 kiloByteAbbreviation2=%.*f KB
 megaByteAbbreviation2=%.*f MB
 gigaByteAbbreviation2=%.*f GB
 
+## LOCALIZATION NOTE(folderWithAccount):
+## This is used to show folder name together with an account name.
+## %1$S = folder name
+## %2$S = account name
+folderWithAccount=%1$S - %2$S
+## LOCALIZATION NOTE(folderWithUnreadMsgs):
+## This is a concatenation of two strings to compose a folder label with unread messages.
+## %1$S = folder name
+## %2$S = count of unread messages
+folderWithUnreadMsgs=%1$S (%2$S)
+## LOCALIZATION NOTE(summarizedValue):
+## This string shows an indication that the value shown is actually a summary
+## accumulated from all subfolders.
+## %S = summarized value from all subfolders
+folderSummarizedValue=*%S
+
 # Error message if message for a message id wasn't found
 errorOpenMessageForMessageIdTitle=Error opening message-id
 errorOpenMessageForMessageIdMessage=Message for message-id %S not found
 
 # Warnings to alert users about phishing urls
 confirmPhishingTitle=Email Scam Alert
 #LOCALIZATION NOTE %1$S is the brand name, %2$S is the host name of the url being visited
 confirmPhishingUrl=%1$S thinks this message is a scam. The links in the message may be trying to impersonate web pages you want to visit. Are you sure you want to visit %2$S?
--- a/mailnews/base/util/nsMsgDBFolder.cpp
+++ b/mailnews/base/util/nsMsgDBFolder.cpp
@@ -4187,17 +4187,21 @@ NS_IMETHODIMP nsMsgDBFolder::SummaryChan
   UpdateSummaryTotals(false);
   return NS_OK;
 }
 
 NS_IMETHODIMP nsMsgDBFolder::GetNumUnread(bool deep, int32_t *numUnread)
 {
   NS_ENSURE_ARG_POINTER(numUnread);
 
-  int32_t total = mNumUnreadMessages + mNumPendingUnreadMessages;
+  bool isServer = false;
+  nsresult rv = GetIsServer(&isServer);
+  NS_ENSURE_SUCCESS(rv, rv);
+  int32_t total = isServer ? 0 : mNumUnreadMessages + mNumPendingUnreadMessages;
+
   if (deep)
   {
     if (total < 0) // deep search never returns negative counts
       total = 0;
     int32_t count = mSubFolders.Count();
     for (int32_t i = 0; i < count; i++)
     {
       nsCOMPtr<nsIMsgFolder> folder(mSubFolders[i]);
@@ -4214,17 +4218,21 @@ NS_IMETHODIMP nsMsgDBFolder::GetNumUnrea
   *numUnread = total;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsMsgDBFolder::GetTotalMessages(bool deep, int32_t *totalMessages)
 {
   NS_ENSURE_ARG_POINTER(totalMessages);
 
-  int32_t total = mNumTotalMessages + mNumPendingTotalMessages;
+  bool isServer = false;
+  nsresult rv = GetIsServer(&isServer);
+  NS_ENSURE_SUCCESS(rv, rv);
+  int32_t total = isServer ? 0 : mNumTotalMessages + mNumPendingTotalMessages;
+
   if (deep)
   {
     if (total < 0) // deep search never returns negative counts
       total = 0;
     int32_t count = mSubFolders.Count();
     for (int32_t i = 0; i < count; i++)
     {
       nsCOMPtr<nsIMsgFolder> folder(mSubFolders[i]);
--- a/mailnews/imap/src/nsImapMailFolder.cpp
+++ b/mailnews/imap/src/nsImapMailFolder.cpp
@@ -1761,16 +1761,23 @@ NS_IMETHODIMP nsImapMailFolder::GetDelet
 
   *deletable = !(isServer || (mFlags & nsMsgFolderFlags::SpecialUse));
   return NS_OK;
 }
 
 NS_IMETHODIMP nsImapMailFolder::GetSizeOnDisk(int64_t *size)
 {
   NS_ENSURE_ARG_POINTER(size);
+
+  bool isServer = false;
+  nsresult rv = GetIsServer(&isServer);
+  // If this is the rootFolder, return 0 as a safe value.
+  if (NS_FAILED(rv) || isServer)
+    mFolderSize = 0;
+
   *size = mFolderSize;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsImapMailFolder::GetCanCreateSubfolders(bool *aResult)
 {
   NS_ENSURE_ARG_POINTER(aResult);
--- a/mailnews/local/src/nsLocalMailFolder.cpp
+++ b/mailnews/local/src/nsLocalMailFolder.cpp
@@ -1109,32 +1109,38 @@ NS_IMETHODIMP nsMsgLocalMailFolder::Refr
   if (NS_SUCCEEDED(GetSizeOnDisk(&mFolderSize)))
     NotifyIntPropertyChanged(kFolderSizeAtom, oldFolderSize, mFolderSize);
   return NS_OK;
 }
 
 NS_IMETHODIMP nsMsgLocalMailFolder::GetSizeOnDisk(int64_t *aSize)
 {
   NS_ENSURE_ARG_POINTER(aSize);
-  nsresult rv = NS_OK;
+
+  bool isServer = false;
+  nsresult rv = GetIsServer(&isServer);
+  // If this is the rootFolder, return 0 as a safe value.
+  if (NS_FAILED(rv) || isServer)
+    mFolderSize = 0;
+
   if (mFolderSize == kSizeUnknown)
   {
     nsCOMPtr<nsIFile> file;
     rv = GetFilePath(getter_AddRefs(file));
     NS_ENSURE_SUCCESS(rv, rv);
     // Use a temporary variable so that we keep mFolderSize on kSizeUnknown
     // if GetFileSize() fails.
     int64_t folderSize;
     rv = file->GetFileSize(&folderSize);
     NS_ENSURE_SUCCESS(rv, rv);
 
     mFolderSize = folderSize;
   }
   *aSize = mFolderSize;
-  return rv;
+  return NS_OK;
 }
 
 nsresult
 nsMsgLocalMailFolder::GetTrashFolder(nsIMsgFolder** result)
 {
   NS_ENSURE_ARG_POINTER(result);
   nsresult rv;
   nsCOMPtr<nsIMsgFolder> rootFolder;
--- a/mailnews/news/src/nsNewsFolder.cpp
+++ b/mailnews/news/src/nsNewsFolder.cpp
@@ -756,18 +756,24 @@ NS_IMETHODIMP nsMsgNewsFolder::RefreshSi
     NotifyIntPropertyChanged(kFolderSizeAtom, oldFolderSize, mFolderSize);
   return NS_OK;
 }
 
 NS_IMETHODIMP nsMsgNewsFolder::GetSizeOnDisk(int64_t *size)
 {
   NS_ENSURE_ARG_POINTER(size);
 
+  bool isServer = false;
+  nsresult rv = GetIsServer(&isServer);
+  // If this is the rootFolder, return 0 as a safe value.
+  if (NS_FAILED(rv) || isServer)
+    mFolderSize = 0;
+
   // 0 is a valid folder size (meaning empty file with no offline messages),
-  // but 1 is not. So use 1 as a special value meaning no file size was fetched
+  // but 1 is not. So use -1 as a special value meaning no file size was fetched
   // from disk yet.
   if (mFolderSize == kSizeUnknown)
   {
     nsCOMPtr<nsIFile> diskFile;
     nsresult rv = GetFilePath(getter_AddRefs(diskFile));
     NS_ENSURE_SUCCESS(rv, rv);
 
     // If there were no news messages downloaded for offline use, the folder file