<?xml version="1.0"?>

<!DOCTYPE bindings [
  <!ENTITY % wizardDTD SYSTEM "chrome://global/locale/wizard.dtd">
  %wizardDTD;
]>

<bindings id="wizardBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="wizard-base">
    <resources>
      <stylesheet src="chrome://global/skin/wizard.css"/>
    </resources>
  </binding>
  
  <binding id="wizard" extends="chrome://global/content/bindings/general.xml#root-element">
    <resources>
      <stylesheet src="chrome://global/skin/wizard.css"/>
    </resources>
    <content>
      <xul:hbox class="wizard-header" anonid="Header"/>
      
      <xul:deck class="wizard-page-box" flex="1" anonid="Deck">
        <children includes="wizardpage"/>
      </xul:deck>
      <children/>
      
      <xul:hbox class="wizard-buttons" anonid="Buttons" xbl:inherits="pagestep,firstpage,lastpage"/>
    </content>
    
    <implementation>
      <property name="title" onget="return document.title;"
                             onset="return document.title = val;"/>

      <property name="canAdvance" onget="return this._canAdvance;"
                                  onset="this._nextButton.disabled = !val; return this._canAdvance = val;"/>
      <property name="canRewind" onget="return this._canRewind;"
                                 onset="this._backButton.disabled = !val; return this._canRewind = val;"/>

      <property name="pageStep" readonly="true" onget="return this._pageStack.length"/>

      <field name="pageCount">0</field>
      
      <field name="_accessMethod">null</field>
      <field name="_pageStack">null</field>
      <field name="_currentPage">null</field>

      <property name="wizardPages">
        <getter>
        <![CDATA[
          var xulns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
          return this.getElementsByTagNameNS(xulns, "wizardpage");
        ]]>
        </getter>
      </property>

      <property name="currentPage" onget="return this._currentPage">
        <setter>
        <![CDATA[
          if (!val)
            return val;
            
          this._currentPage = val;

          // Setting this attribute allows wizard's clients to dynamically
          // change the styles of each page based on purpose of the page. 
          this.setAttribute("currentpageid", val.pageid);
                    
          if (this.onFirstPage) {
            this.canRewind = false;
            this.setAttribute("firstpage", "true");
          } else {
            this.canRewind = true;
            this.setAttribute("firstpage", "false");
          }
                    
          if (this.onLastPage) {
            this.canAdvance = true;
            this.setAttribute("lastpage", "true");
          } else {
            this.setAttribute("lastpage", "false");
          }

          this._deck.setAttribute("selectedIndex", val.pageIndex);
          this._advanceFocusToPage(val);

          this._adjustWizardHeader();
          this._wizardButtons.onPageChange();

          this._fireEvent(val, "pageshow");
          
          return val;
        ]]>
        </setter>
      </property>

      <property name="pageIndex"
                onget="return this._currentPage ? this._currentPage.pageIndex : -1;">
        <setter>
        <![CDATA[
          if (val < 0 || val >= this.pageCount)
            return val;

          var page = this.wizardPages[val];
          this._pageStack[this._pageStack.length-1] = page;
          this.currentPage = page;

          return val;
        ]]>
        </setter>
      </property>

      <property name="onFirstPage" readonly="true"
                onget="return this._pageStack.length == 1;"/>

      <property name="onLastPage" readonly="true">
        <getter><![CDATA[
          var cp = this.currentPage;
          return cp && ((this._accessMethod == "sequential" && cp.pageIndex == this.pageCount-1) ||
                       (this._accessMethod == "random" && cp.next == ""));
         ]]></getter>
      </property>

      <method name="getButton">
        <parameter name="aDlgType"/>
        <body>
        <![CDATA[
          var btns = this.getElementsByAttribute("dlgtype", aDlgType);
          return btns.item(0) ? btns[0] : document.getAnonymousElementByAttribute(this._wizardButtons, "dlgtype", aDlgType);
        ]]>
        </body>
      </method>

      <field name="_canAdvance"/>
      <field name="_canRewind"/>
      <field name="_wizardHeader"/>
      <field name="_wizardButtons"/>
      <field name="_deck"/>
      <field name="_backButton"/>
      <field name="_nextButton"/>
      <field name="_cancelButton"/>

      <!-- functions to be added as oncommand listeners to the wizard buttons -->
      <field name="_backFunc">(function() { document.documentElement.rewind(); })</field>
      <field name="_nextFunc">(function() { document.documentElement.advance(); })</field>
      <field name="_finishFunc">(function() { document.documentElement.advance(); })</field>
      <field name="_cancelFunc">(function() { document.documentElement.cancel(); })</field>
      <field name="_extra1Func">(function() { document.documentElement.extra1(); })</field>
      <field name="_extra2Func">(function() { document.documentElement.extra2(); })</field>

      <field name="_closeHandler">(function(event) {
        if (document.documentElement.cancel())
          event.preventDefault();
      })</field>
       
      <constructor><![CDATA[
        this._canAdvance = true;
        this._canRewind = false;
        this._hasLoaded = false;
        
        this._pageStack = [];
        
        try {
          // need to create string bundle manually instead of using <xul:stringbundle/>
          // see bug 63370 for details
          this._bundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
                                   .getService(Components.interfaces.nsIStringBundleService)
                                   .createBundle("chrome://global/locale/wizard.properties");
        } catch (e) {
          // This fails in remote XUL, which has to provide titles for all pages
          // see bug 142502
        }
        
        // get anonymous content references
        this._wizardHeader = document.getAnonymousElementByAttribute(this, "anonid", "Header");
        this._wizardButtons = document.getAnonymousElementByAttribute(this, "anonid", "Buttons");
        this._deck = document.getAnonymousElementByAttribute(this, "anonid", "Deck");
        
        this._initWizardButton("back");
        this._initWizardButton("next");
        this._initWizardButton("finish");
        this._initWizardButton("cancel");
        this._initWizardButton("extra1");
        this._initWizardButton("extra2");
        
        this._initPages();
        
        window.addEventListener("close", this._closeHandler, false);
        
        // start off on the first page
        this.pageCount = this.wizardPages.length;
        this.advance();
        
        // give focus to the first focusable element in the dialog
        window.addEventListener("load", this._setInitialFocus, false);
      ]]></constructor>

      <method name="getPageById">
        <parameter name="aPageId"/>
        <body><![CDATA[
          var els = this.getElementsByAttribute("pageid", aPageId);
          return els.item(0);
        ]]></body>
      </method>
      
      <method name="extra1">
        <body><![CDATA[
          if (this.currentPage) 
            this._fireEvent(this.currentPage, "extra1");
        ]]></body>
      </method>

      <method name="extra2">
        <body><![CDATA[
          if (this.currentPage) 
            this._fireEvent(this.currentPage, "extra2");
        ]]></body>
      </method>

      <method name="rewind">
        <body><![CDATA[
          if (!this.canRewind)
            return;

          if (this.currentPage && !this._fireEvent(this.currentPage, "pagehide"))
            return;

          if (this.currentPage && !this._fireEvent(this.currentPage, "pagerewound"))
            return;

          if (!this._fireEvent(this, "wizardback"))
            return;
            
            
          this._pageStack.pop();
          this.currentPage = this._pageStack[this._pageStack.length-1];
          this.setAttribute("pagestep", this._pageStack.length);
        ]]></body>
      </method>

      <method name="advance">
        <parameter name="aPageId"/>
        <body><![CDATA[
          if (!this.canAdvance)
            return; 
            
          if (this.currentPage && !this._fireEvent(this.currentPage, "pagehide"))
            return;
            
          if (this.currentPage && !this._fireEvent(this.currentPage, "pageadvanced"))
            return;

          if (this.onLastPage) {
            if (this._fireEvent(this, "wizardfinish"))
              window.setTimeout(function() {window.close();}, 1);
          } else {
            if (!this._fireEvent(this, "wizardnext"))
              return; 
            
            var page;
            if (aPageId)
              page = this.getPageById(aPageId);
            else {
              if (this.currentPage) {
                if (this._accessMethod == "random")
                  page = this.getPageById(this.currentPage.next);
                else
                  page = this.wizardPages[this.currentPage.pageIndex+1];
              } else
                page = this.wizardPages[0];
            }

            if (page) {
              this._pageStack.push(page);
              this.setAttribute("pagestep", this._pageStack.length);
  
              this.currentPage = page;
            }
          }
        ]]></body>
      </method>
      
      <method name="goTo">
        <parameter name="aPageId"/>
        <body><![CDATA[
          var page = this.getPageById(aPageId);
          if (page) {
            this._pageStack[this._pageStack.length-1] = page;
            this.currentPage = page;
          }
        ]]></body>
      </method>
              
      <method name="cancel">
        <body><![CDATA[
          if (!this._fireEvent(this, "wizardcancel"))
            return true;

          window.close();
          window.setTimeout(function() {window.close();}, 1);
          return false;
        ]]></body>
      </method>
      
      <method name="_setInitialFocus">
        <parameter name="aEvent"/>
        <body>
        <![CDATA[
          document.documentElement._hasLoaded = true;
          var focusInit = 
            function() {
              // give focus to the first focusable element in the dialog
              if (!document.commandDispatcher.focusedElement)
                document.commandDispatcher.advanceFocusIntoSubtree(document.documentElement);

              try {
                var button =
                      document.documentElement._wizardButtons.defaultButton;
                if (button)
                  window.notifyDefaultButtonLoaded(button);
              } catch (e) { }
            };

          // Give focus after onload completes, see bug 103197.
          setTimeout(focusInit, 0);
        ]]>
        </body>
      </method>                

      <method name="_advanceFocusToPage">
        <parameter name="aPage"/>
        <body>
        <![CDATA[
          if (!this._hasLoaded)
            return;
            
          document.commandDispatcher.advanceFocusIntoSubtree(aPage);
          
          // if advanceFocusIntoSubtree tries to focus one of our
          // dialog buttons, then remove it and put it on the root
          var focused = document.commandDispatcher.focusedElement;
          if (focused && focused.hasAttribute("dlgtype"))
            this.focus();
        ]]>
        </body>
      </method>                

      <method name="_initPages">
        <body><![CDATA[
          var meth = "sequential";
          var pages = this.wizardPages;
          for (var i = 0; i < pages.length; ++i) {
            var page = pages[i];
            page.pageIndex = i;
            if (page.next != "")
              meth = "random";
          }
          this._accessMethod = meth;
        ]]></body>
      </method>

      <method name="_initWizardButton">
        <parameter name="aName"/>
        <body><![CDATA[
         var btn = document.getAnonymousElementByAttribute(this._wizardButtons, "dlgtype", aName);
         if (btn) {
           btn.addEventListener("command", this["_"+aName+"Func"], false);
           this["_"+aName+"Button"] = btn;
         }
         return btn;
        ]]></body>
      </method>

      <method name="_adjustWizardHeader">
        <body><![CDATA[
          var label = this.currentPage.getAttribute("label");
          if (!label && this.onFirstPage && this._bundle)
            label = this._bundle.formatStringFromName("default-first-title", [this.title], 1);
          else if (!label && this.onLastPage && this._bundle)
            label = this._bundle.formatStringFromName("default-last-title", [this.title], 1);
          this._wizardHeader.setAttribute("label", label);
          this._wizardHeader.setAttribute("description", this.currentPage.getAttribute("description"));
        ]]></body>
      </method>

      <method name="_hitEnter">
        <parameter name="evt"/>
        <body>
        <![CDATA[
          if (!evt.getPreventDefault())
            this.advance();
        ]]>
        </body>
      </method>

      <method name="_fireEvent">
        <parameter name="aTarget"/>
        <parameter name="aType"/>
        <body>
        <![CDATA[
          var event = document.createEvent("Events");
          event.initEvent(aType, true, true);
          
          // handle dom event handlers
          var noCancel = aTarget.dispatchEvent(event);
          
          // handle any xml attribute event handlers
          var handler = aTarget.getAttribute("on"+aType);
          if (handler != "") {
            var fn = new Function("event", handler);
            var returned = fn.apply(aTarget, [event]);
            if (returned == false)
              noCancel = false;
          }

          return noCancel;
        ]]>
        </body>
      </method>

    </implementation>

    <handlers>
      <handler event="keypress" keycode="VK_ENTER"
               group="system" action="this._hitEnter(event)"/>
      <handler event="keypress" keycode="VK_RETURN"
               group="system" action="this._hitEnter(event)"/>
      <handler event="keypress" keycode="VK_ESCAPE" group="system">
        if (!event.getPreventDefault())
          this.cancel();
      </handler>
    </handlers>
  </binding>

  <binding id="wizardpage" extends="chrome://global/content/bindings/wizard.xml#wizard-base">
    <implementation>
      <field name="pageIndex">-1</field>
      
      <property name="pageid" onget="return this.getAttribute('pageid');"
                              onset="this.setAttribute('pageid', val);"/>

      <property name="next"   onget="return this.getAttribute('next');"
                              onset="this.setAttribute('next', val);
                                     this.parentNode._accessMethod = 'random';
                                     return val;"/>
    </implementation>
  </binding>


  <binding id="wizard-header" extends="chrome://global/content/bindings/wizard.xml#wizard-base">
    <content>
      <xul:hbox class="wizard-header-box-1" flex="1">
        <xul:vbox class="wizard-header-box-text" flex="1">
          <xul:label class="wizard-header-label" xbl:inherits="xbl:text=label"/>
          <xul:label class="wizard-header-description" xbl:inherits="xbl:text=description"/>
        </xul:vbox>
        <xul:image class="wizard-header-icon" xbl:inherits="src=iconsrc"/>
      </xul:hbox>
    </content>
  </binding>
  
  <binding id="wizard-buttons" extends="chrome://global/content/bindings/wizard.xml#wizard-base">
    <content>
      <xul:vbox class="wizard-buttons-box-1" flex="1">
        <xul:separator class="wizard-buttons-separator groove"/>
        <xul:hbox class="wizard-buttons-box-2">
          <xul:button class="wizard-button" dlgtype="extra1" hidden="true"/>
          <xul:button class="wizard-button" dlgtype="extra2" hidden="true"/>
          <xul:spacer flex="1" anonid="spacer"/>
          <xul:button label="&button-back-win.label;" accesskey="&button-back-win.accesskey;"
                      class="wizard-button" dlgtype="back" icon="go-back"/>
          <xul:deck class="wizard-next-deck" anonid="WizardButtonDeck">
            <xul:hbox>
              <xul:button label="&button-finish-win.label;" class="wizard-button"
                          dlgtype="finish" default="true" flex="1"/> 
            </xul:hbox>
            <xul:hbox>
              <xul:button label="&button-next-win.label;" accesskey="&button-next-win.accesskey;"
                          class="wizard-button" dlgtype="next" icon="go-forward" 
                          default="true" flex="1"/>
            </xul:hbox>
          </xul:deck>
          <xul:button label="&button-cancel-win.label;" class="wizard-button"
                      dlgtype="cancel" icon="cancel"/> 
        </xul:hbox>
      </xul:vbox>
    </content>
    
    <implementation>
      <field name="_wizardButtonDeck" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "WizardButtonDeck");
      </field>
      
      <method name="onPageChange">
        <body><![CDATA[
          if (this.getAttribute("lastpage") == "true") {
            this._wizardButtonDeck.setAttribute("selectedIndex", 0);
          } else {
            this._wizardButtonDeck.setAttribute("selectedIndex", 1);
          }
        ]]></body>
      </method>

      <property name="defaultButton" readonly="true">
        <getter><![CDATA[
          const kXULNS =
            "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
          var buttons = this._wizardButtonDeck.selectedPanel
                            .getElementsByTagNameNS(kXULNS, "button");
          for (var i = 0; i < buttons.length; i++) {
            if (buttons[i].getAttribute("default") == "true" &&
                !buttons[i].hidden && !buttons[i].disabled)
              return buttons[i];
          }
          return null;
        ]]></getter>
      </property>
    </implementation>
  </binding>

</bindings>
