Revision control

Copy as Markdown

Other Tools

<?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"
<binding id="prefwindow"
<resources>
</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[
// 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,
"_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"
<resources>
</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>