diff options
Diffstat (limited to 'comm/suite/components/bindings/prefwindow.xml')
-rw-r--r-- | comm/suite/components/bindings/prefwindow.xml | 548 |
1 files changed, 548 insertions, 0 deletions
diff --git a/comm/suite/components/bindings/prefwindow.xml b/comm/suite/components/bindings/prefwindow.xml new file mode 100644 index 0000000000..64173b6c76 --- /dev/null +++ b/comm/suite/components/bindings/prefwindow.xml @@ -0,0 +1,548 @@ +<?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/. --> + +<!-- + SeaMonkey Extended Preferences Window Framework + + The binding implemented here mostly works like its toolkit ancestor, with + one important difference: the <prefwindow> recognizes the first <tree> in + its content and assumes that this is the navigation tree: + + <prefwindow> + <tree> + ... + <treeitem id="prefTreeItemA" prefpane="prefPaneA"> + ... + </tree> + <prefpane id="prefPaneB">...</prefpane> + <prefpane id="prefPaneA"> + </prefwindow> + + The <tree> structure defines the hierarchical layout of the preference + window's navigation tree. A <treeitem>'s "prefpane" attribute references + one of the <prefpane>s given on the <prefwindow>'s main level. + All <prefpane>s not referenced by a <treeitem> will be appended to the + navigation tree's top level. <treeitem>s can be nested as needed, but + <treeitem>s without a related <prefpane> will be hidden. + + Furthermore, if the <prefwindow> has attribute "autopanes" set to "true", + non-existing <prefpane>s will be generated automatically from certain + attributes of the <treeitem>: + - "url" must contain the <prefpane>'s url + - "prefpane" should contain the <prefpane>'s desired id, + otherwise its url will be used as id + - "helpTopic" may contain an index into SeaMonkey's help + + Unlike in XPFE, where preferences panels were loaded into a separate + iframe, <prefpane>s are an integral part of the <prefwindow> document, + by virtue of loadOverlay. Hence <script>s will be loaded into the + <prefwindow> scope and possibly clash. To avoid this, <prefpane>s should + specify a "script" attribute with a whitespace delimited list of scripts + to load into the <prefpane>'s context. The subscriptloader will take care + of any internal scoping, so no this.* fest is necessary inside the script. + + <prefwindow> users who want to share the very same file between SeaMonkey + and other toolkit apps should hide the <tree> (set <tree>.hidden=true); + this binding will then unhide the <tree> if necessary, ie more than just + one <prefpane> exists. + Also, the <tree> will get the class "prefnavtree" added, so that it may be + prestyled by the SeaMonkey themes. + Setting <prefwindow xpfe="false"> will enforce the application of just the + basic toolkit <prefwindow> even in SeaMonkey. The same "xpfe" attribute + exists for <prefpane>, too. +--> + +<!DOCTYPE bindings [ + <!ENTITY % dtdPrefs SYSTEM "chrome://communicator/locale/pref/preferences.dtd"> %dtdPrefs; + <!ENTITY % dtdGlobalPrefs SYSTEM "chrome://global/locale/preferences.dtd"> %dtdGlobalPrefs; +]> + +<bindings id="prefwindowBindings" + xmlns="http://www.mozilla.org/xbl" + xmlns:xbl="http://www.mozilla.org/xbl" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <binding id="prefwindow" + extends="chrome://communicator/content/bindings/preferences.xml#prefwindow"> + <resources> + <stylesheet src="chrome://communicator/skin/preferences.css"/> + </resources> + + <!-- The only difference between the following <content> and its toolkit + ancestor is the help button and the 'navTrees' <vbox> before the 'paneDeck'! --> + <content dlgbuttons="accept,cancel" persist="lastSelected screenX screenY" + closebuttonlabel="&preferencesCloseButton.label;" + closebuttonaccesskey="&preferencesCloseButton.accesskey;" + role="dialog"> + <xul:radiogroup anonid="selector" orient="horizontal" class="paneSelector chromeclass-toolbar" + role="listbox"/> <!-- Expose to accessibility APIs as a listbox --> + <xul:hbox flex="1" class="paneDeckContainer"> + <xul:vbox anonid="navTrees"> + <children includes="tree"/> + </xul:vbox> + <xul:vbox flex="1"> + <xul:dialogheader anonid="paneHeader" hidden="true"/> + <xul:deck anonid="paneDeck" flex="1"> + <children includes="prefpane"/> + </xul:deck> + </xul:vbox> + </xul:hbox> + <xul:hbox anonid="dlg-buttons" class="prefWindow-dlgbuttons" pack="end"> +#ifdef XP_UNIX + <xul:button dlgtype="disclosure" class="dialog-button" hidden="true"/> + <xul:button dlgtype="help" class="dialog-button" hidden="true" icon="help"/> + <xul:button dlgtype="extra2" class="dialog-button" hidden="true"/> + <xul:button dlgtype="extra1" class="dialog-button" hidden="true"/> + <xul:spacer anonid="spacer" flex="1"/> + <xul:button dlgtype="cancel" class="dialog-button" icon="cancel"/> + <xul:button dlgtype="accept" class="dialog-button" icon="accept"/> +#else + <xul:button dlgtype="extra2" class="dialog-button" hidden="true"/> + <xul:spacer anonid="spacer" flex="1"/> + <xul:button dlgtype="accept" class="dialog-button" icon="accept"/> + <xul:button dlgtype="extra1" class="dialog-button" hidden="true"/> + <xul:button dlgtype="cancel" class="dialog-button" icon="cancel"/> + <xul:button dlgtype="help" class="dialog-button" hidden="true" icon="help"/> + <xul:button dlgtype="disclosure" class="dialog-button" hidden="true"/> +#endif + </xul:hbox> + <xul:hbox> + <children/> + </xul:hbox> + </content> + + <implementation> + <constructor> + <![CDATA[ + var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); + + // grab the first child tree and try to tie it to the prefpanes + var tree = this.getElementsByTagName('tree')[0]; + this.initNavigationTree(tree); + // hide the toolkit pref strip if we have a tree + if (this._navigationTree) + this._selector.hidden = true; + ]]> + </constructor> + + <field name="_navigationTree">null</field> + + <!-- <prefwindow> users can call this method to exchange the <tree> --> + <method name="initNavigationTree"> + <parameter name="aTreeElement"/> + <body> + <![CDATA[ + this._navigationTree = null; + if (!aTreeElement) + return; + + // don't grab trees in prefpanes etc. + if (aTreeElement.parentNode != this) + return; + + // autogenerate <prefpane>s from <treecell>.url if requested + var autopanes = (this.getAttribute('autopanes') == 'true'); + if (!autopanes) + { + // without autopanes, we can return early: don't bother + // with a navigation tree if we only have one prefpane + aTreeElement.hidden = (this.preferencePanes.length < 2); + if (aTreeElement.hidden) + return; + } + + // ensure that we have a tree body + if (!aTreeElement.getElementsByTagName('treechildren').length) + aTreeElement.appendChild(document.createElement('treechildren')); + + // ensure that we have a tree column + if (!aTreeElement.getElementsByTagName('treecol').length) + { + var navcols = document.createElement('treecols'); + var navcol = document.createElement('treecol'); + navcol.setAttribute('id', 'navtreecol'); + navcol.setAttribute('primary', true); + navcol.setAttribute('flex', 1); + navcol.setAttribute('hideheader', true); + navcols.appendChild(navcol); + aTreeElement.appendChild(navcols); + aTreeElement.setAttribute('hidecolumnpicker', true); + } + + // add the class "prefnavtree", so that themes can set defaults + aTreeElement.className += ' prefnavtree'; + + // Do some magic with the treeitem ingredient: + // - if it has a label attribute but no treerow child, + // generate a treerow with a treecell child with that label + // - if it has a prefpane attribute, tie it to that panel + // - if still no panel found and a url attribute is present, + // autogenerate the prefpane and connect to it + var treeitems = aTreeElement.getElementsByTagName('treeitem'); + for (var i = 0; i < treeitems.length; ++i) + { + var node = treeitems[i]; + var label = node.getAttribute('label'); + if (label) + { + // autocreate the treecell? + var row = node.firstChild; + while (row && row.nodeName != 'treerow') + row = row.nextSibling; + if (!row) + { + var itemrow = document.createElement('treerow'); + var itemcell = document.createElement('treecell'); + itemcell.setAttribute('label', label); + itemrow.appendChild(itemcell); + node.appendChild(itemrow); + } + } + var paneID = node.getAttribute('prefpane'); + var pane = paneID && document.getElementById(paneID); + if (!pane && autopanes) + { + // if we have a url, create a <prefpane> for it + var paneURL = node.getAttribute('url'); + if (paneURL) + { + // reuse paneID if present, else use the url as id + pane = document.createElement('prefpane'); + pane.setAttribute('id', paneID || paneURL); + pane.setAttribute('src', paneURL); + pane.setAttribute('label', label || paneID || paneURL); + var helpTopic = node.getAttribute('helpTopic'); + if (helpTopic) + { + pane.setAttribute('helpURI', 'chrome://communicator/locale/help/suitehelp.rdf'); + pane.setAttribute('helpTopic', helpTopic); + } + // add pane to prefwindow + this.appendChild(pane); + } + } + node.prefpane = pane; + if (pane) + pane.preftreeitem = node; + // hide unused treeitems + node.hidden = !pane; + } + + // now that the number of <prefpane>s is known, try to return early: + // don't bother with a navigation tree if we only have one prefpane + aTreeElement.hidden = (this.preferencePanes.length < 2); + if (aTreeElement.hidden) + return; + this._navigationTree = aTreeElement; + + // append any still unreferenced <prefpane>s to the tree's top level + for (var j = 0; j < this.preferencePanes.length; ++j) + { + // toolkit believes in fancy pane resizing - we don't + var lostpane = this.preferencePanes[j]; + lostpane.setAttribute('flex', 1); + + if (!("preftreeitem" in lostpane)) + { + var treebody = this._navigationTree + .getElementsByTagName('treechildren')[0]; + var treeitem = document.createElement('treeitem'); + var treerow = document.createElement('treerow'); + var treecell = document.createElement('treecell'); + var label = lostpane.getAttribute('label'); + if (!label) + label = lostpane.getAttribute('id'); + treecell.setAttribute('label', label); + treerow.appendChild(treecell); + treeitem.appendChild(treerow); + treebody.appendChild(treeitem); + treeitem.prefpane = lostpane; + lostpane.preftreeitem = treeitem; + } + } + + // Some parts of the toolkit base binding's initialization code (like + // panel select events) "fire" before we get here. Thus, we may need + // to sync the tree manually now (again), if we added any panels or + // if toolkit failed to select one. + // (This is a loose copy from the toolkit ctor.) + var lastPane = this.lastSelected && + document.getElementById(this.lastSelected); + if (!lastPane) + this.lastSelected = ""; + if ("arguments" in window && window.arguments[0]) + { + var initialPane = document.getElementById(window.arguments[0]); + if (initialPane && initialPane.nodeName == "prefpane") + { + this.currentPane = initialPane; + this.lastSelected = initialPane.id; + } + } + else if (lastPane) + this.currentPane = lastPane; + try + { + this.showPane(this.currentPane); // may need to load it first + this.syncTreeWithPane(this.currentPane, true); + } + catch (e) + { + dump('***** broken prefpane: ' + this.currentPane.id + '\n' + e + '\n'); + } + ]]> + </body> + </method> + + <!-- don't do any fancy animations --> + <property name="_shouldAnimate" onget="return false;"/> + + <method name="setPaneTitle"> + <parameter name="aPaneElement"/> + <body> +#ifndef XP_MACOSX + <![CDATA[ + // show pane title, if given + var paneHeader = document.getAnonymousElementByAttribute(this, 'anonid', 'paneHeader'); + var paneHeaderLabel = ''; + if (aPaneElement) + paneHeaderLabel = aPaneElement.getAttribute('label'); + paneHeader.hidden = !paneHeaderLabel; + if (!paneHeader.hidden) + paneHeader.setAttribute('title', paneHeaderLabel); + ]]> +#endif + </body> + </method> + + <method name="syncPaneWithTree"> + <parameter name="aTreeIndex"/> + <body> + <![CDATA[ + var pane = null; + if ((this._navigationTree) && (aTreeIndex >= 0)) + { + // load the prefpane associated with this treeitem + var treeitem = this._navigationTree.contentView + .getItemAtIndex(aTreeIndex); + if ('prefpane' in treeitem) + { + pane = treeitem.prefpane; + if (pane && (this.currentPane != pane)) + { + try + { + this.showPane(pane); // may need to load it first + } + catch (e) + { + dump('***** broken prefpane: ' + pane.id + '\n' + e + '\n'); + pane = null; + } + } + } + } + // don't show broken panels + this._paneDeck.hidden = (pane == null); + this.setPaneTitle(pane); + ]]> + </body> + </method> + + <method name="syncTreeWithPane"> + <parameter name="aPane"/> + <parameter name="aExpand"/> + <body> + <![CDATA[ + if (this._navigationTree && aPane) + { + if ('preftreeitem' in aPane) + { + // make sure the treeitem is visible + var container = aPane.preftreeitem; + if (!aExpand) + container = container.parentNode.parentNode; + while (container != this._navigationTree) + { + container.setAttribute('open', true); + container = container.parentNode.parentNode; + } + + // mark selected pane in navigation tree + var index = this._navigationTree.contentView + .getIndexOfItem(aPane.preftreeitem); + this._navigationTree.view.selection.select(index); + } + } + this.setPaneTitle(aPane); + if (this.getAttribute("overflow") != "auto") + { + if (this.scrollHeight > window.innerHeight) + window.innerHeight = this.scrollHeight; + if (this.scrollWidth > window.innerWidth) + window.innerWidth = this.scrollWidth; + } + ]]> + </body> + </method> + + <!-- copied from contextHelp.js + Locate existing help window for this helpFileURI. --> + <method name="locateHelpWindow"> + <parameter name="helpFileURI"/> + <body> + <![CDATA[ + const iterator = Services.wm.getEnumerator("suite:help"); + var topWindow = null; + var aWindow; + + // Loop through help windows looking for one with selected helpFileURI + while (iterator.hasMoreElements()) + { + aWindow = iterator.getNext(); + if (aWindow.closed) + continue; + if (aWindow.getHelpFileURI() == helpFileURI) + topWindow = aWindow; + } + return topWindow; + ]]> + </body> + </method> + + <!-- copied from contextHelp.js + Opens up the Help Viewer with the specified topic and helpFileURI. --> + <method name="openHelp"> + <parameter name="topic"/> + <parameter name="helpFileURI"/> + <body> + <![CDATA[ + // Empty help windows are not helpful... + if (!helpFileURI) + return; + + // Try to find previously opened help. + var topWindow = this.locateHelpWindow(helpFileURI); + if (topWindow) + { + // Open topic in existing window. + topWindow.focus(); + topWindow.displayTopic(topic); + } + else + { + // Open topic in new window. + const params = Cc["@mozilla.org/embedcomp/dialogparam;1"] + .createInstance(Ci.nsIDialogParamBlock); + params.SetNumberStrings(2); + params.SetString(0, helpFileURI); + params.SetString(1, topic); + Services.ww.openWindow(null, + "chrome://help/content/help.xul", + "_blank", + "chrome,all,alwaysRaised,dialog=no", + params); + } + ]]> + </body> + </method> + </implementation> + + <handlers> + <handler event="dialoghelp"> + <![CDATA[ + this.openHelp(this.currentPane.helpTopic, this.currentPane.getAttribute("helpURI")); + ]]> + </handler> + <handler event="select"> + <![CDATA[ + // navigation tree select or deck change? + var target = event.originalTarget; + if (target == this._navigationTree) + { + this.syncPaneWithTree(target.currentIndex); + } + else if (target == this._paneDeck) + { + // deck.selectedIndex is a string! + var pane = this.preferencePanes[Number(target.selectedIndex)]; + this.syncTreeWithPane(pane, false); + } + ]]> + </handler> + + <handler event="paneload"> + <![CDATA[ + // panes may load asynchronously, + // so we have to "late-sync" those to our navigation tree + this.syncTreeWithPane(event.originalTarget, false); + ]]> + </handler> + + <handler event="keypress" key="&focusSearch.key;" modifiers="accel"> + <![CDATA[ + var searchBox = this.currentPane.getElementsByAttribute("type", "search")[0]; + if (searchBox) + { + searchBox.focus(); + event.stopPropagation(); + event.preventDefault(); + } + ]]> + </handler> + </handlers> + </binding> + + <binding id="prefpane" + extends="chrome://communicator/content/bindings/preferences.xml#prefpane"> + <resources> + <stylesheet src="chrome://communicator/skin/preferences.css"/> + </resources> + + <handlers> + <handler event="paneload"> + <![CDATA[ + // Since all <prefpane>s now share the same global document, their + // <script>s might clash. Thus we expect the "script" attribute to + // contain a whitespace delimited list of script files to be loaded + // into the <prefpane>'s context. + + // list of scripts to load + var scripts = this.getAttribute('script').match(/\S+/g); + if (!scripts) + return; + var count = scripts.length; + for (var i = 0; i < count; ++i) + { + var script = scripts[i]; + if (script) + { + try + { + Services.scriptloader.loadSubScript(script, this); + } + catch (e) + { + let errorStr = + "prefpane.paneload: loadSubScript(" + script + ") failed:\n" + + (e.fileName ? "at " + e.fileName + " : " + e.lineNumber + "\n" + : "") + + e + " - " + e.stack + "\n"; + dump(errorStr); + Cu.reportError(errorStr); + } + } + } + + // if we have a Startup method, call it + if ('Startup' in this) + this.Startup(); + ]]> + </handler> + </handlers> + </binding> + +</bindings> |