<?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 % dtdPlatform    SYSTEM "chrome://communicator-platform/locale/pref/platformPrefOverlay.dtd"> %dtdPlatform;
  <!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://global/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[
          // 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 wm = Components.classes['@mozilla.org/appshell/window-mediator;1']
                               .getService(Components.interfaces.nsIWindowMediator);
          const iterator = 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.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 = Components.classes["@mozilla.org/embedcomp/dialogparam;1"]
                                     .createInstance(Components.interfaces.nsIDialogParamBlock);
            params.SetNumberStrings(2);
            params.SetString(0, helpFileURI);
            params.SetString(1, topic);
            const ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
                                 .getService(Components.interfaces.nsIWindowWatcher);
            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://global/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.
        var subScriptLoader = null;
        try
        {
          subScriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
                                      .getService(Components.interfaces.mozIJSSubScriptLoader);
        }
        catch (e)
        {
          var msg = 'prefpane.paneload: no mozIJSSubScriptLoader!\n' + e;
          Components.utils.reportError(msg);
          return;
        }

        // 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
            {
              subScriptLoader.loadSubScript(script, this);
            }
            catch (e)
            {
              var msg = 'prefpane.paneload: loadSubScript("'
                      + script + '") failed:\n' + e;
              Components.utils.reportError(msg);
            }
          }
        }

        // if we have a Startup method, call it
        if ('Startup' in this)
          this.Startup();
      ]]>
      </handler>
    </handlers>
  </binding>

</bindings>
