/* 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/. */ XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI", "resource:///modules/CustomizableUI.jsm"); /** * Maintains the state and dispatches events for the main menu panel. */ const PanelUI = { /** Panel events that we listen for. **/ get kEvents() ["popupshowing", "popupshown", "popuphiding", "popuphidden"], /** * Used for lazily getting and memoizing elements from the document. Lazy * getters are set in init, and memoizing happens after the first retrieval. */ get kElements() { return { contents: "PanelUI-contents", mainView: "PanelUI-mainView", multiView: "PanelUI-multiView", helpView: "PanelUI-help", menuButton: "PanelUI-menu-button", panel: "PanelUI-popup" }; }, init: function() { for (let [k, v] of Iterator(this.kElements)) { // Need to do fresh let-bindings per iteration let getKey = k; let id = v; this.__defineGetter__(getKey, function() { delete this[getKey]; return this[getKey] = document.getElementById(id); }); } for (let event of this.kEvents) { this.panel.addEventListener(event, this); } this.helpView.addEventListener("ViewShowing", this._onHelpViewShow, false); this.helpView.addEventListener("ViewHiding", this._onHelpViewHide, false); }, uninit: function() { for (let event of this.kEvents) { this.panel.removeEventListener(event, this); } this.helpView.removeEventListener("ViewShowing", this._onHelpViewShow); this.helpView.removeEventListener("ViewHiding", this._onHelpViewHide); }, /** * Customize mode extracts the mainView and puts it somewhere else while the * user customizes. Upon completion, this function can be called to put the * panel back to where it belongs in normal browsing mode. * * @param aMainView * The mainView node to put back into place. */ setMainView: function(aMainView) { this.multiView.setMainView(aMainView); }, /** * Opens the menu panel if it's closed, or closes it if it's * open. If the event target has a child with the toolbarbutton-icon * attribute, the panel will be anchored on that child. Otherwise, the panel * is anchored on the event target itself. * * @param aEvent the event that triggers the toggle. */ toggle: function(aEvent) { if (this.panel.state == "open") { this.hide(); } else if (this.panel.state == "closed") { this.ensureRegistered(); this.panel.hidden = false; let anchor = aEvent.target; let iconAnchor = document.getAnonymousElementByAttribute(anchor, "class", "toolbarbutton-icon"); this.panel.openPopup(iconAnchor || anchor, "bottomcenter topright"); } }, /** * If the menu panel is being shown, hide it. */ hide: function() { this.panel.hidePopup(); }, handleEvent: function(aEvent) { switch (aEvent.type) { case "popupshowing": // Fall through case "popupshown": // Fall through case "popuphiding": // Fall through case "popuphidden": { this._updatePanelButton(aEvent.target); break; } } }, /** * Registering the menu panel is done lazily for performance reasons. This * method is exposed so that CustomizationMode can force registration in the * event that customization mode is started before the panel has had a chance * to register itself. * * @param aCustomizing (optional) set to true if this was called while entering * customization mode. If that's the case, we trust that customization * mode will handle calling beginBatchUpdate and endBatchUpdate. */ ensureRegistered: function(aCustomizing=false) { if (aCustomizing) { CustomizableUI.registerMenuPanel(this.contents); } else { this.beginBatchUpdate(); CustomizableUI.registerMenuPanel(this.contents); this.endBatchUpdate(); } }, /** * Switch the panel to the main view if it's not already * in that view. */ showMainView: function() { this.multiView.showMainView(); }, /** * Switch the panel to the help view if it's not already * in that view. */ showHelpView: function(aAnchor) { this.multiView.showSubView("PanelUI-help", aAnchor); }, /** * Shows a subview in the panel with a given ID. * * @param aViewId the ID of the subview to show. * @param aAnchor the element that spawned the subview. * @param aPlacementArea the CustomizableUI area that aAnchor is in. */ showSubView: function(aViewId, aAnchor, aPlacementArea) { let viewNode = document.getElementById(aViewId); if (!viewNode) { Cu.reportError("Could not show panel subview with id: " + aViewId); return; } if (!aAnchor) { Cu.reportError("Expected an anchor when opening subview with id: " + aViewId); return; } if (aPlacementArea == CustomizableUI.AREA_PANEL) { this.multiView.showSubView(aViewId, aAnchor); } else { // Emit the ViewShowing event so that the widget definition has a chance // to lazily populate the subview with things. let evt = document.createEvent("CustomEvent"); evt.initCustomEvent("ViewShowing", true, true, viewNode); viewNode.dispatchEvent(evt); if (evt.defaultPrevented) { return; } let tempPanel = document.createElement("panel"); tempPanel.setAttribute("type", "arrow"); tempPanel.setAttribute("id", "customizationui-widget-panel"); tempPanel.setAttribute("level", "top"); document.getElementById(CustomizableUI.AREA_NAVBAR).appendChild(tempPanel); let multiView = document.createElement("panelmultiview"); tempPanel.appendChild(multiView); multiView.setMainView(viewNode); CustomizableUI.addPanelCloseListeners(tempPanel); tempPanel.addEventListener("popuphidden", function panelRemover() { tempPanel.removeEventListener("popuphidden", panelRemover); CustomizableUI.removePanelCloseListeners(tempPanel); let evt = new CustomEvent("ViewHiding", {detail: viewNode}); viewNode.dispatchEvent(evt); this.multiView.appendChild(viewNode); tempPanel.parentElement.removeChild(tempPanel); }.bind(this)); let iconAnchor = document.getAnonymousElementByAttribute(aAnchor, "class", "toolbarbutton-icon"); tempPanel.openPopup(iconAnchor || aAnchor, "bottomcenter topright"); } }, /** * This function can be used as a command event listener for subviews * so that the panel knows if and when to close itself. */ onCommandHandler: function(aEvent) { if (!aEvent.originalTarget.hasAttribute("noautoclose")) { PanelUI.hide(); } }, /** * Signal that we're about to make a lot of changes to the contents of the * panels all at once. For performance, we ignore the mutations. */ beginBatchUpdate: function() { this.multiView.ignoreMutations = true; }, /** * Signal that we're done making bulk changes to the panel. We now pay * attention to mutations. This automatically synchronizes the multiview * container with whichever view is displayed if the panel is open. */ endBatchUpdate: function(aReason) { this.multiView.ignoreMutations = false; }, /** * Sets the anchor node into the open or closed state, depending * on the state of the panel. */ _updatePanelButton: function() { this.menuButton.open = this.panel.state == "open" || this.panel.state == "showing"; }, _onHelpViewShow: function(aEvent) { // Call global menu setup function buildHelpMenu(); let helpMenu = document.getElementById("menu_HelpPopup"); let items = this.getElementsByTagName("vbox")[0]; let attrs = ["oncommand", "onclick", "label", "key", "disabled"]; let NSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; // Remove all buttons from the view while (items.firstChild) { items.removeChild(items.firstChild); } // Add the current set of menuitems of the Help menu to this view let menuItems = Array.prototype.slice.call(helpMenu.getElementsByTagName("menuitem")); let fragment = document.createDocumentFragment(); for (let node of menuItems) { if (node.hidden) continue; let button = document.createElementNS(NSXUL, "toolbarbutton"); // Copy specific attributes from a menuitem of the Help menu for (let attrName of attrs) { if (!node.hasAttribute(attrName)) continue; button.setAttribute(attrName, node.getAttribute(attrName)); } fragment.appendChild(button); } items.appendChild(fragment); this.addEventListener("command", PanelUI.onCommandHandler); }, _onHelpViewHide: function(aEvent) { this.removeEventListener("command", PanelUI.onCommandHandler); } };