summaryrefslogtreecommitdiffstats
path: root/mailnews/base/content/folderWidgets.xml
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/base/content/folderWidgets.xml')
-rw-r--r--mailnews/base/content/folderWidgets.xml829
1 files changed, 829 insertions, 0 deletions
diff --git a/mailnews/base/content/folderWidgets.xml b/mailnews/base/content/folderWidgets.xml
new file mode 100644
index 000000000..605f4230c
--- /dev/null
+++ b/mailnews/base/content/folderWidgets.xml
@@ -0,0 +1,829 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/.
+-->
+
+<bindings id="mailFolderBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xbl="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="folder-menupopup"
+ extends="chrome://global/content/bindings/popup.xml#popup">
+ <implementation>
+ <constructor><![CDATA[
+ Components.utils.import("resource:///modules/FeedUtils.jsm", this);
+ Components.utils.import("resource:///modules/folderUtils.jsm", this);
+ Components.utils.import("resource:///modules/iteratorUtils.jsm", this);
+ Components.utils.import("resource:///modules/mailServices.js", this);
+ Components.utils.import("resource:///modules/MailUtils.js", this);
+ Components.utils.import("resource:///modules/StringBundle.js", this);
+ this._stringBundle = new this
+ .StringBundle("chrome://messenger/locale/folderWidgets.properties");
+
+ // Get the displayformat if set.
+ if (this.parentNode && this.parentNode.localName == "menulist")
+ this._displayformat = this.parentNode.getAttribute("displayformat");
+
+ // Find out if we are in a wrapper (customize toolbars mode is active).
+ let inWrapper = false;
+ let node = this;
+ while (node instanceof XULElement) {
+ if (node.id.startsWith("wrapper-")) {
+ inWrapper = true;
+ break;
+ }
+ node = node.parentNode;
+ }
+
+ if (!inWrapper) {
+ if (this.hasAttribute("original-width")) {
+ // If we were in a wrapper before and have a width stored, restore it now.
+ if (this.getAttribute("original-width") == "none")
+ this.removeAttribute("width");
+ else
+ this.setAttribute("width", this.getAttribute("original-width"));
+
+ this.removeAttribute("original-width");
+ }
+
+ // If we are a child of a menulist, and we aren't in a wrapper, we
+ // need to build our content right away, otherwise the menulist
+ // won't have proper sizing.
+ if (this.parentNode && this.parentNode.localName == "menulist")
+ this._ensureInitialized();
+ } else {
+ // But if we're in a wrapper, remove our children, because we're
+ // getting re-created when the toolbar customization closes.
+ this._teardown();
+
+ // Store our current width and set a safe small width when we show
+ // in a wrapper.
+ if (!this.hasAttribute("original-width")) {
+ this.setAttribute("original-width", this.hasAttribute("width") ?
+ this.getAttribute("width") : "none");
+ this.setAttribute("width", "100");
+ }
+ }
+ ]]></constructor>
+ <destructor><![CDATA[
+ // Clean up when being destroyed.
+ this._removeListener();
+ ]]></destructor>
+ <!--
+ - Make sure we remove our listener when the window is being destroyed
+ - or the widget teared down.
+ -->
+ <method name="_removeListener">
+ <body><![CDATA[
+ if (!this._initialized)
+ return;
+
+ this.MailServices.mailSession.RemoveFolderListener(this._listener);
+ ]]></body>
+ </method>
+
+ <field name="_stringBundle">null</field>
+
+ <!--
+ - If non-null, the subFolders of this nsIMsgFolder will be used to
+ - populate this menu. If this is null, the menu will be populated
+ - using the root-folders for all accounts
+ -->
+ <field name="_parentFolder">null</field>
+ <property name="parentFolder"
+ onget="return this._parentFolder;"
+ onset="return this._parentFolder = val;"/>
+
+ <!--
+ - Various filtering modes can be used with this menu-binding. To use
+ - one of them, append the mode="foo" attribute to the element. When
+ - building the menu, we will then use this._filters[mode] as a filter
+ - function to eliminate folders that should not be shown.
+ -
+ - Note that extensions should feel free to plug in here!
+ -->
+ <field name="_filters"><![CDATA[({
+ // Returns true if messages can be filed in the folder
+ filing: function filter_filing(aFolder) {
+ if (!aFolder.server.canFileMessagesOnServer)
+ return false;
+
+ return (aFolder.canFileMessages || aFolder.hasSubFolders);
+ },
+
+ // Returns true if we can get mail for this folder. (usually this just
+ // means the "root" fake folder)
+ getMail: function filter_getMail(aFolder) {
+ if (aFolder.isServer && aFolder.server.type != "none")
+ return true;
+ if (aFolder.server.type == "nntp" || aFolder.server.type == "rss")
+ return true;
+ return false;
+ },
+
+ // Returns true if we can add filters to this folder/account
+ filters: function filter_filter(aFolder) {
+ // We can always filter news
+ if (aFolder.server.type == "nntp")
+ return true;
+
+ return aFolder.server.canHaveFilters;
+ },
+
+ subscribe: function filter_subscribe(aFolder) {
+ return aFolder.canSubscribe;
+ },
+
+ newFolder: function filter_newFolder(aFolder) {
+ return aFolder.canCreateSubfolders &&
+ aFolder.server.canCreateFoldersOnServer;
+ },
+
+ deferred: function filter_defered(aFolder) {
+ return aFolder.server.canCreateFoldersOnServer &&
+ !aFolder.supportsOffline;
+ },
+
+ // Folders that are not in a deferred account
+ notDeferred: function(aFolder) {
+ let server = aFolder.server;
+ return !(server instanceof Components.interfaces.nsIPop3IncomingServer &&
+ server.deferredToAccount);
+ },
+
+ // Folders that can be searched.
+ search: function filter_search(aFolder) {
+ if (!aFolder.server.canSearchMessages ||
+ aFolder.getFlag(Components.interfaces.nsMsgFolderFlags.Virtual))
+ return false;
+ return true;
+ },
+
+ // Folders that can subscribe feeds.
+ feeds: function filter_feeds(aFolder) {
+ if (aFolder.server.type != "rss" ||
+ aFolder.getFlag(Components.interfaces.nsMsgFolderFlags.Trash) ||
+ aFolder.getFlag(Components.interfaces.nsMsgFolderFlags.Virtual))
+ return false;
+ return true;
+ },
+
+ junk: function filter_junk(aFolder) {
+ // Don't show servers (nntp & any others) which do not allow search or filing
+ // I don't really understand why canSearchMessages is needed, but it was included in
+ // earlier code, so I include it as well.
+ if (!aFolder.server.canFileMessagesOnServer || !aFolder.server.canSearchMessages)
+ return false;
+ // show parents that might have usable subfolders, or usable folders
+ return aFolder.hasSubFolders || aFolder.canFileMessages;
+ }
+ })]]></field>
+
+ <!--
+ - The maximum number of entries in the "Recent" menu
+ -->
+ <field name="_MAXRECENT">15</field>
+
+ <!--
+ - Is this list containing only servers (accounts) and no real folders?
+ -->
+ <field name="_serversOnly">true</field>
+
+ <!--
+ - Our listener to let us know when folders change/appear/disappear so
+ - we can know to rebuild ourselves.
+ -->
+ <field name="_listener">
+ <![CDATA[({
+ _menu: this,
+ _clearMenu: function(aMenu) {
+ if (aMenu._teardown)
+ aMenu._teardown();
+ },
+ OnItemAdded: function act_add(aRDFParentItem, aItem) {
+ if (!(aItem instanceof Components.interfaces.nsIMsgFolder))
+ return;
+ if (this._filterFunction && !this._filterFunction(aItem)) {
+ return;
+ }
+ //xxx we can optimize this later
+ //xxx I'm not quite sure why this isn't always a function
+ if (this._menu._teardown)
+ this._menu._teardown();
+ },
+
+ OnItemRemoved: function act_remove(aRDFParentItem, aItem) {
+ if (!(aItem instanceof Components.interfaces.nsIMsgFolder))
+ return;
+ if (this._filterFunction && !this._filterFunction(aItem)) {
+ return;
+ }
+ //xxx we can optimize this later
+ if (this._menu._teardown)
+ this._menu._teardown();
+ },
+
+ //xxx I stole this listener list from nsMsgFolderDatasource.cpp, but
+ // someone should really document what events are fired when, so that
+ // we make sure we're updating at the right times.
+ OnItemPropertyChanged: function(aItem, aProperty, aOld, aNew) {},
+ OnItemIntPropertyChanged: function(aItem, aProperty, aOld, aNew) {
+ var child = this._getChildForItem(aItem);
+ if (child)
+ this._menu._setCssSelectors(child._folder, child);
+ },
+ OnItemBoolPropertyChanged: function(aItem, aProperty, aOld, aNew) {
+ var child = this._getChildForItem(aItem);
+ if (child)
+ this._menu._setCssSelectors(child._folder, child);
+ },
+ OnItemUnicharPropertyChanged: function(aItem, aProperty, aOld, aNew) {
+ var child = this._getChildForItem(aItem);
+ if (child)
+ this._menu._setCssSelectors(child._folder, child);
+ },
+ OnItemPropertyFlagChanged: function(aItem, aProperty, aOld, aNew) {},
+ OnItemEvent: function(aFolder, aEvent) {
+ if (aEvent.toString() == "MRMTimeChanged") {
+ if (this._menu.getAttribute("showRecent") != "true" ||
+ !this._menu.firstChild || !this._menu.firstChild.firstChild)
+ return;
+ // if this folder is already in the recent menu, return.
+ if (this._getChildForItem(aFolder,
+ this._menu.firstChild.firstChild))
+ return;
+ }
+ // Special casing folder renames here, since they require more work
+ // since sort-order may have changed.
+ else if (aEvent.toString() == "RenameCompleted") {
+ if (!this._getChildForItem(aFolder))
+ return;
+ }
+ else
+ return;
+ // folder renamed, or new recent folder, so rebuild.
+ setTimeout(this._clearMenu, 0, this._menu);
+ },
+
+ /**
+ * Helper function to check and see whether we have a menuitem for this
+ * particular nsIMsgFolder
+ *
+ * @param aItem the nsIMsgFolder to check
+ * @param aMenu (optional) menu to look in, defaults to this._menu.
+ * @returns null if no child for that folder exists, otherwise the
+ * menuitem for that child
+ */
+ _getChildForItem: function act__itemIsChild(aItem, aMenu) {
+ aMenu = aMenu || this._menu;
+ if (!aMenu || !aMenu.childNodes)
+ return null;
+
+ if (!(aItem instanceof Components.interfaces.nsIMsgFolder))
+ return null;
+ for (let i = 0; i < aMenu.childNodes.length; i++) {
+ let folder = aMenu.childNodes[i]._folder;
+ if (folder && folder.URI == aItem.URI)
+ return aMenu.childNodes[i];
+ }
+ return null;
+ }
+ })]]></field>
+
+ <!--
+ - True if we have already built our menu-items and are now just
+ - listening for changes
+ -->
+ <field name="_initialized">false</field>
+
+ <!--
+ - Call this if you are unsure whether the menu-items have been built,
+ - but know that they need to be built now if they haven't.
+ -->
+ <method name="_ensureInitialized">
+ <body><![CDATA[
+ if (this._initialized)
+ return;
+
+ // I really wish they'd just make this global...
+ const Ci = Components.interfaces;
+
+ let folders;
+
+ // Figure out which folders to build. If we don't have a parent, then
+ // we assume we should build the top-level accounts. (Actually we
+ // build the fake root folders for those accounts.)
+ if (!this._parentFolder) {
+ let accounts = this.allAccountsSorted(true);
+
+ // Now generate our folder-list. Note that we'll special case this
+ // situation below, to avoid destroying the sort order we just made
+ folders = accounts.map(acct => acct.incomingServer.rootFolder);
+ } else {
+ // If we do have a parent folder, then we just build based on those
+ // subFolders for that parent.
+ folders = this.toArray(this.fixIterator(this._parentFolder.subFolders,
+ Ci.nsIMsgFolder));
+ }
+
+ this._build(folders);
+
+ // Lastly, we add a listener to get notified of changes in the folder
+ // structure.
+ this.MailServices.mailSession.AddFolderListener(this._listener,
+ Ci.nsIFolderListener.all);
+
+ this._initialized = true;
+ ]]></body>
+ </method>
+
+ <!--
+ - Actually constructs the menu-items based on the folders given.
+ -
+ - @param aFolders An array of nsIMsgFolders to use for building.
+ -->
+ <method name="_build">
+ <parameter name="aFolders"/>
+ <body><![CDATA[
+ let folders;
+ let excludeServers = [];
+ let disableServers = [];
+
+ // excludeServers attribute is a comma separated list of server keys.
+ if (this.hasAttribute("excludeServers"))
+ excludeServers = this.getAttribute("excludeServers").split(",");
+
+ // disableServers attribute is a comma separated list of server keys.
+ if (this.hasAttribute("disableServers"))
+ disableServers = this.getAttribute("disableServers").split(",");
+
+ // Extensions and other consumers can add to these modes too, see the
+ // above note on the _filters field.
+ var mode = this.getAttribute("mode");
+ if (mode && mode != "") {
+ var filterFunction = this._filters[mode];
+ folders = aFolders.filter(filterFunction);
+ this._listener._filterFunction = filterFunction;
+ } else {
+ folders = aFolders;
+ }
+
+ if (excludeServers.length > 0) {
+ folders = folders.filter(function(aFolder) {
+ return !(excludeServers.indexOf(aFolder.server.key) != -1); });
+ }
+
+ /* This code block will do the following: Add a menu item that refers
+ back to the parent folder when there is a showFileHereLabel
+ attribute or no mode attribute. However the code won't add such a
+ menu item if one of the following conditions is met:
+ (*) There is no parent folder
+ (*) Folder is server and showAccountsFileHere is explicitly false
+ (*) Current folder has a mode, the parent folder can be selected,
+ no messages can be filed into the parent folder (e.g. when the
+ parent folder is a news group or news server) and the folder
+ mode is not equal to newFolder
+
+ The menu item will have the value of the fileHereLabel attribute as
+ label or if the attribute does not exist the name of the parent
+ folder instead.
+ */
+ let parent = this._parentFolder;
+ if (parent && (this.getAttribute("showFileHereLabel") == "true" || !mode)) {
+ let showAccountsFileHere = this.getAttribute("showAccountsFileHere");
+ if ((!parent.isServer || showAccountsFileHere != "false") &&
+ (!mode || mode == "newFolder" || parent.noSelect ||
+ parent.canFileMessages || showAccountsFileHere == "true")) {
+ var menuitem = document.createElement("menuitem");
+ menuitem._folder = this._parentFolder;
+ menuitem.setAttribute("generated", "true");
+ if (this.hasAttribute("fileHereLabel")) {
+ menuitem.setAttribute("label", this.getAttribute("fileHereLabel"));
+ menuitem.setAttribute("accesskey", this.getAttribute("fileHereAccessKey"));
+ } else {
+ menuitem.setAttribute("label", this._parentFolder.prettyName);
+ menuitem.setAttribute("class", "folderMenuItem menuitem-iconic");
+ this._setCssSelectors(this._parentFolder, menuitem);
+ }
+ // Eww. have to support some legacy code here...
+ menuitem.setAttribute("id", this._parentFolder.URI);
+ this.appendChild(menuitem);
+
+ if (this._parentFolder.noSelect)
+ menuitem.setAttribute("disabled", "true");
+
+ var sep= document.createElement("menuseparator");
+ sep.setAttribute("generated", "true");
+ this.appendChild(sep);
+ }
+ }
+
+ let globalInboxFolder = null;
+ // See if this is the toplevel menu (usually with accounts).
+ if (!this._parentFolder) {
+ // Some menus want a "Recent" option, but that should only be on our
+ // top-level menu
+ if (this.getAttribute("showRecent") == "true")
+ this._buildRecentMenu();
+ // If we are showing the accounts for deferring, move Local Folders to the top.
+ if (mode == "deferred") {
+ globalInboxFolder = this.MailServices.accounts.localFoldersServer
+ .rootFolder;
+ let localFoldersIndex = folders.indexOf(globalInboxFolder);
+ if (localFoldersIndex != -1) {
+ folders.splice(localFoldersIndex, 1);
+ folders.unshift(globalInboxFolder);
+ }
+ }
+ // If we're the root of the folder hierarchy, then we actually don't
+ // want to sort the folders, but rather the accounts to which the
+ // folders belong. Since that sorting was already done, we don't need
+ // to do anything for that case here.
+ } else {
+ // Sorts the list of folders. We give first priority to the sortKey
+ // property if it is available, otherwise a case-insensitive
+ // comparison of names.
+ folders = folders.sort(function nameCompare(a, b) {
+ return a.compareSortKeys(b);
+ });
+ }
+
+ /* In some cases, the user wants to have a list of subfolders for only
+ * some account types (or maybe all of them). So we use this to
+ * determine what the user wanted.
+ */
+ var shouldExpand;
+ var labels = null;
+ if (this.getAttribute("expandFolders") == "true" ||
+ !this.hasAttribute("expandFolders")) {
+ shouldExpand = function (e) { return true; };
+ } else if (this.getAttribute("expandFolders") == "false") {
+ shouldExpand = function (e) { return false; };
+ } else {
+ /* We want a subfolder list for only some servers. We also may need
+ * to create headers to select the servers. If so, then headlabels
+ * is a comma-delimited list of labels corresponding to the server
+ * types specified in expandFolders.
+ */
+ var types = this.getAttribute("expandFolders").split(/ *, */);
+ // Set the labels. labels[type] = label
+ if (this.hasAttribute("headlabels")) {
+ var labelNames = this.getAttribute("headlabels").split(/ *, */);
+ labels = {};
+ // If the length isn't equal, don't give them any of the labels,
+ // since any combination will probably be wrong.
+ if (labelNames.length == types.length) {
+ for (var index in types)
+ labels[types[index]] = labelNames[index];
+ }
+ }
+ shouldExpand = function (e) { return types.indexOf(e) != -1; };
+ }
+
+ // We need to call this, or hasSubFolders will always return false.
+ // Remove this workaround when Bug 502900 is fixed.
+ this.MailUtils.discoverFolders();
+ this._serversOnly = true;
+
+ for (let folder of folders) {
+ let node;
+ if (!folder.isServer)
+ this._serversOnly = false;
+
+ // If we're going to add subFolders, we need to make menus, not
+ // menuitems.
+ if (!folder.hasSubFolders || !shouldExpand(folder.server.type)) {
+ node = document.createElement("menuitem");
+ // Grumble, grumble, legacy code support
+ node.setAttribute("id", folder.URI);
+ node.setAttribute("class", "folderMenuItem menuitem-iconic");
+ node.setAttribute("generated", "true");
+ this.appendChild(node);
+ } else {
+ this._serversOnly = false;
+ //xxx this is slightly problematic in that we haven't confirmed
+ // whether any of the subfolders will pass the filter
+ node = document.createElement("menu");
+ node.setAttribute("class", "folderMenuItem menu-iconic");
+ node.setAttribute("generated", "true");
+ this.appendChild(node);
+
+ // Create the submenu
+ // (We must use cloneNode here because on OS X the native menu
+ // functionality and very sad limitations of XBL1 cause the bindings
+ // to never get created for popup if we create a new element. We
+ // perform a shallow clone to avoid picking up any of our children.)
+ var popup = this.cloneNode(false);
+ popup._parentFolder = folder;
+ popup.setAttribute("class", this.getAttribute("class"));
+ popup.setAttribute("type", this.getAttribute("type"));
+ if (this.hasAttribute("fileHereLabel"))
+ popup.setAttribute("fileHereLabel",
+ this.getAttribute("fileHereLabel"));
+ popup.setAttribute("showFileHereLabel",
+ this.getAttribute("showFileHereLabel"));
+ popup.setAttribute("oncommand",
+ this.getAttribute("oncommand"));
+ popup.setAttribute("mode",
+ this.getAttribute("mode"));
+ if (this.hasAttribute("disableServers"))
+ popup.setAttribute("disableServers",
+ this.getAttribute("disableServers"));
+ if (this.hasAttribute("position"))
+ popup.setAttribute("position",
+ this.getAttribute("position"));
+
+ // If there are labels, add the labels now
+ if (labels) {
+ var serverNode = document.createElement("menuitem");
+ serverNode.setAttribute("label", labels[folder.server.type]);
+ serverNode._folder = folder;
+ serverNode.setAttribute("generated", "true");
+ popup.appendChild(serverNode);
+ var sep = document.createElement("menuseparator");
+ sep.setAttribute("generated", "true");
+ popup.appendChild(sep);
+ }
+
+ popup.setAttribute("generated", "true");
+ node.appendChild(popup);
+ }
+
+ if (disableServers.indexOf(folder.server.key) != -1)
+ node.setAttribute("disabled", "true");
+
+ node._folder = folder;
+ let label = "";
+ if (mode == "deferred" && folder.isServer &&
+ folder.server.rootFolder == globalInboxFolder) {
+ label = this._stringBundle.get("globalInbox", [folder.prettyName]);
+ } else {
+ label = folder.prettyName;
+ }
+ node.setAttribute("label", label);
+ this._setCssSelectors(folder, node);
+ }
+ ]]></body>
+ </method>
+
+ <!--
+ - Builds a submenu with all of the recently used folders in it, to
+ - allow for easy access.
+ -->
+ <method name="_buildRecentMenu">
+ <body><![CDATA[
+ const Ci = Components.interfaces;
+
+ // Iterate through all folders in all accounts, and find 15 (_MAXRECENT)
+ // of most recently modified ones.
+ let allFolders = this.toArray(
+ this.fixIterator(this.MailServices.accounts.allFolders, Ci.nsIMsgFolder));
+
+ allFolders = allFolders.filter(f => f.canFileMessages);
+
+ let recentFolders = this.getMostRecentFolders(allFolders,
+ this._MAXRECENT,
+ "MRMTime");
+
+ // Cache the pretty names so that they do not need to be fetched
+ // _MAXRECENT^2 times later.
+ recentFolders = recentFolders.map(
+ function (f) { return { folder: f, name: f.prettyName } });
+
+ // Because we're scanning across multiple accounts, we can end up with
+ // several folders with the same name. Find those dupes.
+ let dupeNames = new Set();
+ for (let i = 0; i < recentFolders.length; i++) {
+ for (let j = i + 1; j < recentFolders.length; j++) {
+ if (recentFolders[i].name == recentFolders[j].name)
+ dupeNames.add(recentFolders[i].name);
+ }
+ }
+
+ for (let folderItem of recentFolders) {
+ // If this folder name appears multiple times in the recent list,
+ // append the server name to disambiguate.
+ // TODO:
+ // - maybe this could use verboseFolderFormat from messenger.properties
+ // instead of hardcoded " - ".
+ // - disambiguate folders with same name in same account
+ // (in different subtrees).
+ let label = folderItem.name;
+ if (dupeNames.has(label))
+ label += " - " + folderItem.folder.server.prettyName;
+
+ folderItem.label = label;
+ }
+
+ // Make sure the entries are sorted alphabetically.
+ recentFolders.sort((a, b) => this.folderNameCompare(a.label, b.label));
+
+ // Now create the Recent folder and its children
+ var menu = document.createElement("menu");
+ menu.setAttribute("label", this.getAttribute("recentLabel"));
+ menu.setAttribute("accesskey", this.getAttribute("recentAccessKey"));
+ var popup = document.createElement("menupopup");
+ popup.setAttribute("class", this.getAttribute("class"));
+ popup.setAttribute("generated", "true");
+ menu.appendChild(popup);
+
+ // Create entries for each of the recent folders.
+ for (let folderItem of recentFolders) {
+ let node = document.createElement("menuitem");
+
+ node.setAttribute("label", folderItem.label);
+ node._folder = folderItem.folder;
+
+ node.setAttribute("class", "folderMenuItem menuitem-iconic");
+ this._setCssSelectors(folderItem.folder, node);
+ node.setAttribute("generated", "true");
+ popup.appendChild(node);
+ }
+ menu.setAttribute("generated", "true");
+ this.appendChild(menu);
+ if (!recentFolders.length)
+ menu.setAttribute("disabled", "true");
+
+ var sep = document.createElement("menuseparator");
+ sep.setAttribute("generated", "true");
+ this.appendChild(sep);
+ ]]></body>
+ </method>
+
+ <!--
+ - This function adds attributes on menu/menuitems to make it easier for
+ - css to style them.
+ -
+ - @param aFolder the folder that corresponds to the menu/menuitem
+ - @param aMenuNode the actual DOM node to set attributes on
+ -->
+ <method name="_setCssSelectors">
+ <parameter name="aFolder"/>
+ <parameter name="aMenuNode"/>
+ <body><![CDATA[
+
+ // First set the SpecialFolder attribute
+ aMenuNode.setAttribute("SpecialFolder", this.getSpecialFolderString(aFolder));
+
+ // Now set the biffState
+ let biffStates = ["NewMail", "NoMail", "UnknownMail"];
+ for (let state of biffStates) {
+ if (aFolder.biffState ==
+ Components.interfaces.nsIMsgFolder["nsMsgBiffState_" + state]) {
+ aMenuNode.setAttribute("BiffState", state);
+ break;
+ }
+ }
+
+ aMenuNode.setAttribute("IsServer", aFolder.isServer);
+ aMenuNode.setAttribute("IsSecure", aFolder.server.isSecure);
+ aMenuNode.setAttribute("ServerType", aFolder.server.type);
+ aMenuNode.setAttribute("IsFeedFolder",
+ (this.FeedUtils.getFeedUrlsInFolder(aFolder) ? true : false));
+ ]]></body>
+ </method>
+
+ <!--
+ - This function returns a formatted display name for a menulist
+ - selected folder. The desired format is set as the 'displayformat'
+ - attribute of the folderpicker's <menulist>, one of:
+ - 'name' (default) - Folder
+ - 'verbose' - Folder on Account
+ - 'path' - Account/Folder/Subfolder
+ -
+ - @param aFolder the folder that corresponds to the menu/menuitem
+ - @return string display name
+ -->
+ <field name="_displayformat">null</field>
+ <method name="getDisplayName">
+ <parameter name="aFolder"/>
+ <body><![CDATA[
+ if (aFolder.isServer)
+ return aFolder.prettyName;
+
+ if (this._displayformat == "verbose")
+ return this._stringBundle.getFormattedString("verboseFolderFormat",
+ [aFolder.prettyName, aFolder.server.prettyName]);
+
+ if (this._displayformat == "path")
+ return this.FeedUtils.getFolderPrettyPath(aFolder) || aFolder.name;
+
+ return aFolder.name;
+ ]]></body>
+ </method>
+
+ <!--
+ - Makes a given folder selected.
+ -
+ - @param aFolder the folder to select (if none, then Choose Folder)
+ - @note If aFolder is not in this popup, but is instead a descendant of
+ - a member of the popup, that ancestor will be selected.
+ - @return true if any usable folder was found, otherwise false.
+ -->
+ <method name="selectFolder">
+ <parameter name="aFolder"/>
+ <body><![CDATA[
+ // Set the label of the menulist element as if aFolder had been selected.
+ function setupParent(aFolder, aMenulist, aNoFolders) {
+ let menupopup = aMenulist.menupopup;
+ if (aFolder) {
+ aMenulist.setAttribute("label", menupopup.getDisplayName(aFolder));
+ } else {
+ aMenulist.setAttribute("label", menupopup._stringBundle.getString(
+ aNoFolders ? "noFolders" :
+ (menupopup._serversOnly ? "chooseAccount" : "chooseFolder")));
+ }
+ aMenulist.setAttribute("value",
+ aFolder ? aFolder.URI : "");
+ aMenulist.setAttribute("IsServer",
+ aFolder ? aFolder.isServer : false);
+ aMenulist.setAttribute("IsSecure",
+ aFolder ? aFolder.server.isSecure : false);
+ aMenulist.setAttribute("ServerType",
+ aFolder ? aFolder.server.type : "none");
+ aMenulist.setAttribute("SpecialFolder",
+ aFolder ? menupopup.getSpecialFolderString(aFolder) : "none");
+ aMenulist.setAttribute("IsFeedFolder", Boolean(
+ aFolder && menupopup.FeedUtils.getFeedUrlsInFolder(aFolder)));
+ }
+
+ let folder;
+ if (aFolder) {
+ for (let child of this.childNodes) {
+ if (child && child._folder && !child.disabled &&
+ (child._folder.URI == aFolder.URI ||
+ (child.tagName == "menu" &&
+ child._folder.isAncestorOf(aFolder)))) {
+ if (child._folder.URI == aFolder.URI)
+ this.parentNode.selectedItem = child;
+ folder = aFolder;
+ break;
+ }
+ }
+ }
+
+ // If the caller specified a folder to select and it was not
+ // found, or if the caller didn't pass a folder (meaning a logical
+ // and valid folder wasn't determined), don't blow up but reset
+ // attributes and set a nice Choose Folder label so the user may
+ // select a valid folder per the filter for this picker. If there are
+ // no children, then no folder passed the filter; disable the menulist
+ // as there's nothing to choose from.
+ let noFolders;
+ if (!this.childElementCount)
+ {
+ this.parentNode.setAttribute("disabled", true);
+ noFolders = true;
+ }
+ else
+ {
+ this.parentNode.removeAttribute("disabled");
+ noFolders = false;
+ }
+
+ setupParent(folder, this.parentNode, noFolders);
+ return folder ? true : false;
+ ]]></body>
+ </method>
+
+ <!--
+ - Removes all menu-items for this popup, resets all fields, and
+ - removes the listener. This function is invoked when a change
+ - that affects this menu is detected by our listener.
+ -->
+ <method name="_teardown">
+ <body><![CDATA[
+ for (let i = this.childNodes.length - 1; i >= 0; i--) {
+ let child = this.childNodes[i];
+ if (child.getAttribute("generated") != "true")
+ continue;
+ if ("_teardown" in child)
+ child._teardown();
+ child.remove();
+ }
+
+ this._removeListener();
+
+ this._initialized = false;
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <!--
+ - In order to improve performance, we're not going to build any of the
+ - menu until we're shown (unless we're the child of a menulist, see
+ - note in the constructor).
+ -
+ - @note _ensureInitialized can be called repeatedly without issue, so
+ - don't worry about it here.
+ -->
+ <handler event="popupshowing" phase="capturing">
+ this._ensureInitialized();
+ </handler>
+ </handlers>
+ </binding>
+</bindings>