<?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/. -->


<bindings id="dialogBindings"
          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="dialog" extends="chrome://global/content/bindings/general.xml#root-element">
    <resources>
      <stylesheet src="chrome://global/skin/dialog.css"/>
    </resources>
    <content>
      <xul:vbox class="box-inherit dialog-content-box" flex="1">
        <children/>
      </xul:vbox>

      <xul:hbox class="dialog-button-box" anonid="buttons"
                xbl:inherits="pack=buttonpack,align=buttonalign,dir=buttondir,orient=buttonorient"
                >
        <xul:button dlgtype="disclosure" class="dialog-button" hidden="true"/>
        <xul:button dlgtype="help" class="dialog-button" hidden="true"/>
        <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"/>
        <xul:button dlgtype="accept" class="dialog-button" xbl:inherits="disabled=buttondisabledaccept"/>
      </xul:hbox>
    </content>

    <implementation>
      <field name="_mStrBundle">null</field>
      <field name="_closeHandler">(function(event) {
        if (!document.documentElement.cancelDialog())
          event.preventDefault();
      })</field>

      <property name="buttons"
                onget="return this.getAttribute('buttons');"
                onset="this._configureButtons(val); return val;"/>

      <property name="defaultButton">
        <getter>
        <![CDATA[
          if (this.hasAttribute("defaultButton"))
            return this.getAttribute("defaultButton");
          else // default to the accept button
            return "accept";
        ]]>
        </getter>
        <setter>
        <![CDATA[
          this._setDefaultButton(val);
          return val;
        ]]>
        </setter>
      </property>

      <method name="acceptDialog">
        <body>
        <![CDATA[
          return this._doButtonCommand("accept");
        ]]>
        </body>
      </method>

      <method name="cancelDialog">
        <body>
        <![CDATA[
          return this._doButtonCommand("cancel");
        ]]>
        </body>
      </method>

      <method name="getButton">
        <parameter name="aDlgType"/>
        <body>
        <![CDATA[
          return this._buttons[aDlgType];
        ]]>
        </body>
      </method>

      <method name="moveToAlertPosition">
        <body>
        <![CDATA[
          // hack. we need this so the window has something like its final size
          if (window.outerWidth == 1) {
            dump("Trying to position a sizeless window; caller should have called sizeToContent() or sizeTo(). See bug 75649.\n");
            sizeToContent();
          }

          if (opener) {
            var xOffset = (opener.outerWidth - window.outerWidth) / 2;
            var yOffset = opener.outerHeight / 5;

            var newX = opener.screenX + xOffset;
            var newY = opener.screenY + yOffset;
          } else {
            newX = (screen.availWidth - window.outerWidth) / 2;
            newY = (screen.availHeight - window.outerHeight) / 2;
          }

          // ensure the window is fully onscreen (if smaller than the screen)
          if (newX < screen.availLeft)
            newX = screen.availLeft + 20;
          if ((newX + window.outerWidth) > (screen.availLeft + screen.availWidth))
            newX = (screen.availLeft + screen.availWidth) - window.outerWidth - 20;

          if (newY < screen.availTop)
            newY = screen.availTop + 20;
          if ((newY + window.outerHeight) > (screen.availTop + screen.availHeight))
            newY = (screen.availTop + screen.availHeight) - window.outerHeight - 60;

          window.moveTo( newX, newY );
        ]]>
        </body>
      </method>

      <method name="centerWindowOnScreen">
        <body>
        <![CDATA[
          var xOffset = screen.availWidth/2 - window.outerWidth/2;
          var yOffset = screen.availHeight/2 - window.outerHeight/2; //(opener.outerHeight *2)/10;

          xOffset = xOffset > 0 ? xOffset : 0;
          yOffset = yOffset > 0 ? yOffset : 0;
          window.moveTo(xOffset, yOffset);
        ]]>
        </body>
      </method>

      <constructor>
      <![CDATA[
        this._configureButtons(this.buttons);

        // listen for when window is closed via native close buttons
        window.addEventListener("close", this._closeHandler, false);

        // for things that we need to initialize after onload fires
        window.addEventListener("load", this.postLoadInit, false);

        window.moveToAlertPosition = this.moveToAlertPosition;
        window.centerWindowOnScreen = this.centerWindowOnScreen;
      ]]>
      </constructor>

      <method name="postLoadInit">
        <parameter name="aEvent"/>
        <body>
        <![CDATA[
          let { AppConstants } =
              Components.utils.import("resource://gre/modules/AppConstants.jsm", {});

          function focusInit() {
            const dialog = document.documentElement;
            const defaultButton = dialog.getButton(dialog.defaultButton);
            // give focus to the first focusable element in the dialog
            if (!document.commandDispatcher.focusedElement) {
              document.commandDispatcher.advanceFocusIntoSubtree(dialog);

              var focusedElt = document.commandDispatcher.focusedElement;
              if (focusedElt) {
                var initialFocusedElt = focusedElt;
                while (focusedElt.localName == "tab" ||
                       focusedElt.getAttribute("noinitialfocus") == "true") {
                  document.commandDispatcher.advanceFocusIntoSubtree(focusedElt);
                  focusedElt = document.commandDispatcher.focusedElement;
                  if (focusedElt == initialFocusedElt)
                    break;
                }

                if (initialFocusedElt.localName == "tab") {
                  if (focusedElt.hasAttribute("dlgtype")) {
                    // We don't want to focus on anonymous OK, Cancel, etc. buttons,
                    // so return focus to the tab itself
                    initialFocusedElt.focus();
                  }
                }
                else if (AppConstants.platform != "macosx" &&
                         focusedElt.hasAttribute("dlgtype") && focusedElt != defaultButton) {
                  defaultButton.focus();
                }
              }
            }

            try {
              if (defaultButton)
                window.notifyDefaultButtonLoaded(defaultButton);
            } catch (e) { }
          }

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

      <property name="mStrBundle">
        <getter>
        <![CDATA[
          if (!this._mStrBundle) {
            // need to create string bundle manually instead of using <xul:stringbundle/>
            // see bug 63370 for details
            this._mStrBundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
                                         .getService(Components.interfaces.nsIStringBundleService)
                                         .createBundle("chrome://global/locale/dialog.properties");
          }
          return this._mStrBundle;
        ]]></getter>
      </property>

      <method name="_configureButtons">
        <parameter name="aButtons"/>
        <body>
        <![CDATA[
          // by default, get all the anonymous button elements
          var buttons = {};
          this._buttons = buttons;
          buttons.accept = document.getAnonymousElementByAttribute(this, "dlgtype", "accept");
          buttons.cancel = document.getAnonymousElementByAttribute(this, "dlgtype", "cancel");
          buttons.extra1 = document.getAnonymousElementByAttribute(this, "dlgtype", "extra1");
          buttons.extra2 = document.getAnonymousElementByAttribute(this, "dlgtype", "extra2");
          buttons.help = document.getAnonymousElementByAttribute(this, "dlgtype", "help");
          buttons.disclosure = document.getAnonymousElementByAttribute(this, "dlgtype", "disclosure");

          // look for any overriding explicit button elements
          var exBtns = this.getElementsByAttribute("dlgtype", "*");
          var dlgtype;
          var i;
          for (i = 0; i < exBtns.length; ++i) {
            dlgtype = exBtns[i].getAttribute("dlgtype");
            buttons[dlgtype].hidden = true; // hide the anonymous button
            buttons[dlgtype] = exBtns[i];
          }

          // add the label and oncommand handler to each button
          for (dlgtype in buttons) {
            var button = buttons[dlgtype];
            button.addEventListener("command", this._handleButtonCommand, true);

            // don't override custom labels with pre-defined labels on explicit buttons
            if (!button.hasAttribute("label")) {
              // dialog attributes override the default labels in dialog.properties
              if (this.hasAttribute("buttonlabel"+dlgtype)) {
                button.setAttribute("label", this.getAttribute("buttonlabel"+dlgtype));
                if (this.hasAttribute("buttonaccesskey"+dlgtype))
                  button.setAttribute("accesskey", this.getAttribute("buttonaccesskey"+dlgtype));
              } else if (dlgtype != "extra1" && dlgtype != "extra2") {
                button.setAttribute("label", this.mStrBundle.GetStringFromName("button-"+dlgtype));
                var accessKey = this.mStrBundle.GetStringFromName("accesskey-"+dlgtype);
                if (accessKey)
                  button.setAttribute("accesskey", accessKey);
              }
            }
            // allow specifying alternate icons in the dialog header
            if (!button.hasAttribute("icon")) {
              // if there's an icon specified, use that
              if (this.hasAttribute("buttonicon"+dlgtype))
                button.setAttribute("icon", this.getAttribute("buttonicon"+dlgtype));
              // otherwise set defaults
              else
                switch (dlgtype) {
                  case "accept":
                    button.setAttribute("icon","accept");
                    break;
                  case "cancel":
                    button.setAttribute("icon","cancel");
                    break;
                  case "disclosure":
                    button.setAttribute("icon","properties");
                    break;
                  case "help":
                    button.setAttribute("icon","help");
                    break;
                  default:
                    break;
                }
            }
          }

          // ensure that hitting enter triggers the default button command
          this.defaultButton = this.defaultButton;

          // if there is a special button configuration, use it
          if (aButtons) {
            // expect a comma delimited list of dlgtype values
            var list = aButtons.split(",");

            // mark shown dlgtypes as true
            var shown = { accept: false, cancel: false, help: false,
                          disclosure: false, extra1: false, extra2: false };
            for (i = 0; i < list.length; ++i)
              shown[list[i].replace(/ /g, "")] = true;

            // hide/show the buttons we want
            for (dlgtype in buttons)
              buttons[dlgtype].hidden = !shown[dlgtype];

            let { AppConstants } =
                Components.utils.import("resource://gre/modules/AppConstants.jsm", {});
            // show the spacer on Windows only when the extra2 button is present
            if (AppConstants.platform == "win") {
              var spacer = document.getAnonymousElementByAttribute(this, "anonid", "spacer");
              spacer.removeAttribute("hidden");
              spacer.setAttribute("flex", shown["extra2"]?"1":"0");
            }
          }
        ]]>
        </body>
      </method>

      <method name="_setDefaultButton">
        <parameter name="aNewDefault"/>
        <body>
        <![CDATA[
          // remove the default attribute from the previous default button, if any
          var oldDefaultButton = this.getButton(this.defaultButton);
          if (oldDefaultButton)
            oldDefaultButton.removeAttribute("default");

          var newDefaultButton = this.getButton(aNewDefault);
          if (newDefaultButton) {
            this.setAttribute("defaultButton", aNewDefault);
            newDefaultButton.setAttribute("default", "true");
          }
          else {
            this.setAttribute("defaultButton", "none");
            if (aNewDefault != "none")
              dump("invalid new default button: " +  aNewDefault + ", assuming: none\n");
          }
        ]]>
        </body>
      </method>

      <method name="_handleButtonCommand">
        <parameter name="aEvent"/>
        <body>
        <![CDATA[
          return document.documentElement._doButtonCommand(
                                        aEvent.target.getAttribute("dlgtype"));
        ]]>
        </body>
      </method>

      <method name="_doButtonCommand">
        <parameter name="aDlgType"/>
        <body>
        <![CDATA[
          var button = this.getButton(aDlgType);
          if (!button.disabled) {
            var noCancel = this._fireButtonEvent(aDlgType);
            if (noCancel) {
              if (aDlgType == "accept" || aDlgType == "cancel") {
                var closingEvent = new CustomEvent("dialogclosing", {
                  bubbles: true,
                  detail: { button: aDlgType },
                });
                this.dispatchEvent(closingEvent);
                window.close();
              }
            }
            return noCancel;
          }
          return true;
        ]]>
        </body>
      </method>

      <method name="_fireButtonEvent">
        <parameter name="aDlgType"/>
        <body>
        <![CDATA[
          var event = document.createEvent("Events");
          event.initEvent("dialog"+aDlgType, true, true);

          // handle dom event handlers
          var noCancel = this.dispatchEvent(event);

          // handle any xml attribute event handlers
          var handler = this.getAttribute("ondialog"+aDlgType);
          if (handler != "") {
            var fn = new Function("event", handler);
            var returned = fn(event);
            if (returned == false)
              noCancel = false;
          }

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

      <method name="_hitEnter">
        <parameter name="evt"/>
        <body>
        <![CDATA[
          if (evt.defaultPrevented)
            return;

          var btn = this.getButton(this.defaultButton);
          if (btn)
            this._doButtonCommand(this.defaultButton);
        ]]>
        </body>
      </method>

    </implementation>

    <handlers>
      <handler event="keypress" keycode="VK_RETURN"
               group="system" action="this._hitEnter(event);"/>
      <handler event="keypress" keycode="VK_ESCAPE" group="system">
        if (!event.defaultPrevented)
          this.cancelDialog();
      </handler>
      <handler event="focus" phase="capturing">
        var btn = this.getButton(this.defaultButton);
        if (btn)
          btn.setAttribute("default", event.originalTarget == btn || !(event.originalTarget instanceof Components.interfaces.nsIDOMXULButtonElement));
      </handler>
    </handlers>

  </binding>

  <binding id="dialogheader">
    <resources>
      <stylesheet src="chrome://global/skin/dialog.css"/>
    </resources>
    <content>
      <xul:label class="dialogheader-title" xbl:inherits="value=title,crop" crop="right" flex="1"/>
      <xul:label class="dialogheader-description" xbl:inherits="value=description"/>
    </content>
  </binding>

</bindings>
