<?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"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <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 sizetopopup="pref">
      <xul:hbox class="menulist-label-box" flex="1">
        <xul:image class="menulist-icon" xbl:inherits="src"/>
        <xul:label class="menulist-label" xbl:inherits="value=label,crop,accesskey" crop="right" flex="1"/>
      </xul:hbox>
      <xul:dropmarker class="menulist-dropmarker" type="menu"/>
      <children includes="menupopup"/>
    </content>

    <handlers>
      <handler event="command" phase="capturing">
        <![CDATA[
          // XXX see bug #196755
          // originalTarget is workaround for arrowscrollbox retargeting bug
          if (event.originalTarget.parentNode.parentNode == this)
            this.selectedItem = event.originalTarget;
        ]]>
      </handler>
        
      <handler event="popupshowing">
        <![CDATA[
          // XXX see bug #196755
          // originalTarget is workaround for arrowscrollbox retargeting bug
          if (event.originalTarget.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.
            this.menuBoxObject.activeChild = this.selectedInternal;
        ]]>
      </handler>
    </handlers>

    <implementation implements="nsIDOMXULMenuListElement, nsIAccessibleProvider">
      <constructor>
        this.setInitialSelection()
      </constructor>
    
      <method name="setInitialSelection">
        <body>
          <![CDATA[
            var popup = this.menupopup;
            if (popup) {
              var arr = popup.getElementsByAttribute('selected', 'true');

              if (!arr.length && this.value)
                arr = popup.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 = null;
            var popup = this.menupopup;
            if (popup)
              arr = popup.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.hasAttribute('disabled');"/>

      <property name="open" onset="this.menuBoxObject.openMenu(val);
                                   return val;"
                            onget="return this.hasAttribute('open');"/>

      <property name="menupopup" readonly="true">
        <getter>
          <![CDATA[
            var popup = this.firstChild;
            while (popup && popup.localName != "menupopup")
              popup = popup.nextSibling;
            return popup;
          ]]>
        </getter>
      </property>

      <field name="menuBoxObject" readonly="true">
        this.boxObject.QueryInterface(Components.interfaces.nsIMenuBoxObject)
      </field>

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

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

            var children = this.selectedInternal.parentNode.childNodes;
            var i = children.length;
            while (i--)
              if (children[i] == this.selectedInternal)
                break;

            return i;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            var popup = this.menupopup;
            if (popup && 0 <= val && val < popup.childNodes.length)
              this.selectedItem = popup.childNodes[val];
            else
              this.selectedItem = null;
            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>
      
      <method name="appendItem">
        <parameter name="label"/>
        <parameter name="value"/>
        <body>
        <![CDATA[
          const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
          var popup = this.menupopup ||
                      this.appendChild(document.createElementNS(XULNS, "menupopup"));
          var item = document.createElementNS(XULNS, "menuitem");
          item.setAttribute("label", label);
          item.setAttribute("value", value);
          popup.appendChild(item);
          return item;
        ]]>
        </body>
      </method>
      
      <method name="insertItemAt">
        <parameter name="index"/>
        <parameter name="label"/>
        <parameter name="value"/>
        <body>
        <![CDATA[
          const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
          var popup = this.menupopup ||
                      this.appendChild(document.createElementNS(XULNS, "menupopup"));
          var item = document.createElementNS(XULNS, "menuitem");
          item.setAttribute("label", label);
          item.setAttribute("value", value);
          if (index < popup.childNodes.length)
            popup.insertBefore(item, popup.childNodes[index]);
          else
            popup.appendChild(item);
          return item;
        ]]>
        </body>
      </method>

      <method name="removeItemAt">
        <parameter name="index"/>
        <body>
        <![CDATA[
          var popup = this.menupopup;
          if (popup && 0 <= index && index < popup.childNodes.length) {
            var remove = popup.childNodes[index];
            popup.removeChild(remove);
            return remove;
          }
          return null;
        ]]>
        </body>
      </method>

      <method name="removeAllItems">
        <body>
        <![CDATA[
          this.selectedInternal = null;
          var popup = this.menupopup;
          if (popup)
            this.removeChild(popup);
        ]]>
        </body>
      </method>

      <property name="accessible">
        <getter>
          <![CDATA[
            var accService = Components.classes["@mozilla.org/accessibilityService;1"].getService(Components.interfaces.nsIAccessibilityService);
            return accService.createXULComboboxAccessible(this);
          ]]>
        </getter>
      </property>
    </implementation>  
  </binding>

  <binding id="menulist-editable" extends="chrome://global/content/bindings/menulist.xml#menulist">
    <content sizetopopup="pref">
      <xul:hbox class="menulist-editable-box textbox-input-box" xbl:inherits="context" flex="1">
        <html:input class="menulist-editable-input" flex="1" anonid="input" allowevents="true"
                    xbl:inherits="value=label,disabled"/>
      </xul:hbox>
      <xul:dropmarker class="menulist-dropmarker" type="menu"/>
      <children includes="menupopup"/>
    </content>
    
    <implementation>
      <constructor><![CDATA[
        this.setInitialSelection();
      ]]></constructor>

      <method name="_selectInputFieldValueInList">
        <body>
        <![CDATA[
          if (this.hasAttribute("disableautoselect"))
            return "";

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

          if (popup)
            arr = popup.getElementsByAttribute('label', this.inputField.value);

          if (arr && arr.length)
            this.setSelectionInternal(arr[0]);
          else
            this.setSelectionInternal(null);
        ]]>
        </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;

            if (val)
              val.setAttribute('selected', 'true');

            //Do NOT change the "value", which is owned by inputField
            return val;
          ]]>
        </body>
      </method>

      <field name="mInputField">null</field>
      
      <property name="inputField" readonly="true">
        <getter><![CDATA[
          if (!this.mInputField)    
            this.mInputField = document.getAnonymousElementByAttribute(this, "anonid", "input");
          return this.mInputField;
        ]]></getter>
      </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="disableautoselect" 
                onset="if (val) this.setAttribute('disableautoselect','true');
                       else this.removeAttribute('disableautoselect'); return val;"
                onget="return this.hasAttribute('disableautoselect');"/>

      <method name="select">
        <body>
          this.inputField.select();
        </body>
      </method>
    </implementation>

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

      <handler event="popupshowing">
        <![CDATA[
          // editable menulists elements aren't in the focus order,
          // so when the popup opens we need to force the focus to the inputField
          // XXX see bug #196755
          // originalTarget is workaround for arrowscrollbox retargeting bug
          if (event.originalTarget.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.
              this.menuBoxObject.activeChild = this.selectedInternal;
          }
        ]]>
      </handler>

      <handler event="keypress">
        <![CDATA[
          // open popup if key is up arrow, down arrow, or F4
          if (!event.ctrlKey && !event.shiftKey) {
            if (event.keyCode == KeyEvent.DOM_VK_UP ||
                event.keyCode == KeyEvent.DOM_VK_DOWN ||
                (event.keyCode == KeyEvent.DOM_VK_F4 && !event.altKey)) {
              event.preventDefault();
              this.open = true;
            }
          }
        ]]>
      </handler>
    </handlers>    
  </binding>
  
  <binding id="menulist-compact" display="xul:menu" 
           extends="chrome://global/content/bindings/menulist.xml#menulist">
    <content sizetopopup="false">
      <xul:dropmarker class="menulist-dropmarker" type="menu"/>
      <xul:image class="menulist-icon" xbl:inherits="src"/>
      <xul:label class="menulist-label" xbl:inherits="value=label,crop,accesskey" crop="right" flex="1"/>
      <children includes="menupopup"/>
    </content>
  </binding>
  
</bindings>
