diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/suite/chatzilla/js/lib/menu-manager.js | |
parent | Initial commit. (diff) | |
download | thunderbird-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.js | 848 |
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); + } + } +} + |