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

<!DOCTYPE bindings [
<!ENTITY % messengerDTD SYSTEM "chrome://messenger/locale/messenger.dtd">
%messengerDTD;
]>

<bindings id="SearchBindings"
   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">

  <!--
    - The glodaSearch binding implements a gloda-backed search mechanism.  The
    -  actual search logic comes from the glodaFacet tab mode in the
    -  glodaFacetTabType definition.  This binding serves as a means to display
    -  and alter the current search query if a "glodaFacet" tab is displayed,
    -  or enter a search query and spawn a new "glodaFacet" tab if one is
    -  currently not displayed.
    -
    - This widget used to have many weird implementation nuances.  Now we are
    -  just a little bit of extra stuff on top of the toolkit autocomplete
    -  implementation.  Our deviations are:
    -  - We collapse ourselves when gloda is disabled; we track the state.
    -  -
    -->
  <binding id="glodaSearch"
           extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">

    <handlers>
      <handler event="drop" phase="capturing"><![CDATA[
        nsDragAndDrop.drop(event, this.searchInputDNDObserver);
      ]]></handler>

      <handler event="keypress" keycode="VK_RETURN"><![CDATA[
        this.doSearch();
        event.preventDefault();
        event.stopPropagation();
      ]]></handler>
      <handler event="keypress" keycode="VK_ESCAPE"><![CDATA[
        this.clearSearch();
        event.preventDefault();
        event.stopPropagation();
      ]]></handler>
    </handlers>

    <implementation implements="nsIObserver">
      <constructor><![CDATA[
        const Cc = Components.classes;
        const Ci = Components.interfaces;
        const Cu = Components.utils;
        Cu.import("resource:///modules/errUtils.js");
        Cu.import("resource://gre/modules/Services.jsm");
        Cu.import("resource://gre/modules/AppConstants.jsm");

        try {
          this.setAttribute(
            "placeholder",
            this.getAttribute("emptytextbase")
                .replace("#1", this.getAttribute(
                                 (AppConstants.platform == "macosx") ?
                                  "keyLabelMac" : "keyLabelNonMac")));

          Services.prefs.addObserver("mailnews.database.global.indexer.enabled",
                                     this._prefObserver, false);

          this.glodaCompleter =
            Components.classes["@mozilla.org/autocomplete/search;1?name=gloda"]
                      .getService()
                      .wrappedJSObject;
          Services.obs.addObserver(this, "autocomplete-did-enter-text", false);

          this.glodaEnabled =
            Services.prefs.getBoolPref("mailnews.database.global.indexer.enabled");
          this.collapsed = !this.glodaEnabled;

          // make sure we set our emptytext here from the get-go
        if (this.hasAttribute("placeholder"))
          this.placeholder = this.getAttribute("placeholder");
        } catch (e) {
          logException(e, true);
        }
      ]]></constructor>

      <destructor>
        <![CDATA[
          Components.utils.import("resource://gre/modules/Services.jsm");

          Services.prefs.removeObserver("mailnews.database.global.indexer.enabled",
                                        this._prefObserver);
          Services.obs.removeObserver(this, "autocomplete-did-enter-text");
        ]]>
      </destructor>

      <field name="_prefObserver">({
        inputSearch: this,
        observe: function(subject, topic, data)
        {
          Components.utils.import("resource://gre/modules/Services.jsm");

          if (topic == "nsPref:changed") {
            subject.QueryInterface(Components.interfaces.nsIPrefBranch);
            switch (data) {
            case "mailnews.database.global.indexer.enabled":
              this.inputSearch.glodaEnabled =
                Services.prefs.getBoolPref(
                  "mailnews.database.global.indexer.enabled");
              this.inputSearch.collapsed = !this.inputSearch.glodaEnabled;
              break;
            }
          }
        },

        QueryInterface: function(aIID)
        {
          if (aIID.equals(Components.interfaces.nsIObserver) ||
              aIID.equals(Components.interfaces.nsISupports))
            return this;
          throw Components.results.NS_NOINTERFACE;
        }
        });
      </field>
      <field name="glodaCompleter">null</field>
      <property name="menupopup" readonly="true">
        <getter><![CDATA[
          return document.getAnonymousElementByAttribute(
                   this, 'anonid', 'quick-search-menupopup');
        ]]></getter>
      </property>

      <property name="state">
        <getter><![CDATA[
          return {
            'string': this.value
          };
        ]]></getter>
        <setter><![CDATA[
          this.value = val['string'];
        ]]></setter>
      </property>

      // DND Observer
      <field name="searchInputDNDObserver" readonly="true"><![CDATA[
      ({
        inputSearch: this,

        onDrop: function (aEvent, aXferData, aDragSession) {
          try {
            if (aXferData.data) {
              this.inputSearch.focus();
              this.inputSearch.value = aXferData.data;
              // XXX for some reason the input field is _cleared_ even though
              // the search works.
              this.inputSearch.doSearch();
            }
          } catch (e) {
            logException(e);
          }
        },

        getSupportedFlavours: function () {
          var flavourSet = new FlavourSet();
          flavourSet.appendFlavour("text/unicode");
          return flavourSet;
        }
      })
      ]]></field>

      <method name="observe">
        <parameter name="aSubject"/>
        <parameter name="aTopic"/>
        <parameter name="aData"/>
        <body><![CDATA[
        try {
          if (aTopic == "autocomplete-did-enter-text" && aSubject == this) {
            let selectedIndex = this.popup.selectedIndex;
            let curResult = this.glodaCompleter.curResult;
            if (! curResult)
              return; // autocomplete didn't even finish.
            let row = curResult.getObjectAt(selectedIndex);
            if (row == null)
              return;
            if (row.fullText) {
              // The autocomplete-did-enter-text notification is synchronously
              // generated by nsAutoCompleteController which will attempt to
              // call ClosePopup after we return and then tell the searchbox
              // about the text entered.  Since doSearch may close the current
              // tab (and thus destroy the XUL document that owns the popup and
              // the input field), the search box may no longer have its XBL
              // binding attached when we return and telling it about the
              // entered text could fail.
              // To avoid this, we defer the doSearch call to the next turn of
              // the event loop by using setTimeout.
              setTimeout(this.doSearch.bind(this), 0);
            } else if (row.nounDef) {
              let theQuery = Gloda.newQuery(Gloda.NOUN_MESSAGE);
              if (row.nounDef.name == "tag") {
                theQuery = theQuery.tags(row.item);
              } else if (row.nounDef.name == "identity") {
                theQuery = theQuery.involves(row.item);
              }
              theQuery.orderBy('-date');
              document.getElementById("tabmail").openTab("glodaFacet", {
                query: theQuery
              });
            }
          }
        } catch (e) {
          logException(e);
        }
        ]]></body>
      </method>

      <method name="doSearch">
        <body><![CDATA[
          Components.utils.import("resource://gre/modules/Services.jsm");

          try {
            if (this.value) {
              let tabmail = document.getElementById("tabmail");
              // If the current tab is a gloda search tab, reset the value
              //  to the initial search value.  Otherwise, clear it.  This
              //  is the value that 3is going to be saved with the current
              //  tab when we switch back to it next.
              let searchString = this.value;

              if (tabmail.currentTabInfo.mode.name == "glodaFacet") {
                // we'd rather reuse the existing tab (and somehow do something
                // smart with any preexisting facet choices, but that's a
                // bit hard right now, so doing the cheap thing and closing
                // this tab and starting over
                tabmail.closeTab();
              }
              this.value = ''; // clear our value, to avoid persistence
              let args = {
                searcher: new GlodaMsgSearcher(null, searchString)
              };
              tabmail.openTab("glodaFacet", args);
            }
          } catch (e) {
            logException(e);
          }
        ]]>
        </body>
      </method>
      <method name="clearSearch">
        <body><![CDATA[
          this.value = "";
        ]]></body>
      </method>
    </implementation>
  </binding>

  <binding id="searchBarDropMarker">
    <resources>
      <stylesheet src="chrome://messenger/skin/searchBox.css"/>
    </resources>
    <content popup="_child">
      <children/>
      <xul:stack flex="1">
        <xul:hbox align="center">
          <xul:image class="quick-search-button-image" xbl:inherits="src"/>
        </xul:hbox>
        <xul:hbox align="center">
          <xul:image class="quick-search-button-dropmarker"/>
        </xul:hbox>
      </xul:stack>
    </content>
  </binding>

</bindings>
