summaryrefslogtreecommitdiffstats
path: root/comm/suite/chatzilla/js/lib/menu-manager.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /comm/suite/chatzilla/js/lib/menu-manager.js
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--comm/suite/chatzilla/js/lib/menu-manager.js848
1 files changed, 848 insertions, 0 deletions
diff --git a/comm/suite/chatzilla/js/lib/menu-manager.js b/comm/suite/chatzilla/js/lib/menu-manager.js
new file mode 100644
index 0000000000..6fd0686833
--- /dev/null
+++ b/comm/suite/chatzilla/js/lib/menu-manager.js
@@ -0,0 +1,848 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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/. */
+
+//
+function MenuManager(commandManager, menuSpecs, contextFunction, commandStr)
+{
+ var menuManager = this;
+
+ this.commandManager = commandManager;
+ this.menuSpecs = menuSpecs;
+ this.contextFunction = contextFunction;
+ this.commandStr = commandStr;
+ this.repeatId = 0;
+ this.cxStore = new Object();
+
+ this.onPopupShowing =
+ function mmgr_onshow(event) { return menuManager.showPopup(event); };
+ this.onPopupHiding =
+ function mmgr_onhide(event) { return menuManager.hidePopup(event); };
+ this.onMenuCommand =
+ function mmgr_oncmd(event) { return menuManager.menuCommand(event); };
+
+ /* The code using us may override these with functions which will be called
+ * after all our internal processing is done. Both are called with the
+ * arguments 'event' (DOM), 'cx' (JS), 'popup' (DOM).
+ */
+ this.onCallbackPopupShowing = null;
+ this.onCallbackPopupHiding = null;
+}
+
+MenuManager.prototype.appendMenuItems =
+function mmgr_append(menuId, items)
+{
+ for (var i = 0; i < items.length; ++i)
+ this.menuSpecs[menuId].items.push(items[i]);
+}
+
+MenuManager.prototype.createContextMenus =
+function mmgr_initcxs (document)
+{
+ for (var id in this.menuSpecs)
+ {
+ if (id.indexOf("context:") == 0)
+ this.createContextMenu(document, id);
+ }
+}
+
+MenuManager.prototype.createContextMenu =
+function mmgr_initcx (document, id)
+{
+ if (!document.getElementById(id))
+ {
+ if (!ASSERT(id in this.menuSpecs, "unknown context menu " + id))
+ return;
+
+ var dp = document.getElementById("dynamic-popups");
+ var popup = this.appendPopupMenu (dp, null, id, id);
+ var items = this.menuSpecs[id].items;
+ this.createMenuItems (popup, null, items);
+
+ if (!("uiElements" in this.menuSpecs[id]))
+ this.menuSpecs[id].uiElements = [popup];
+ else if (!arrayContains(this.menuSpecs[id].uiElements, popup))
+ this.menuSpecs[id].uiElements.push(popup);
+ }
+}
+
+
+MenuManager.prototype.createMenus =
+function mmgr_createtb(document, menuid)
+{
+ var menu = document.getElementById(menuid);
+ for (var id in this.menuSpecs)
+ {
+ var domID;
+ if ("domID" in this.menuSpecs[id])
+ domID = this.menuSpecs[id].domID;
+ else
+ domID = id;
+
+ if (id.indexOf(menuid + ":") == 0)
+ this.createMenu(menu, null, id, domID);
+ }
+}
+
+MenuManager.prototype.createMainToolbar =
+function mmgr_createtb(document, id)
+{
+ var toolbar = document.getElementById(id);
+ var spec = this.menuSpecs[id];
+ for (var i in spec.items)
+ {
+ this.appendToolbarItem (toolbar, null, spec.items[i]);
+ }
+
+ toolbar.className = "toolbar-primary chromeclass-toolbar";
+}
+
+MenuManager.prototype.updateMenus =
+function mmgr_updatemenus(document, menus)
+{
+ // Cope with one string (update just the one menu)...
+ if (isinstance(menus, String))
+ {
+ menus = [menus];
+ }
+ // Or nothing/nonsense (update everything).
+ else if ((typeof menus != "object") || !isinstance(menus, Array))
+ {
+ menus = [];
+ for (var k in this.menuSpecs)
+ {
+ if ((/^(mainmenu|context)/).test(k))
+ menus.push(k);
+ }
+ }
+
+ var menuBar = document.getElementById("mainmenu");
+
+ // Loop through this array and update everything we need to.
+ for (var i = 0; i < menus.length; i++)
+ {
+ var id = menus[i];
+ if (!(id in this.menuSpecs))
+ continue;
+ var menu = this.menuSpecs[id];
+ var domID;
+ if ("domID" in this.menuSpecs[id])
+ domID = this.menuSpecs[id].domID;
+ else
+ domID = id;
+
+ // Context menus need to be deleted in order to be regenerated...
+ if ((/^context/).test(id))
+ {
+ var cxMenuNode;
+ if ((cxMenuNode = document.getElementById(id)))
+ cxMenuNode.parentNode.removeChild(cxMenuNode);
+ this.createContextMenu(document, id);
+ }
+ else if ((/^mainmenu/).test(id) &&
+ !("uiElements" in this.menuSpecs[id]))
+ {
+ this.createMenu(menuBar, null, id, domID);
+ continue;
+ }
+ else if ((/^(mainmenu|popup)/).test(id) &&
+ ("uiElements" in this.menuSpecs[id]))
+ {
+ for (var j = 0; j < menu.uiElements.length; j++)
+ {
+ var node = menu.uiElements[j];
+ domID = node.parentNode.id;
+ // Clear the menu node.
+ while (node.lastChild)
+ node.removeChild(node.lastChild);
+
+ this.createMenu(node.parentNode.parentNode,
+ node.parentNode.nextSibling,
+ id, domID);
+ }
+ }
+
+
+ }
+}
+
+
+/**
+ * Internal use only.
+ *
+ * Registers event handlers on a given menu.
+ */
+MenuManager.prototype.hookPopup =
+function mmgr_hookpop (node)
+{
+ node.addEventListener ("popupshowing", this.onPopupShowing, false);
+ node.addEventListener ("popuphiding", this.onPopupHiding, false);
+}
+
+/**
+ * Internal use only.
+ *
+ * |showPopup| is called from the "onpopupshowing" event of menus managed
+ * by the CommandManager. If a command is disabled, represents a command
+ * that cannot be "satisfied" by the current command context |cx|, or has an
+ * "enabledif" attribute that eval()s to false, then the menuitem is disabled.
+ * In addition "checkedif" and "visibleif" attributes are eval()d and
+ * acted upon accordingly.
+ */
+MenuManager.prototype.showPopup =
+function mmgr_showpop (event)
+{
+ /* returns true if the command context has the properties required to
+ * execute the command associated with |menuitem|.
+ */
+ function satisfied()
+ {
+ if (menuitem.hasAttribute("isSeparator") ||
+ !menuitem.hasAttribute("commandname"))
+ {
+ return true;
+ }
+
+ if (menuitem.hasAttribute("repeatfor"))
+ return false;
+
+ if (!("menuManager" in cx))
+ {
+ dd ("no menuManager in cx");
+ return false;
+ }
+
+ var name = menuitem.getAttribute("commandname");
+ var commandManager = cx.menuManager.commandManager;
+ var commands = commandManager.commands;
+
+ if (!ASSERT (name in commands,
+ "menu contains unknown command '" + name + "'"))
+ {
+ return false;
+ }
+
+ var rv = commandManager.isCommandSatisfied(cx, commands[name]);
+ delete cx.parseError;
+ return rv;
+ };
+
+ /* Convenience function for "enabledif", etc, attributes. */
+ function has (prop)
+ {
+ return (prop in cx);
+ };
+
+ /* evals the attribute named |attr| on the node |node|. */
+ function evalIfAttribute (node, attr)
+ {
+ var ex;
+ var expr = node.getAttribute(attr);
+ if (!expr)
+ return true;
+
+ expr = expr.replace (/\Wand\W/gi, " && ");
+ expr = expr.replace (/\Wor\W/gi, " || ");
+
+ try
+ {
+ return eval("(" + expr + ")");
+ }
+ catch (ex)
+ {
+ dd ("caught exception evaling '" + node.getAttribute("id") + "'.'" +
+ attr + "': '" + expr + "'\n" + ex);
+ }
+ return true;
+ };
+
+ /* evals the attribute named |attr| on the node |node|. */
+ function evalAttribute(node, attr)
+ {
+ var ex;
+ var expr = node.getAttribute(attr);
+ if (!expr)
+ return null;
+
+ try
+ {
+ return eval(expr);
+ }
+ catch (ex)
+ {
+ dd ("caught exception evaling '" + node.getAttribute("id") + "'.'" +
+ attr + "': '" + expr + "'\n" + ex);
+ }
+ return null;
+ };
+
+ var cx;
+ var popup = event.originalTarget;
+ var menuName = popup.getAttribute("menuName");
+
+ /* If the host provided a |contextFunction|, use it now. Remember the
+ * return result as this.cx for use if something from this menu is actually
+ * dispatched. */
+ if (typeof this.contextFunction == "function")
+ {
+ cx = this.cx = this.contextFunction(menuName, event);
+ }
+ else
+ {
+ cx = this.cx = { menuManager: this, originalEvent: event };
+ }
+
+ // Keep the context around by menu name. Removed in hidePopup.
+ this.cxStore[menuName] = cx;
+
+ var menuitem = popup.firstChild;
+ do
+ {
+ if (!menuitem.hasAttribute("repeatfor"))
+ continue;
+
+ // Remove auto-generated items (located prior to real item).
+ while (menuitem.previousSibling &&
+ menuitem.previousSibling.hasAttribute("repeatgenerated"))
+ {
+ menuitem.parentNode.removeChild(menuitem.previousSibling);
+ }
+
+ if (!("repeatList" in cx))
+ cx.repeatList = new Object();
+
+ /* Get the array of new items to add by evaluating "repeatfor" with
+ * "cx" in scope. Usually will return an already-calculated Array
+ * either from "cx" or somewhere in the object model.
+ */
+ var ary = evalAttribute(menuitem, "repeatfor");
+
+ if ((typeof ary != "object") || !isinstance(ary, Array))
+ ary = [];
+
+ /* The item itself should only be shown if there's no items in the
+ * array - this base item is always disabled.
+ */
+ if (ary.length > 0)
+ menuitem.setAttribute("hidden", "true");
+ else
+ menuitem.removeAttribute("hidden");
+
+ // Save the array in the context object.
+ cx.repeatList[menuitem.getAttribute("repeatid")] = ary;
+
+ /* Get the maximum number of items we're allowed to show from |ary| by
+ * evaluating "repeatlimit" with "cx" in scope. This could be a fixed
+ * limit or dynamically calculated (e.g. from prefs).
+ */
+ var limit = evalAttribute(menuitem, "repeatlimit");
+ // Make sure we've got a number at all...
+ if (typeof limit != "number")
+ limit = ary.length;
+ // ...and make sure it's no higher than |ary.length|.
+ limit = Math.min(ary.length, limit);
+
+ var cmd = menuitem.getAttribute("commandname");
+ var props = { repeatgenerated: true, repeatindex: -1,
+ repeatid: menuitem.getAttribute("repeatid"),
+ repeatmap: menuitem.getAttribute("repeatmap") };
+
+ /* Clone non-repeat attributes. All attributes except those starting
+ * with 'repeat', and those matching 'hidden' or 'disabled' are saved
+ * to |props|, which is then supplied to |appendMenuItem| later.
+ */
+ for (var i = 0; i < menuitem.attributes.length; i++)
+ {
+ var name = menuitem.attributes[i].nodeName;
+ if (!name.match(/^(repeat|(hidden|disabled)$)/))
+ props[name] = menuitem.getAttribute(name);
+ }
+
+ var lastGroup = "";
+ for (i = 0; i < limit; i++)
+ {
+ /* Check for groupings. For each item we add, if "repeatgroup" gives
+ * a different value, we insert a separator.
+ */
+ if (menuitem.getAttribute("repeatgroup"))
+ {
+ cx.index = i;
+ ary = cx.repeatList[menuitem.getAttribute("repeatid")];
+ var item = ary[i];
+ /* Apply any updates to "cx" for this item by evaluating
+ * "repeatmap" with "cx" and "item" in scope. This may just
+ * copy some attributes from "item" to "cx" or it may do more.
+ */
+ evalAttribute(menuitem, "repeatmap");
+ /* Get the item's group by evaluating "repeatgroup" with "cx"
+ * and "item" in scope. Usually will return an appropriate
+ * property from "item".
+ */
+ var group = evalAttribute(menuitem, "repeatgroup");
+
+ if ((i > 0) && (lastGroup != group))
+ this.appendMenuSeparator(popup, menuitem, props);
+
+ lastGroup = group;
+ }
+
+ props.repeatindex = i;
+ this.appendMenuItem(popup, menuitem, cmd, props);
+ }
+ } while ((menuitem = menuitem.nextSibling));
+
+ menuitem = popup.firstChild;
+ do
+ {
+ if (menuitem.hasAttribute("repeatgenerated") &&
+ menuitem.hasAttribute("repeatmap"))
+ {
+ cx.index = menuitem.getAttribute("repeatindex");
+ ary = cx.repeatList[menuitem.getAttribute("repeatid")];
+ var item = ary[cx.index];
+ /* Apply any updates to "cx" for this item by evaluating
+ * "repeatmap" with "cx" and "item" in scope. This may just
+ * copy some attributes from "item" to "cx" or it may do more.
+ */
+ evalAttribute(menuitem, "repeatmap");
+ }
+
+ /* should it be visible? */
+ if (menuitem.hasAttribute("visibleif"))
+ {
+ if (evalIfAttribute(menuitem, "visibleif"))
+ menuitem.removeAttribute ("hidden");
+ else
+ {
+ menuitem.setAttribute ("hidden", "true");
+ continue;
+ }
+ }
+
+ /* it's visible, maybe it has a dynamic label? */
+ if (menuitem.hasAttribute("format"))
+ {
+ var label = replaceVars(menuitem.getAttribute("format"), cx);
+ if (label.indexOf("\$") != -1)
+ label = menuitem.getAttribute("backupLabel");
+ menuitem.setAttribute("label", label);
+ }
+
+ /* ok, it's visible, maybe it should be disabled? */
+ if (satisfied())
+ {
+ if (menuitem.hasAttribute("enabledif"))
+ {
+ if (evalIfAttribute(menuitem, "enabledif"))
+ menuitem.removeAttribute ("disabled");
+ else
+ menuitem.setAttribute ("disabled", "true");
+ }
+ else
+ menuitem.removeAttribute ("disabled");
+ }
+ else
+ {
+ menuitem.setAttribute ("disabled", "true");
+ }
+
+ /* should it have a check? */
+ if (menuitem.hasAttribute("checkedif"))
+ {
+ if (evalIfAttribute(menuitem, "checkedif"))
+ menuitem.setAttribute ("checked", "true");
+ else
+ menuitem.removeAttribute ("checked");
+ }
+ } while ((menuitem = menuitem.nextSibling));
+
+ if (typeof this.onCallbackPopupShowing == "function")
+ this.onCallbackPopupShowing(event, cx, popup);
+
+ return true;
+}
+
+/**
+ * Internal use only.
+ *
+ * |hidePopup| is called from the "onpopuphiding" event of menus
+ * managed by the CommandManager. Clean up this.cxStore, but
+ * not this.cx because that messes up nested menus.
+ */
+MenuManager.prototype.hidePopup =
+function mmgr_hidepop(event)
+{
+ var popup = event.originalTarget;
+ var menuName = popup.getAttribute("menuName");
+
+ if (typeof this.onCallbackPopupHiding == "function")
+ this.onCallbackPopupHiding(event, this.cxStore[menuName], popup);
+
+ delete this.cxStore[menuName];
+
+ return true;
+}
+
+MenuManager.prototype.menuCommand =
+function mmgr_menucmd(event)
+{
+ /* evals the attribute named |attr| on the node |node|. */
+ function evalAttribute(node, attr)
+ {
+ var ex;
+ var expr = node.getAttribute(attr);
+ if (!expr)
+ return null;
+
+ try
+ {
+ return eval(expr);
+ }
+ catch (ex)
+ {
+ dd ("caught exception evaling '" + node.getAttribute("id") + "'.'" +
+ attr + "': '" + expr + "'\n" + ex);
+ }
+ return null;
+ };
+
+ var menuitem = event.originalTarget;
+ var cx = this.cx;
+ /* We need to re-run the repeat-map if the user has selected a special
+ * repeat-generated menu item, so that the context object is correct.
+ */
+ if (menuitem.hasAttribute("repeatgenerated") &&
+ menuitem.hasAttribute("repeatmap"))
+ {
+ cx.index = menuitem.getAttribute("repeatindex");
+ var ary = cx.repeatList[menuitem.getAttribute("repeatid")];
+ var item = ary[cx.index];
+ /* Apply any updates to "cx" for this item by evaluating
+ * "repeatmap" with "cx" and "item" in scope. This may just
+ * copy some attributes from "item" to "cx" or it may do more.
+ */
+ evalAttribute(menuitem, "repeatmap");
+ }
+
+ eval(this.commandStr);
+};
+
+
+/**
+ * Appends a sub-menu to an existing menu.
+ * @param parentNode DOM Node to insert into
+ * @param beforeNode DOM Node already contained by parentNode, to insert before
+ * @param domId ID of the sub-menu to add.
+ * @param label Text to use for this sub-menu.
+ * @param accesskey Accesskey to use for the sub-menu.
+ * @param attribs Object containing CSS attributes to set on the element.
+ */
+MenuManager.prototype.appendSubMenu =
+function mmgr_addsmenu(parentNode, beforeNode, menuName, domId, label,
+ accesskey, attribs)
+{
+ var document = parentNode.ownerDocument;
+
+ /* sometimes the menu is already there, for overlay purposes. */
+ var menu = document.getElementById(domId);
+
+ if (!menu)
+ {
+ menu = document.createElement ("menu");
+ menu.setAttribute ("id", domId);
+ }
+
+ var menupopup = menu.firstChild;
+
+ if (!menupopup)
+ {
+ menupopup = document.createElement ("menupopup");
+ menupopup.setAttribute ("id", domId + "-popup");
+ menu.appendChild(menupopup);
+ menupopup = menu.firstChild;
+ }
+
+ menupopup.setAttribute ("menuName", menuName);
+
+ menu.setAttribute("accesskey", accesskey);
+ label = label.replace("&", "");
+ menu.setAttribute ("label", label);
+ menu.setAttribute ("isSeparator", true);
+
+ // Only attach the menu if it's not there already. This can't be in the
+ // if (!menu) block because the updateMenus code clears toplevel menus,
+ // orphaning the submenus, to (parts of?) which we keep handles in the
+ // uiElements array. See the updateMenus code.
+ if (!menu.parentNode)
+ parentNode.insertBefore(menu, beforeNode);
+
+ if (typeof attribs == "object")
+ {
+ for (var p in attribs)
+ menu.setAttribute (p, attribs[p]);
+ }
+
+ this.hookPopup (menupopup);
+
+ return menupopup;
+}
+
+/**
+ * Appends a popup to an existing popupset.
+ * @param parentNode DOM Node to insert into
+ * @param beforeNode DOM Node already contained by parentNode, to insert before
+ * @param id ID of the popup to add.
+ * @param label Text to use for this popup. Popup menus don't normally have
+ * labels, but we set a "label" attribute anyway, in case
+ * the host wants it for some reason. Any "&" characters will
+ * be stripped.
+ * @param attribs Object containing CSS attributes to set on the element.
+ */
+MenuManager.prototype.appendPopupMenu =
+function mmgr_addpmenu (parentNode, beforeNode, menuName, id, label, attribs)
+{
+ var document = parentNode.ownerDocument;
+ var popup = document.createElement ("menupopup");
+ popup.setAttribute ("id", id);
+ if (label)
+ popup.setAttribute ("label", label.replace("&", ""));
+ if (typeof attribs == "object")
+ {
+ for (var p in attribs)
+ popup.setAttribute (p, attribs[p]);
+ }
+
+ popup.setAttribute ("menuName", menuName);
+
+ parentNode.insertBefore(popup, beforeNode);
+ this.hookPopup (popup);
+
+ return popup;
+}
+
+/**
+ * Appends a menuitem to an existing menu or popup.
+ * @param parentNode DOM Node to insert into
+ * @param beforeNode DOM Node already contained by parentNode, to insert before
+ * @param command A reference to the CommandRecord this menu item will represent.
+ * @param attribs Object containing CSS attributes to set on the element.
+ */
+MenuManager.prototype.appendMenuItem =
+function mmgr_addmenu (parentNode, beforeNode, commandName, attribs)
+{
+ var menuManager = this;
+
+ var document = parentNode.ownerDocument;
+ if (commandName == "-")
+ return this.appendMenuSeparator(parentNode, beforeNode, attribs);
+
+ var parentId = parentNode.getAttribute("id");
+
+ if (!ASSERT(commandName in this.commandManager.commands,
+ "unknown command " + commandName + " targeted for " +
+ parentId))
+ {
+ return null;
+ }
+
+ var command = this.commandManager.commands[commandName];
+ var menuitem = document.createElement ("menuitem");
+ menuitem.setAttribute ("id", parentId + ":" + commandName);
+ menuitem.setAttribute ("commandname", command.name);
+ // Add keys if this isn't a context menu:
+ if (parentId.indexOf("context") != 0)
+ menuitem.setAttribute("key", "key:" + command.name);
+ menuitem.setAttribute("accesskey", command.accesskey);
+ var label = command.label.replace("&", "");
+ menuitem.setAttribute ("label", label);
+ if (command.format)
+ {
+ menuitem.setAttribute("format", command.format);
+ menuitem.setAttribute("backupLabel", label);
+ }
+
+ if ((typeof attribs == "object") && attribs)
+ {
+ for (var p in attribs)
+ menuitem.setAttribute (p, attribs[p]);
+ if ("repeatfor" in attribs)
+ menuitem.setAttribute("repeatid", this.repeatId++);
+ }
+
+ command.uiElements.push(menuitem);
+ parentNode.insertBefore (menuitem, beforeNode);
+ /* It seems, bob only knows why, that this must be done AFTER the node is
+ * added to the document.
+ */
+ menuitem.addEventListener("command", this.onMenuCommand, false);
+
+ return menuitem;
+}
+
+/**
+ * Appends a menuseparator to an existing menu or popup.
+ * @param parentNode DOM Node to insert into
+ * @param beforeNode DOM Node already contained by parentNode, to insert before
+ * @param attribs Object containing CSS attributes to set on the element.
+ */
+MenuManager.prototype.appendMenuSeparator =
+function mmgr_addsep (parentNode, beforeNode, attribs)
+{
+ var document = parentNode.ownerDocument;
+ var menuitem = document.createElement ("menuseparator");
+ menuitem.setAttribute ("isSeparator", true);
+ if (typeof attribs == "object")
+ {
+ for (var p in attribs)
+ menuitem.setAttribute (p, attribs[p]);
+ }
+ parentNode.insertBefore (menuitem, beforeNode);
+
+ return menuitem;
+}
+
+/**
+ * Appends a toolbaritem to an existing box element.
+ * @param parentNode DOM Node to insert into
+ * @param beforeNode DOM Node already contained by parentNode, to insert before
+ * @param command A reference to the CommandRecord this toolbaritem will
+ * represent.
+ * @param attribs Object containing CSS attributes to set on the element.
+ */
+MenuManager.prototype.appendToolbarItem =
+function mmgr_addtb (parentNode, beforeNode, commandName, attribs)
+{
+ if (commandName == "-")
+ return this.appendToolbarSeparator(parentNode, beforeNode, attribs);
+
+ var parentId = parentNode.getAttribute("id");
+
+ if (!ASSERT(commandName in this.commandManager.commands,
+ "unknown command " + commandName + " targeted for " +
+ parentId))
+ {
+ return null;
+ }
+
+ var command = this.commandManager.commands[commandName];
+ var document = parentNode.ownerDocument;
+ var tbitem = document.createElement ("toolbarbutton");
+
+ var id = parentNode.getAttribute("id") + ":" + commandName;
+ tbitem.setAttribute ("id", id);
+ tbitem.setAttribute ("class", "toolbarbutton-1");
+ if (command.tip)
+ tbitem.setAttribute ("tooltiptext", command.tip);
+ tbitem.setAttribute ("label", command.label.replace("&", ""));
+ tbitem.setAttribute ("oncommand",
+ "dispatch('" + commandName + "');");
+ if (typeof attribs == "object")
+ {
+ for (var p in attribs)
+ tbitem.setAttribute (p, attribs[p]);
+ }
+
+ command.uiElements.push(tbitem);
+ parentNode.insertBefore (tbitem, beforeNode);
+
+ return tbitem;
+}
+
+/**
+ * Appends a toolbarseparator to an existing box.
+ * @param parentNode DOM Node to insert into
+ * @param beforeNode DOM Node already contained by parentNode, to insert before
+ * @param attribs Object containing CSS attributes to set on the element.
+ */
+MenuManager.prototype.appendToolbarSeparator =
+function mmgr_addmenu (parentNode, beforeNode, attribs)
+{
+ var document = parentNode.ownerDocument;
+ var tbitem = document.createElement ("toolbarseparator");
+ tbitem.setAttribute ("isSeparator", true);
+ if (typeof attribs == "object")
+ {
+ for (var p in attribs)
+ tbitem.setAttribute (p, attribs[p]);
+ }
+ parentNode.appendChild (tbitem);
+
+ return tbitem;
+}
+
+/**
+ * Creates menu DOM nodes from a menu specification.
+ * @param parentNode DOM Node to insert into
+ * @param beforeNode DOM Node already contained by parentNode, to insert before
+ * @param menuSpec array of menu items
+ */
+MenuManager.prototype.createMenu =
+function mmgr_newmenu (parentNode, beforeNode, menuName, domId, attribs)
+{
+ if (typeof domId == "undefined")
+ domId = menuName;
+
+ if (!ASSERT(menuName in this.menuSpecs, "unknown menu name " + menuName))
+ return null;
+
+ var menuSpec = this.menuSpecs[menuName];
+ if (!("accesskey" in menuSpec))
+ menuSpec.accesskey = getAccessKey(menuSpec.label);
+
+ var subMenu = this.appendSubMenu(parentNode, beforeNode, menuName, domId,
+ menuSpec.label, menuSpec.accesskey,
+ attribs);
+
+ // Keep track where we're adding popup nodes derived from some menuSpec
+ if (!("uiElements" in this.menuSpecs[menuName]))
+ this.menuSpecs[menuName].uiElements = [subMenu];
+ else if (!arrayContains(this.menuSpecs[menuName].uiElements, subMenu))
+ this.menuSpecs[menuName].uiElements.push(subMenu);
+
+ this.createMenuItems (subMenu, null, menuSpec.items);
+ return subMenu;
+}
+
+MenuManager.prototype.createMenuItems =
+function mmgr_newitems (parentNode, beforeNode, menuItems)
+{
+ function itemAttribs()
+ {
+ return (1 in menuItems[i]) ? menuItems[i][1] : null;
+ };
+
+ var parentId = parentNode.getAttribute("id");
+
+ for (var i in menuItems)
+ {
+ var itemName = menuItems[i][0];
+ if (itemName[0] == ">")
+ {
+ itemName = itemName.substr(1);
+ if (!ASSERT(itemName in this.menuSpecs,
+ "unknown submenu " + itemName + " referenced in " +
+ parentId))
+ {
+ continue;
+ }
+ this.createMenu (parentNode, beforeNode, itemName,
+ parentId + ":" + itemName, itemAttribs());
+ }
+ else if (itemName in this.commandManager.commands)
+ {
+ this.appendMenuItem (parentNode, beforeNode, itemName,
+ itemAttribs());
+ }
+ else if (itemName == "-")
+ {
+ this.appendMenuSeparator (parentNode, beforeNode, itemAttribs());
+ }
+ else
+ {
+ dd ("unknown command " + itemName + " referenced in " + parentId);
+ }
+ }
+}
+