<?xml version="1.0"?>

<bindings id="menulistBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:html="http://www.w3.org/1999/xhtml"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <binding id="menulist-base">
    <resources>
      <stylesheet src="chrome://global/skin/menulist.css"/>
    </resources>
  </binding>

  <binding id="menulist" display="xul:menu" 
           extends="chrome://global/content/bindings/menulist.xml#menulist-base">
    <content>
      <xul:image class="menu-icon" inherits="src"/>
      <xul:text class="menu-text" flex="1" inherits="value=label,accesskey,crop" crop="right"/>
      <xul:image class="menu-dropmarker"/>
      
      <children includes="menupopup"/>
    </content>

    <handlers>
      <handler event="command" phase="capturing"
        action="if (event.originalTarget.localName == 'menuitem') this.selectedItem = event.originalTarget;"/>
        
      <handler event="popupshowing">
        <![CDATA[
          if (event.target.parentNode == this && this.selectedItem) {
            // Not ready for auto-setting the active child in hierarchies yet.
            // For now, only do this when the outermost menupopup opens.
            var menuBox = this.boxObject.QueryInterface(Components.interfaces.nsIMenuBoxObject);
            menuBox.activeChild = this.selectedItem;
          }
        ]]>
      </handler>
    </handlers>

    <implementation>
      <constructor>
        this.setInitialSelection()
      </constructor>
    
      <method name="setInitialSelection">
        <body>
          <![CDATA[
            this.setAttribute('sizetopopup', 'true');
            if (!this.label && this.childNodes.length) {
              var arr = this.firstChild.getElementsByAttribute('selected', 'true');

              if (!arr.length && this.value)
                arr = this.firstChild.getElementsByAttribute('value', this.value);

              if (arr.length)
                this.selectedItem = arr[0];
              else
                this.selectedIndex = 0;
            }
          ]]>
        </body>
      </method>

      <property name="value" onget="return this.getAttribute('value');">
        <setter>
          <![CDATA[
            var arr;

            if (this.childNodes.length)
              arr = this.firstChild.getElementsByAttribute('value', val);

            if (arr && arr.length)
              this.selectedItem = arr[0];
            else
              this.setAttribute('value', val);

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

      <property name="crop" onset="this.setAttribute('crop',val); return val;"
                            onget="return this.getAttribute('crop');"/>
      <property name="src"  onset="this.setAttribute('src',val); return val;"
                            onget="return this.getAttribute('src');"/>
      <property name="label" onset="this.setAttribute('label',val); return val;"
                             onget="return this.getAttribute('label');"/>
      <property name="disabled" onset="if (val) this.setAttribute('disabled',true); 
                                       else this.removeAttribute('disabled');
                                       return val;"
                                onget="return (this.getAttribute('disabled')=='true');"/>

      <property name="selectedInternal">
        null
      </property>

      <property name="selectedIndex">
        <getter>
          <![CDATA[
            // Quick and dirty. We won't deal with hierarchical menulists yet.
            if (!this.selectedItem)
              return -1;

            var children = this.selectedItem.parentNode.childNodes;
            for (var i = 0; i < children.length; i++) {
              if (children[i] == this.selectedItem)
                return i;
            }

            return -1;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            if (val < 0)
              this.selectedItem = null;
            else {
              var curr = this.firstChild;
              while (curr && curr.localName != 'menupopup')
                curr = curr.nextSibling;
              if (curr && val < curr.childNodes.length)
                this.selectedItem = curr.childNodes[val];
            }
            return val;    
          ]]>
        </setter>
      </property>

      <property name="selectedItem">
        <getter>
          <![CDATA[
            return this.selectedInternal;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            if (this.selectedInternal == val)
              return val;
            
            if (this.selectedInternal)
              this.selectedInternal.removeAttribute('selected');

            this.selectedInternal = val;
            if (!this.selectedInternal) {
              this.removeAttribute('value');
              this.removeAttribute('src');
              this.removeAttribute('label');
              return val;
            }
             
            val.setAttribute('selected', 'true');

            this.setAttribute('value', val.getAttribute('value'));
            this.setAttribute('src', val.getAttribute('src'));
            this.setAttribute('label', val.getAttribute('label'));

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

  <binding id="menulist-editable" extends="chrome://global/content/bindings/menulist.xml#menulist">
    <content>
      <xul:hbox flex="1" class="menulist-editable-box" align="center">
        <xul:image class="menu-icon" inherits="src"/>
        <html:input type="text" flex="1" class="menulist-editable-text" allowevents="true"
                    inherits="value=label,disabled"/>
        <xul:image class="menu-dropmarker"/>
      </xul:hbox>
    
      <children includes="menupopup"/>
    </content>
    
    <implementation>
      <constructor><![CDATA[
        this.setInitialSelection();

        // Default behavior is to use _selectInputFieldValueInList()
        //  to automatically select the inputField.value in the list
        if (!this.getAttribute("autoSelectMenuitem"))
          this.setAttribute("autoSelectMenuitem", "true");

      ]]></constructor>

      <method name="_selectInputFieldValueInList">
        <body>
        <![CDATA[
          if (this.getAttribute("autoSelectMenuitem") != "true")
            return "";

          // Find and select the menuitem that matches inputField's "value"
          var inputVal = this.inputField.value;
          var arr;

          if (this.childNodes.length)
            arr = this.firstChild.getElementsByAttribute('label', inputVal);

          if (arr && arr.length)
            this.setSelectionInternal(arr[0]);
          else
            this.setSelectionInternal(null);

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

      <method name="setSelectionInternal">
        <parameter name="val"/>
        <body>
          <![CDATA[
            // This is called internally to set selected item
            //  without triggering infinite loop
            //  when using selectedItem's setter
            if (this.selectedInternal == val)
              return val;
            
            if (this.selectedInternal)
              this.selectedInternal.removeAttribute('selected');

            this.selectedInternal = val;

            //Do NOT change the "value", which is owned by inputField
            if (!this.selectedInternal) {
              this.removeAttribute('src');
              return val;
            }
            val.setAttribute('selected', 'true');
            this.setAttribute('src', val.getAttribute('src'));

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

      <property name="inputField" readonly="true">
       <![CDATA[
        var v = document.getAnonymousNodes(this);
        var input = null;
        for (var i = 0; i < v.length; i++) {
          try {
            var l = v[i].getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "input");
            if (l.length > 0) {
              input = l[0];
              break;
            }
          } catch (e) {} 
        }
        input;
      ]]>
      </property>
      <property name="label"      onset="this.inputField.value = val; return val;"
                                  onget="return this.inputField.value;"/>

      <property name="value"      onget="return this.inputField.value;">
        <setter>
        <![CDATA[
          // Override menulist's value setter to refer to the inputField's value
          // (Allows using "menulist.value" instead of "menulist.inputField.value")
          this.inputField.value = val;
          this.setAttribute('value', val);
          this.setAttribute('label', val);
          this._selectInputFieldValueInList();
          return val;
        ]]>
        </setter>
      </property>

      <property name="selectedItem">
        <getter>
          <![CDATA[
            // Make sure internally-selected item
            //  is in sync with inputField.value
            this._selectInputFieldValueInList();
            return this.selectedInternal;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            // This doesn't touch inputField.value or "value" and "label" attributes
            this.setSelectionInternal(val);

            if (!this.selectedInternal) {
              this.inputField.value = "";
              this.removeAttribute('value');
              this.removeAttribute('label');
              return val;
            }

            // Editable menulist uses "label" as its "value"
            var label = val.getAttribute('label');
            this.inputField.value = label;
            this.setAttribute('value', label);
            this.setAttribute('label', label);

            return val;
          ]]>
        </setter>
      </property>
      <property name="autoSelectMenuitem" 
                onset="this.setAttribute('autoSelectMenuitem',val); return val;"
                onget="return this.getAttribute('autoSelectMenuitem');"/>
    </implementation>

    <handlers>
      <handler event="focus" phase="capturing">
      <![CDATA[
        if (this.getAttribute('focused') != 'true') 
        {
          this.setAttribute('focused','true');
          this.suppressFocusBlur = true;
          if (document.commandDispatcher.focusedElement != this.inputField) 
            this.inputField.focus();
          this.suppressFocusBlur = false;
        }
      ]]>
      </handler>
      
      <handler event="blur" phase="capturing">
      <![CDATA[
        if (!this.suppressFocusBlur && this.getAttribute('focused') == 'true') {
          this.removeAttribute('focused');
        }
      ]]>
      </handler>

      <handler event="popupshowing">
        <![CDATA[
          // BUG in Classic skin: doesn't shift focus when popup is opened
          //   so force it to inputField here
          if (event.target.parentNode == this) {
            if (document.commandDispatcher.focusedElement != this.inputField) 
              this.inputField.focus();

            if (this.selectedItem) {
              // Not ready for auto-setting the active child in hierarchies yet.
              // For now, only do this when the outermost menupopup opens.
              var menuBox = this.boxObject.QueryInterface(Components.interfaces.nsIMenuBoxObject);
              menuBox.activeChild = this.selectedItem;
            }
          }
        ]]>
      </handler>

      <handler event="keypress">
        <![CDATA[
          // open popup if key is up or down
          if (event.keyCode == 38 || event.keyCode == 40)
          {
            var popups = this.getElementsByTagNameNS(
                              "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
                              "menupopup");
            if (popups) {
              if (popups[0].getAttribute("open") != "true") {
                event.preventDefault();

                popups[0].openPopup(this, -1, -1, "popup", "bottomleft", "topleft");

                this._selectInputFieldValueInList();
              }
            }
          }
        ]]>
      </handler>
    </handlers>    
  </binding>
  
</bindings>
