<?xml version="1.0"?>

<bindings id="popupBindings"
   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="popup-base">
    <resources>
      <stylesheet src="chrome://global/skin/popup.css"/>
    </resources>

    <implementation implements="nsIDOMXULPopupElement">
      <property name="label" onget="return this.getAttribute('label');"
                             onset="this.setAttribute('label', val); return val;"/>
      <property name="position" onget="return this.getAttribute('position');"
                                onset="this.setAttribute('position', val); return val;"/>
      <property name="popupBoxObject">
        <getter>
          return this.boxObject.QueryInterface(Components.interfaces.nsIPopupBoxObject);
        </getter>
      </property>

      <property name="state" readonly="true"
                onget="return this.popupBoxObject.popupState"/>

      <property name="triggerNode" readonly="true"
                onget="return this.popupBoxObject.triggerNode"/>

      <property name="anchorNode" readonly="true"
                onget="return this.popupBoxObject.anchorNode"/>

      <method name="openPopup">
        <parameter name="aAnchorElement"/>
        <parameter name="aPosition"/>
        <parameter name="aX"/>
        <parameter name="aY"/>
        <parameter name="aIsContextMenu"/>
        <parameter name="aAttributesOverride"/>
        <parameter name="aTriggerEvent"/>
        <body>
        <![CDATA[
          try {
            var popupBox = this.popupBoxObject;
            if (popupBox)
              popupBox.openPopup(aAnchorElement, aPosition, aX, aY,
                                 aIsContextMenu, aAttributesOverride, aTriggerEvent);
          } catch(e) {}
        ]]>
        </body>
      </method>

      <method name="openPopupAtScreen">
        <parameter name="aX"/>
        <parameter name="aY"/>
        <parameter name="aIsContextMenu"/>
        <parameter name="aTriggerEvent"/>
        <body>
        <![CDATA[
          try {
            var popupBox = this.popupBoxObject;
            if (popupBox)
              popupBox.openPopupAtScreen(aX, aY, aIsContextMenu, aTriggerEvent);
          } catch(e) {}
        ]]>
        </body>
      </method>
      
      <method name="showPopup">
        <parameter name="element"/>
        <parameter name="xpos"/>
        <parameter name="ypos"/>
        <parameter name="popuptype"/>
        <parameter name="anchoralignment"/>
        <parameter name="popupalignment"/>
        <body>
        <![CDATA[
          var popupBox = null;
          var menuBox = null;
          try {
            popupBox = this.popupBoxObject;
          } catch(e) {}
          try {
            menuBox = this.parentNode.boxObject;
          } catch(e) {}
          if (menuBox instanceof Components.interfaces.nsIMenuBoxObject)
            menuBox.openMenu(true);
          else if (popupBox)
            popupBox.showPopup(element, this, xpos, ypos, popuptype, anchoralignment, popupalignment);
        ]]>
        </body>
      </method>
      
      <method name="hidePopup">
        <body>
        <![CDATA[
          var popupBox = null;
          var menuBox = null;
          try {
            popupBox = this.boxObject.QueryInterface(Components.interfaces.nsIPopupBoxObject);
          } catch(e) {}
          try {
            menuBox = this.parentNode.boxObject;
          } catch(e) {}
          if (menuBox instanceof Components.interfaces.nsIMenuBoxObject)
            menuBox.openMenu(false);
          else if (popupBox)
            popupBox.hidePopup();
        ]]>
        </body>
      </method>

      <property name="autoPosition">
        <getter>
        <![CDATA[
          return this.popupBoxObject.autoPosition;
        ]]>
        </getter>
        <setter>
        <![CDATA[
          return this.popupBoxObject.autoPosition = val;
        ]]>
        </setter>
      </property>
      
      <method name="enableKeyboardNavigator">
        <parameter name="aEnableKeyboardNavigator"/>
        <body>
        <![CDATA[
          this.popupBoxObject.enableKeyboardNavigator(aEnableKeyboardNavigator);
        ]]>
        </body>
      </method>
      
      <method name="enableRollup">
        <parameter name="aEnableRollup"/>
        <body>
        <![CDATA[
          this.popupBoxObject.enableRollup(aEnableRollup);
        ]]>
        </body>
      </method>
      
      <method name="sizeTo">
        <parameter name="aWidth"/>
        <parameter name="aHeight"/>
        <body>
        <![CDATA[
          this.popupBoxObject.sizeTo(aWidth, aHeight);
        ]]>
        </body>
      </method>
      
      <method name="moveTo">
        <parameter name="aLeft"/>
        <parameter name="aTop"/>
        <body>
        <![CDATA[
          this.popupBoxObject.moveTo(aLeft, aTop);
        ]]>
        </body>
      </method>
    </implementation>     

  </binding>

  <binding id="popup"
           extends="chrome://global/content/bindings/popup.xml#popup-base">
    
    <content>
      <xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical"
                          smoothscroll="false">
        <children/>
      </xul:arrowscrollbox>
    </content>

    <implementation implements="nsIAccessibleProvider">
      <property name="accessibleType" readonly="true">
        <getter>
          <![CDATA[
              return Components.interfaces.nsIAccessibleProvider.XULMenupopup;
            ]]>
        </getter>
      </property>
    </implementation>

    <handlers>
      <handler event="popupshowing" phase="target">
        <![CDATA[
          var array = [];
          var width = 0;
          for (var menuitem = this.firstChild; menuitem; menuitem = menuitem.nextSibling) {
            if (menuitem.localName == "menuitem" && menuitem.hasAttribute("acceltext")) {
              var accel = document.getAnonymousElementByAttribute(menuitem, "anonid", "accel");
              if (accel && accel.boxObject) {
                array.push(accel);
                if (accel.boxObject.width > width)
                  width = accel.boxObject.width;
              }
            }
          }
          for (var i = 0; i < array.length; i++)
            array[i].width = width;
        ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="panel"
           extends="chrome://global/content/bindings/popup.xml#popup-base">
    <!-- This separate binding for dialog-like panels - not menu, list or autocomplete popups
         exposes the popup as an alert or a pane, depending on whether it is always intended
         to get keyboard navigation when it opens -->
    <implementation implements="nsIDOMXULPopupElement, nsIAccessibleProvider">
      <property name="accessibleType" readonly="true">
        <getter>
          <![CDATA[
          return (this.getAttribute("noautofocus") == "true") ?
                       Components.interfaces.nsIAccessibleProvider.XULAlert :
                       Components.interfaces.nsIAccessibleProvider.XULPane;
        ]]></getter>
      </property>
      <field name="_prevFocus">0</field>
    </implementation>
    
    <handlers>
      <handler event="popupshowing"><![CDATA[
        // Capture the previous focus before has a chance to get set inside the panel
        try {
          this._prevFocus = document.commandDispatcher.focusedElement;
          if (!this._prevFocus)  // Content window has focus
            this._prevFocus = document.commandDispatcher.focusedWindow;
        } catch (ex) {
          this._prevFocus = document.activeElement;
        }
      ]]></handler>
      <handler event="popupshown"><![CDATA[
        // Fire event for accessibility APIs
        var alertEvent = document.createEvent("Events");
        alertEvent.initEvent("AlertActive", true, true);
        this.dispatchEvent(alertEvent);
       ]]></handler>
      <handler event="popuphiding"><![CDATA[
        try {
          this._currentFocus = document.commandDispatcher.focusedElement;
        } catch (e) {
          this._currentFocus = document.activeElement;
        }
      ]]></handler>
      <handler event="popuphidden"><![CDATA[
        var currentFocus = this._currentFocus;
        var prevFocus = this._prevFocus;
        this._currentFocus = null;
        this._prevFocus = null;
        if (prevFocus && currentFocus && this.getAttribute("norestorefocus") != "true") {
          // Try to restore focus
          try {
            if (document.commandDispatcher.focusedWindow != window)
              return; // Focus has already been set to a window outside of this panel
          } catch(ex) {}
          while (currentFocus) {
            if (currentFocus == this) {
              // Focus was set on an element inside this panel,
              // so we need to move it back to where it was previously
              try {
                let fm = Components.classes["@mozilla.org/focus-manager;1"]
                                   .getService(Components.interfaces.nsIFocusManager);
                fm.setFocus(prevFocus, fm.FLAG_NOSCROLL);
              } catch(e) {
                prevFocus.focus();
              }
              return;
            }
            currentFocus = currentFocus.parentNode;
          }
        }
      ]]></handler>
    </handlers>
  </binding>

  <binding id="arrowpanel" extends="chrome://global/content/bindings/popup.xml#panel">
    <content flip="both" side="top" position="bottomcenter topleft">
      <xul:box anonid="container" class="panel-arrowcontainer">
        <xul:box anonid="arrowbox" class="panel-arrowbox">
          <xul:image anonid="arrow" class="panel-arrow"/>
        </xul:box>
        <xul:box class="panel-arrowcontent">
          <xul:box class="panel-inner-arrowcontent" xbl:inherits="align,dir,orient,pack">
            <children/>
          </xul:box>
        </xul:box>
      </xul:box>
    </content>
    <implementation>
      <field name="_fadeTimer"/>
    </implementation>
    <handlers>
      <handler event="popupshowing">
      <![CDATA[
        var container = document.getAnonymousElementByAttribute(this, "anonid", "container");
        var arrowbox = document.getAnonymousElementByAttribute(this, "anonid", "arrowbox");
        var arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow");

        var anchor = this.anchorNode;
        if (!anchor) {
          arrow.hidden = true;
          return;
        }

        // Returns whether the first float is smaller than the second float or
        // equals to it in a range of epsilon.
        function smallerTo(aFloat1, aFloat2, aEpsilon)
        {
          return aFloat1 <= (aFloat2 + aEpsilon);
        }

        let popupRect = this.getBoundingClientRect();
        let popupLeft = window.mozInnerScreenX + popupRect.left;
        let popupTop = window.mozInnerScreenY + popupRect.top;
        let popupRight = popupLeft + popupRect.width;
        let popupBottom = popupTop + popupRect.height;

        let anchorRect = anchor.getBoundingClientRect();
        let anchorLeft = anchor.ownerDocument.defaultView.mozInnerScreenX + anchorRect.left;
        let anchorTop = anchor.ownerDocument.defaultView.mozInnerScreenY + anchorRect.top;
        let anchorRight = anchorLeft + anchorRect.width;
        let anchorBottom = anchorTop + anchorRect.height;

        const epsilon = 0.1;

        var horizPos = smallerTo(popupRight, anchorLeft, epsilon) ? -1 : smallerTo(anchorRight, popupLeft, epsilon) ? 1 : 0;
        var vertPos = smallerTo(popupBottom, anchorTop, epsilon) ? -1 : smallerTo(anchorBottom, popupTop, epsilon) ? 1 : 0;

        var anchorClass = "";
        var hideAnchor = false;
        if (horizPos == 0) {
          container.orient = "vertical";
          arrowbox.orient = "";
          if (vertPos == 0) {
            hideAnchor = true;
          }
          else {
            let pack = "";

            // We have to guess where to position the arrow given that we don't
            // have access to the parameters passed to |openPopup|.

            // If the popup is on the left of the anchor.
            if (smallerTo(popupLeft, anchorLeft, epsilon) && smallerTo(popupRight, anchorRight, epsilon)) {
              pack = (popupLeft + popupRect.width / 2 < anchorLeft) ? "end" : "start";
            // If the popup is on the right of the anchor.
            } else if (smallerTo(anchorLeft, popupLeft, epsilon) && smallerTo(anchorRight, popupRight, epsilon)) {
              pack = (popupLeft + popupRect.width / 2 > anchorRight) ? "start" : "end";
            // If the popup is not on the right nor on the left.
            // Basically, that means one is above the other and one is bigger
            // than the other.
            // In that case, we can't easily choose a position for the arrow so
            // we have to guess depending on which side the popup is more close to.
            } else {
              pack = (Math.abs(popupLeft - anchorLeft) < Math.abs(popupRight - anchorRight)) ? "start" : "end";
            }

            // In RTL, everything should be inverted.
            if (window.getComputedStyle(this).direction == "rtl") {
              pack = (pack == "start") ? "end" : "start";
            }

            arrowbox.pack = pack;

            if (vertPos == 1) {
              container.dir = "";
              anchorClass = "top";
            }
            else if (vertPos == -1) {
              container.dir = "reverse"; 
              anchorClass = "bottom";
            }
          }
        }
        else if (vertPos == 0) {
          container.orient = "";
          arrowbox.orient = "vertical";
          if (horizPos == 0) {
            hideAnchor = true;
          }
          else {
            arrowbox.pack = popupTop + popupRect.height / 2 < anchorTop ? "end" : "start";
            if (horizPos == 1) {
              container.dir = "";
              anchorClass = "left";
            }
            else if (horizPos == -1) {
              container.dir = "reverse";
              anchorClass = "right";
            }
          }
        }
        else {
          hideAnchor = true;
        }
        arrow.hidden = hideAnchor;
        arrow.setAttribute("side", anchorClass);
        this.setAttribute("side", anchorClass);

        // set fading
        var fade = this.getAttribute("fade");
        var fadeDelay = (fade == "fast") ? 1 : fade == "slow" ? 4000 : 0;
        if (fadeDelay) {
          this._fadeTimer = setTimeout(function (self) {
            self.style.opacity = 0.2;
          }, fadeDelay, this);
        }
      ]]>
      </handler>
      <handler event="popuphiding" phase="target">
        clearTimeout(this._fadeTimer);
        this.style.removeProperty("opacity");
      </handler>
      <handler event="transitionend" phase="target">
      <![CDATA[
        if (event.propertyName == "opacity") {
          this.hidePopup();
          this.style.removeProperty("opacity");
        }
      ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="tooltip" extends="chrome://global/content/bindings/popup.xml#popup-base">
    <content>
      <children>
        <xul:label class="tooltip-label" xbl:inherits="xbl:text=label" flex="1"/>
      </children>
    </content>
    
    <implementation implements="nsIAccessibleProvider">
      <property name="accessibleType" readonly="true">
        <getter>
          return Components.interfaces.nsIAccessibleProvider.XULTooltip;
        </getter>
      </property>

      <field name="_mouseOutCount">0</field>
      <field name="_isMouseOver">false</field>
      
      <property name="label"
                onget="return this.getAttribute('label');"
                onset="this.setAttribute('label', val); return val;"/>
    </implementation>

    <handlers>
      <handler event="mouseover"><![CDATA[
        var rel = event.relatedTarget;
        //dump("ENTERING " + (rel ? rel.localName : "null") + "\n");
        if (!rel)
          return;
          
        // find out if the node we entered from is one of our anonymous children
        while (rel) {
          if (rel == this)
            break;
          rel = rel.parentNode;
        }

        // if the exited node is not a descendant of ours, we are entering for the first time
        if (rel != this)
          this._isMouseOver = true;
      ]]></handler>

      <handler event="mouseout"><![CDATA[
        var rel = event.relatedTarget;
        //dump("LEAVING " + (rel ? rel.localName : "null") + "\n");

        // relatedTarget is null when the titletip is first shown: a mouseout event fires
        // because the mouse is exiting the main window and entering the titletip "window".
        // relatedTarget is also null when the mouse exits the main window completely,
        // so count how many times relatedTarget was null after titletip is first shown 
        // and hide popup the 2nd time
        if (!rel) {
          ++this._mouseOutCount;
          if (this._mouseOutCount > 1)
            this.hidePopup();
          return;
        }
          
        // find out if the node we are entering is one of our anonymous children
        while (rel) {
          if (rel == this)
            break;
          rel = rel.parentNode;
        }
        
        // if the entered node is not a descendant of ours, hide the tooltip
        if (rel != this && this._isMouseOver) {
          this.hidePopup();
        }        
      ]]></handler>

      <handler event="popuphiding"><![CDATA[
        this._isMouseOver = false;
        this._mouseOutCount = 0;
      ]]></handler>
    </handlers>
  </binding>

  <binding id="popup-scrollbars" extends="chrome://global/content/bindings/popup.xml#popup">
    <content>
      <xul:hbox class="popup-internal-box" flex="1" orient="vertical" style="overflow: auto;">
        <children/>
      </xul:hbox>
    </content>
  </binding>

</bindings>

