home *** CD-ROM | disk | FTP | other *** search
/ PC World 2007 March / PCWorld_2007-03_cd.bin / komunikace / nvu / nvu-1.0-cs-CZ.win32.installer.exe / chrome / toolkit.jar / content / global / autocomplete.xml < prev    next >
Extensible Markup Language  |  2004-10-28  |  58KB  |  1,596 lines

  1. <?xml version="1.0"?>
  2.  
  3. <bindings id="autocompleteBindings"
  4.           xmlns="http://www.mozilla.org/xbl"
  5.           xmlns:html="http://www.w3.org/1999/xhtml"
  6.           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  7.           xmlns:xbl="http://www.mozilla.org/xbl">
  8.  
  9.   <binding id="autocomplete"
  10.            extends="chrome://global/content/bindings/textbox.xml#textbox">
  11.     <resources>
  12.       <stylesheet src="chrome://global/content/autocomplete.css"/>
  13.       <stylesheet src="chrome://global/skin/autocomplete.css"/>
  14.     </resources>
  15.  
  16.     <content sizetopopup="pref">
  17.       <xul:hbox class="autocomplete-textbox-container" flex="1">
  18.         <children includes="image|deck">
  19.           <xul:image class="autocomplete-icon" allowevents="true"/>
  20.         </children>
  21.  
  22.         <xul:hbox class="textbox-input-box" flex="1" xbl:inherits="context,tooltiptext=inputtooltiptext">
  23.           <html:input anonid="input" class="autocomplete-textbox textbox-input"
  24.                       flex="1" allowevents="true"
  25.                       xbl:inherits="tooltiptext=inputtooltiptext,onfocus,onblur,value,type,maxlength,disabled,size,readonly,userAction"/>
  26.         </xul:hbox>
  27.       </xul:hbox>
  28.                           
  29.       <xul:dropmarker class="autocomplete-history-dropmarker" allowevents="true"
  30.                       xbl:inherits="open,hidden=disablehistory" anonid="historydropmarker"/>
  31.  
  32.       <xul:popupset>
  33.         <xul:popup ignorekeys="true" anonid="popup" class="autocomplete-result-popup" hidden="true" xbl:inherits="for=id,nomatch"/>
  34.       </xul:popupset>
  35.       
  36.       <children includes="menupopup"/>
  37.     </content>
  38.  
  39.     <implementation implements="nsIDOMXULMenuListElement, nsIAccessibleProvider">
  40.  
  41.       <constructor><![CDATA[
  42.         // XXX bug 90337 band-aid until we figure out what's going on here
  43.         if (this.value != this.mInputElt.value)
  44.           this.mInputElt.value = this.value;
  45.         delete this.value;
  46.  
  47.         // set default property values
  48.         this.ifSetAttribute("timeout", 50);
  49.         this.ifSetAttribute("maxrows", 5);
  50.         this.ifSetAttribute("showpopup", true);
  51.         this.ifSetAttribute("disablehistory", true);
  52.         this.ifSetAttribute("disableKeyNavigation", true);
  53.         
  54.         // initialize the search sessions
  55.         this.searchSessions = this.getAttribute("searchSessions");
  56.         
  57.         // hack to work around lack of bottom-up constructor calling
  58.         if ("initialize" in this.resultsPopup)
  59.           this.resultsPopup.initialize();
  60.       ]]></constructor>
  61.  
  62.       <destructor><![CDATA[
  63.         this.clearResults(false);
  64.       ]]></destructor>
  65.       
  66.       <!-- =================== PUBLIC PROPERTIES =================== -->
  67.  
  68.       <property name="value"
  69.                 onset="this.ignoreInputEvent = true;
  70.                        this.mInputElt.value = val;
  71.                        this.ignoreInputEvent = false;
  72.                        return val;"
  73.                 onget="return this.mInputElt ? this.mInputElt.value : null;"/>
  74.   
  75.       <property name="focused"
  76.                 onget="return this.getAttribute('focused') == 'true';"/>
  77.  
  78.       <!-- space-delimited string of search session types to use -->
  79.       <property name="searchSessions" onget="return this.getAttribute('searchSessions')">
  80.         <setter><![CDATA[
  81.           val = val ? val : "";
  82.           var list = val.split(" ");
  83.           this.mSessions = {};
  84.           this.mListeners = {};
  85.           this.mLastResults = {};
  86.           this.mLastStatus = {};
  87.  
  88.           for (var i in list) {
  89.             var name = list[i];
  90.             if (name != "") {
  91.               var contractid = "@mozilla.org/autocompleteSession;1?type=" + name;
  92.               try {
  93.                 var session =
  94.                   Components.classes[contractid].getService(Components.interfaces.nsIAutoCompleteSession);
  95.               } catch (e) {
  96.                 dump("### ERROR - unable to create search session \"" + session + "\".\n");
  97.                 break;
  98.               }
  99.               this.mSessions[name] = session;
  100.               this.mListeners[name] = new (this.mAutoCompleteListener)(name);
  101.               this.mLastResults[name] = null;
  102.               this.mLastStatus[name] = null;
  103.               ++this.sessionCount;
  104.             }
  105.           }
  106.         ]]></setter>
  107.       </property>
  108.  
  109.       <!-- the number of sessions currently in use -->
  110.       <field name="sessionCount">0</field>
  111.  
  112.       <!-- number of milliseconds after a keystroke before a search begins -->
  113.       <property name="timeout"
  114.                 onset="this.setAttribute('timeout', val); return val;"
  115.                 onget="var t = parseInt(this.getAttribute('timeout')); return t ? t : 0;"/>
  116.  
  117.       <!-- number of milliseconds after a keystroke before a search begins -->
  118.       <property name="maxRows"
  119.                 onset="this.setAttribute('maxrows', val); return val;"
  120.                 onget="var t = parseInt(this.getAttribute('maxrows')); return t ? t : 0;"/>
  121.  
  122.       <!-- option for filling the textbox with the best match while typing 
  123.            and selecting the difference -->
  124.       <property name="autoFill"
  125.                 onset="this.setAttribute('autoFill', val); return val;"
  126.                 onget="return this.getAttribute('autoFill') == 'true';"/>
  127.  
  128.       <!-- if the resulting match string is not at the beginning of the typed string,
  129.            this will optionally autofill like this "bar |>> foobar|" -->
  130.       <property name="autoFillAfterMatch"
  131.                 onset="this.setAttribute('autoFillAfterMatch', val); return val;"
  132.                 onget="return this.getAttribute('autoFillAfterMatch') == 'true';"/>
  133.                 
  134.       <!-- toggles a second column in the results list which contains
  135.            the string in the comment field of each autocomplete result -->
  136.       <property name="showCommentColumn"
  137.                 onget=
  138.           "return this.getAttribute('showCommentColumn') == 'true';">
  139.         <setter><![CDATA[
  140.  
  141.           var currentState = this.getAttribute('showCommentColumn');          
  142.  
  143.           // if comment column has been switched from off to on
  144.           //
  145.           if (val && (currentState == 'false')) { 
  146.  
  147.             // reset the flex on the value column and add the comment column
  148.             //
  149.             document.getElementById("value").setAttribute("flex", 2);
  150.             this.resultsPopup.addColumn({id: "comment", flex: 1});
  151.  
  152.           // if comment column has been switched from on to off
  153.           //
  154.           } else if (!val && (currentState == 'true')) {
  155.  
  156.             // reset the flex on the value column and add the comment column
  157.             //
  158.             document.getElementById("value").setAttribute("flex", 1);
  159.             this.resultsPopup.removeColumn('comment');
  160.           }
  161.  
  162.           // save and return the current state
  163.           //
  164.           this.setAttribute('showCommentColumn', val); return val;
  165.         ]]></setter>
  166.       </property>
  167.  
  168.                 
  169.       <!-- option for completing to the default result whenever the user hits
  170.            enter or the textbox loses focus -->
  171.       <property name="forceComplete"
  172.                 onset="this.setAttribute('forceComplete', val); return val;"
  173.                 onget="return this.getAttribute('forceComplete') == 'true';"/>
  174.  
  175.       <!-- option to show the popup containing the results -->
  176.       <property name="showPopup"
  177.                 onset="this.setAttribute('showpopup', val); return val;"
  178.                 onget="return this.getAttribute('showpopup') == 'true';"/>
  179.  
  180.       <!-- option to keep the popup open while typing, even when there are no matches -->
  181.       <property name="alwaysOpenPopup"
  182.                 onset="this.setAttribute('alwaysopenpopup', val); return val;"
  183.                 onget="return this.getAttribute('alwaysopenpopup') == 'true';"/>
  184.  
  185.       <!-- option to allow scrolling through the list via the tab key, rather than
  186.            tab moving focus out of the textbox -->
  187.       <property name="tabScrolling"
  188.                 onset="return this.setAttribute('tabScrolling', val); return val;"
  189.                 onget="return this.getAttribute('tabScrolling') == 'true';"/>
  190.  
  191.       <!-- option to turn off autocomplete -->
  192.       <property name="disableAutocomplete"
  193.                 onset="this.setAttribute('disableAutocomplete', val); return val;"
  194.                 onget="return this.getAttribute('disableAutocomplete') == 'true';"/>
  195.  
  196.       <!-- option to completely ignore any blur events while  
  197.            searches are still going on.  This is useful so that nothing
  198.            gets autopicked if the window is required to lose focus for
  199.            some reason (eg in LDAP autocomplete, another window may be
  200.            brought up so that the user can enter a password to authenticate
  201.            to an LDAP server).  -->
  202.       <property name="ignoreBlurWhileSearching"
  203.                 onset="this.setAttribute('ignoreBlurWhileSearching', val); return val;"
  204.                 onget="return this.getAttribute('ignoreBlurWhileSearching') == 'true';"/>
  205.  
  206.       <property name="minResultsForPopup"
  207.                 onset="this.setAttribute('minResultsForPopup', val); return val;"
  208.                 onget="var t = parseInt(this.getAttribute('minResultsForPopup')); return t ? t : 0;"/>
  209.       
  210.       <!-- state which indicates the current action being performed by the user.
  211.            Possible values are : none, typing, scrolling -->
  212.       <property name="userAction"
  213.                 onset="this.setAttribute('userAction', val); return val;"
  214.                 onget="return this.getAttribute('userAction');"/>
  215.       
  216.       <!-- state which indicates if the last search had no matches -->
  217.       <field name="noMatch">true</field>
  218.  
  219.       <!-- state which indicates a search is currently happening -->
  220.       <field name="isSearching">false</field>
  221.  
  222.       <!-- state which indicates a search timeout is current waiting -->
  223.       <property name="isWaiting" 
  224.                 onget="return this.mAutoCompleteTimer != 0;"/>
  225.  
  226.       <!-- reference to the results popup element -->
  227.       <field name="resultsPopup"><![CDATA[
  228.         var elt = document.getAnonymousElementByAttribute(this, "anonid", "popup");
  229.         elt.__AUTOCOMPLETE_BOX__ = this;
  230.         elt;
  231.       ]]></field>
  232.  
  233.       <!-- nsIAccessibleProvider --> 
  234.       <property name="accessible">
  235.         <getter>
  236.           <![CDATA[
  237.             var accService = Components.classes["@mozilla.org/accessibilityService;1"].getService(Components.interfaces.nsIAccessibilityService);
  238.             return accService.createXULComboboxAccessible(this);
  239.           ]]>
  240.         </getter>
  241.       </property>
  242.  
  243.       <!-- nsIDOMXULMenuListElement properties -->
  244.  
  245.       <property name="editable" readonly="true" onget="return true;" />  
  246.  
  247.       <property name="crop" onset="this.setAttribute('crop',val); return val;"
  248.                             onget="return this.getAttribute('crop');"/>
  249.  
  250.       <property name="label" onset="this.setAttribute('label',val); return val;"
  251.                              onget="return this.getAttribute('label');"/>
  252.  
  253.       <property name="open">
  254.         <getter>
  255.           <![CDATA[
  256.             return this.getAttribute('open') == 'true';
  257.           ]]>
  258.         </getter>
  259.         <setter>
  260.           <![CDATA[
  261.             var historyPopup = document.getAnonymousElementByAttribute(this, "anonid", "historydropmarker");
  262.             if (val) {
  263.               this.setAttribute('open',true); 
  264.               historyPopup.showPopup();
  265.             }
  266.             else {
  267.               this.removeAttribute('open');
  268.               historyPopup.hidePopup();
  269.             }
  270.           ]]>
  271.         </setter>
  272.       </property>
  273.  
  274.       <!-- =================== PRIVATE PROPERTIES =================== -->
  275.  
  276.       <field name="mSessions">null</field>
  277.       <field name="mListeners">null</field>
  278.       <field name="mLastResults">null</field>
  279.       <field name="mLastStatus">null</field>
  280.       <field name="mLastKeyCode">null</field>
  281.       <field name="mAutoCompleteTimer">0</field>
  282.       <field name="mMenuOpen">false</field>
  283.       <field name="mFireAfterSearch">false</field>
  284.       <field name="mFinishAfterSearch">false</field>
  285.       <field name="mNeedToFinish">false</field>
  286.       <field name="mNeedToComplete">false</field>
  287.       <field name="mTransientValue">false</field>
  288.       <field name="mView">null</field>
  289.       <field name="currentSearchString">null</field>
  290.       <field name="ignoreInputEvent">false</field>
  291.       <field name="oninit">null</field>
  292.       <field name="ontextcommand">null</field>
  293.       <field name="ontextrevert">null</field>
  294.       <field name="onerrorcommand">null</field>
  295.       <field name="mDefaultMatchFilled">false</field>
  296.  
  297.       <field name="mAutoCompleteListener"><![CDATA[
  298.         var listener = function(aSession) { this.sessionName = aSession };
  299.         listener.prototype = {
  300.           param: this,
  301.           sessionName: null,
  302.           onAutoComplete: function(aResults, aStatus) 
  303.           {
  304.             this.param.processResults(this.sessionName, aResults, aStatus);
  305.           }
  306.         };
  307.         listener;
  308.       ]]></field>
  309.  
  310.       <field name="mInputElt"><![CDATA[
  311.         document.getAnonymousElementByAttribute(this, "anonid", "input");
  312.       ]]></field>
  313.  
  314.       <!-- =================== PUBLIC METHODS =================== -->
  315.  
  316.       <!-- get the result object from the autocomplete results from a specific session -->
  317.       <method name="getResultAt">
  318.         <parameter name="aIndex"/>
  319.         <body><![CDATA[
  320.           var obj = this.convertIndexToSession(aIndex);
  321.           if (obj && this.mLastResults[obj.session]) {
  322.             const nsIAutoCompleteItem = Components.interfaces.nsIAutoCompleteItem;
  323.             if (obj.index >= 0) {
  324.               var item = this.mLastResults[obj.session].items.QueryElementAt(obj.index, nsIAutoCompleteItem);
  325.               return item;
  326.             }
  327.           }
  328.           return null;
  329.         ]]></body>
  330.       </method>
  331.  
  332.       <!-- get the autocomplete session status returned by the session
  333.            that a given item came from -->
  334.       <method name="getSessionStatusAt">
  335.         <parameter name="aIndex"/>
  336.         <body><![CDATA[
  337.           var obj = this.convertIndexToSession(aIndex);
  338.           return obj ? this.mLastStatus[obj.session] : null;
  339.         ]]></body>
  340.       </method>
  341.  
  342.  
  343.       <!-- get a value from the autocomplete results as a string via an absolute index-->
  344.       <method name="getResultValueAt">
  345.         <parameter name="aIndex"/>
  346.         <body><![CDATA[
  347.           var obj = this.convertIndexToSession(aIndex);
  348.           return obj ? this.getSessionValueAt(obj.session, obj.index) : null;
  349.         ]]></body>
  350.       </method>
  351.  
  352.       <!-- get the result object from the autocomplete results from a specific session -->
  353.       <method name="getSessionResultAt">
  354.         <parameter name="aSession"/>
  355.         <parameter name="aIndex"/>
  356.         <body><![CDATA[
  357.           var session = this.mLastResults[aSession];
  358.           if (session) {
  359.             var item = session.items.QueryElementAt(aIndex, Components.interfaces.nsIAutoCompleteItem);
  360.             return item;
  361.           }
  362.           return null;
  363.         ]]></body>
  364.       </method>
  365.  
  366.       <!-- get a value from the autocomplete results as a string from a specific session -->
  367.       <method name="getSessionValueAt">
  368.         <parameter name="aSession"/>
  369.         <parameter name="aIndex"/>
  370.         <body><![CDATA[
  371.           var result = this.getSessionResultAt(aSession, aIndex);
  372.           if (result)
  373.             return result.value;
  374.           return null;
  375.         ]]></body>
  376.       </method>
  377.  
  378.       <!-- get the total number of results in a specific session or overall if session is null-->
  379.       <method name="getResultCount">
  380.         <parameter name="aSession"/>
  381.         <body><![CDATA[
  382.           return this.view.rowCount;
  383.         ]]></body>
  384.       </method>
  385.       
  386.       <!-- get a session object by index -->
  387.       <method name="getSession">
  388.         <parameter name="aIndex"/>
  389.         <body><![CDATA[
  390.           var idx = 0;
  391.           for (var name in this.mSessions) {
  392.             if (idx == aIndex)
  393.               return this.mSessions[name];
  394.             ++idx;
  395.           }
  396.           return null;
  397.         ]]></body>
  398.       </method>
  399.  
  400.       <!-- get a session object by name -->
  401.       <method name="getSessionByName">
  402.         <parameter name="aSessionName"/>
  403.         <body><![CDATA[
  404.           return this.mSessions[aSessionName];
  405.         ]]></body>
  406.       </method>
  407.  
  408.       <!-- add a session by reference -->
  409.       <method name="addSession">
  410.         <parameter name="aSession"/>
  411.         <body><![CDATA[
  412.           ++this.sessionCount;
  413.           var name = "anon_"+this.sessionCount;
  414.           this.mSessions[name] = aSession;
  415.           this.mListeners[name] = new (this.mAutoCompleteListener)(name);
  416.           this.mLastResults[name] = null;
  417.           this.mLastStatus[name] = null;
  418.         ]]></body>
  419.       </method>
  420.  
  421.       <!-- remove a session by reference -->
  422.       <method name="removeSession">
  423.         <parameter name="aSession"/>
  424.         <body><![CDATA[
  425.           for (var name in this.mSessions) {
  426.             if (this.mSessions[name] == aSession) {
  427.               delete this.mSessions[name];
  428.               delete this.mListeners[name];
  429.               delete this.mLastResults[name];
  430.               delete this.mLastStatus[name];
  431.               --this.sessionCount;
  432.               break;
  433.             }
  434.           }
  435.         ]]></body>
  436.       </method>
  437.  
  438.       <!-- make this widget listen to all of the same autocomplete sessions 
  439.            from another autocomplete widget -->
  440.       <method name="syncSessions">
  441.         <parameter name="aCopyFrom"/>
  442.         <body><![CDATA[
  443.           this.sessionCount = aCopyFrom.sessionCount;
  444.           this.mSessions = {};
  445.           this.mListeners = {};
  446.           this.mLastResults = {};
  447.           this.mLastStatus = {};
  448.           for (var name in aCopyFrom.mSessions) {
  449.             this.mSessions[name] = aCopyFrom.mSessions[name];
  450.             this.mListeners[name] = new (this.mAutoCompleteListener)(name);
  451.             this.mLastResults[name] = null;
  452.             this.mLastStatus[name] = null;
  453.           }
  454.         ]]></body>
  455.       </method>
  456.  
  457.       <!-- get the first session that has results -->
  458.       <method name="getDefaultSession">
  459.         <body><![CDATA[
  460.           for (var name in this.mLastResults) {
  461.             var results = this.mLastResults[name];
  462.             if (results && results.items.Count() > 0)
  463.               return name;
  464.           }
  465.           return null;
  466.         ]]></body>
  467.       </method>
  468.  
  469.       <!-- empty the cached result data and empty the results popup -->
  470.       <method name="clearResults">
  471.         <parameter name="aInvalidate"/>
  472.         <body><![CDATA[
  473.           this.clearResultData();
  474.           this.clearResultElements(aInvalidate);
  475.         ]]></body>
  476.       </method>
  477.  
  478.       <!-- =================== PRIVATE METHODS =================== -->
  479.   
  480.       <!-- ::::::::::::: session searching ::::::::::::: -->
  481.  
  482.       <!--  -->
  483.       <method name="callListener">
  484.         <parameter name="me"/>
  485.         <parameter name="aAction"/>
  486.         <body><![CDATA[
  487.           // bail if the binding was detached or the element removed from
  488.           // document during the timeout
  489.           if (!("startLookup" in me) || !me.ownerDocument || !me.parentNode)
  490.             return;
  491.  
  492.           me.clearTimer();
  493.             
  494.           if (me.disableAutocomplete)
  495.             return;
  496.  
  497.           switch (aAction) {
  498.             case "startLookup":
  499.               me.startLookup();
  500.               break;
  501.  
  502.             case "stopLookup":
  503.               me.stopLookup();
  504.               break;
  505.  
  506.             case "autoComplete":
  507.               me.autoComplete();
  508.               break;
  509.           }
  510.         ]]></body>
  511.       </method>
  512.  
  513.       <!--  -->
  514.       <method name="startLookup">
  515.         <body><![CDATA[
  516.           var str = this.value;
  517.           
  518.           this.isSearching = true;
  519.           this.mSessionReturns = this.sessionCount;
  520.           this.mFailureCount = 0;
  521.           this.mFailureItems = 0;
  522.           this.mDefaultMatchFilled = false; // clear out our prefill state.
  523.  
  524.           // tell each session to start searching...
  525.           for (var name in this.mSessions)
  526.             try {
  527.               this.mSessions[name].onStartLookup(str, this.mLastResults[name], this.mListeners[name]);
  528.             } catch (e) {
  529.               --this.mSessionReturns;
  530.               this.searchFailed();
  531.             }
  532.         ]]></body>
  533.       </method>
  534.  
  535.       <!--  -->
  536.       <method name="stopLookup">
  537.         <body><![CDATA[
  538.           for (var name in this.mSessions)
  539.             this.mSessions[name].onStopLookup();
  540.         ]]></body>
  541.       </method>
  542.  
  543.       <!--  -->
  544.       <method name="autoComplete">
  545.         <body><![CDATA[
  546.           for (var name in this.mSessions)
  547.             this.mSessions[name].onAutoComplete(this.value, 
  548.                                                 this.mLastResults[name],
  549.                                                 this.mListeners[name]);
  550.         ]]></body>
  551.       </method>
  552.  
  553.       <!--  -->
  554.       <method name="processResults">
  555.         <parameter name="aSessionName"/>
  556.         <parameter name="aResults"/>
  557.         <parameter name="aStatus"/>
  558.         <body><![CDATA[
  559.           if (this.disableAutocomplete)
  560.             return;
  561.  
  562.           --this.mSessionReturns;
  563.  
  564.           var firstReturn = this.mSessionReturns == (this.sessionCount-1) - this.mFailureCount;
  565.  
  566.           this.mLastStatus[aSessionName] = aStatus;
  567.  
  568.           // check the many criteria for failure
  569.           if (aStatus == Components.interfaces.nsIAutoCompleteStatus.failed ||
  570.               aStatus == Components.interfaces.nsIAutoCompleteStatus.ignored || 
  571.               aStatus == Components.interfaces.nsIAutoCompleteStatus.noMatch ||
  572.               aResults == null ||
  573.               aResults.items.Count() == 0 ||
  574.               aResults.searchString != this.currentSearchString)
  575.           {
  576.             this.mLastResults[aSessionName] = null;
  577.             if (firstReturn)
  578.               this.clearResults(false);
  579.             this.searchFailed();
  580.             return;
  581.           } else if (aStatus == 
  582.                      Components.interfaces.nsIAutoCompleteStatus.failureItems){
  583.             ++this.mFailureItems;
  584.           }
  585.           
  586.           if (firstReturn) {
  587.             if (this.view.mTree)
  588.               this.view.mTree.beginUpdateBatch();
  589.             this.clearResults(false); // clear results, but don't repaint yet
  590.           }
  591.           this.mLastResults[aSessionName] = aResults;
  592.  
  593.           this.autoFillInput(aSessionName, aResults, false);
  594.  
  595.           // always call openResultPopup...we may not have opened it
  596.           // if a previous search session didn't return enough search results.
  597.           // it's smart and doesn't try to open itself multiple times...
  598.           // be sure to add our result elements before calling openResultPopuup as we need
  599.           // to know the total # of results found so far.
  600.           this.addResultElements(aSessionName, aResults);      
  601.           if (firstReturn && this.view.mTree)
  602.             this.view.mTree.endUpdateBatch();
  603.           this.openResultPopup();
  604.          
  605.           // if this is the last session to return... 
  606.           if (this.mSessionReturns == 0) 
  607.             this.postSearchCleanup();
  608.                
  609.           if (this.mFinishAfterSearch)
  610.             this.finishAutoComplete(false, this.mFireAfterSearch, null);
  611.         ]]></body>
  612.       </method>
  613.  
  614.       <!-- called each time a search fails, except when failure items need
  615.            to be displayed. If all searches have failed, clear the list
  616.            and close the popup -->
  617.       <method name="searchFailed">
  618.         <body><![CDATA[
  619.           // if it's the last session to return, time to clean up...
  620.           if (this.mSessionReturns == 0)
  621.             this.postSearchCleanup();
  622.  
  623.           ++this.mFailureCount;
  624.           
  625.           // if all searches are done and they all failed...
  626.           if (this.mSessionReturns == 0 && this.mFailureCount == this.sessionCount) {
  627.             if (this.alwaysOpenPopup) {
  628.               this.clearResults(true); // clear data and repaint empty
  629.               
  630.               if (this.value) {
  631.                 this.openResultPopup();
  632.               } else
  633.                 this.closeResultPopup(); 
  634.             } else
  635.               this.closeResultPopup(); 
  636.           }
  637.         ]]></body>
  638.       </method>
  639.  
  640.       <!-- does some stuff after a search is done (success or failure) -->
  641.       <method name="postSearchCleanup">
  642.         <body><![CDATA[
  643.           this.isSearching = false;
  644.  
  645.           // figure out if there are no matches in all search sessions
  646.           var failed = true;
  647.           for (var name in this.mSessions) {
  648.             if (this.mLastResults[name])
  649.               failed = this.mLastResults[name].items.Count() < 1;
  650.             if (!failed)
  651.               break;
  652.           }
  653.           this.noMatch = failed;
  654.           if (this.noMatch)
  655.             this.setAttribute("nomatch", 1);
  656.           else
  657.             this.removeAttribute("nomatch");
  658.           
  659.           // if we have processed all of our searches, and none of them gave us a default index,
  660.           // then we should try to auto fill the input field with the first match. 
  661.           // note: autoFillInput is smart enough to kick out if we've already prefilled something...
  662.           if (!this.noMatch) {
  663.              var defaultSession = this.getDefaultSession();
  664.              if (defaultSession)
  665.                 this.autoFillInput(defaultSession, this.mLastResults[defaultSession], true);         
  666.           }
  667.         ]]></body>
  668.       </method>
  669.  
  670.       <!-- when the focus exits the widget or user hits return, 
  671.            determine what value to leave in the textbox -->
  672.       <method name="finishAutoComplete">
  673.         <parameter name="aForceComplete"/>
  674.         <parameter name="aFireTextCommand"/>
  675.         <parameter name="aTriggeringEvent"/>
  676.         <body><![CDATA[
  677.           this.mFinishAfterSearch = false;
  678.           this.mFireAfterSearch = false;
  679.           if (this.mNeedToFinish && !this.disableAutocomplete) {
  680.             // set textbox value to either override value, or default search result 
  681.             var val = this.resultsPopup.getOverrideValue();
  682.             if (val) {
  683.               this.value = val;
  684.               this.mNeedToFinish = false;
  685.             } else if (this.mTransientValue || !this.forceComplete ||
  686.                        !(this.mNeedToComplete || aForceComplete)) {
  687.               this.mNeedToFinish = false;
  688.             } else {
  689.               // we want to use the default item index for the first session which gave us a valid
  690.               // default item index...
  691.               for (var name in this.mLastResults) {
  692.                 var results = this.mLastResults[name];
  693.                if (results && results.items.Count() > 0 && results.defaultItemIndex != -1)
  694.                {
  695.                   this.value = this.getSessionValueAt(name, results.defaultItemIndex);
  696.                   this.mNeedToFinish = false;
  697.                  break;
  698.                }
  699.               }
  700.  
  701.               if (this.mNeedToFinish) {
  702.                 // if a search is happening at this juncture, bail out of this function
  703.                 // and let the search finish, and tell it to come back here when it's done
  704.                 if (this.isSearching || this.isWaiting) {
  705.                   this.mFinishAfterSearch = true;
  706.                   this.mFireAfterSearch = aFireTextCommand;
  707.                   return;
  708.                 }
  709.  
  710.                 this.mNeedToFinish = false;
  711.                 var defaultSession = this.getDefaultSession();
  712.                 if (defaultSession)
  713.                     this.value = this.getSessionValueAt(defaultSession, 0); // preselect the first one...
  714.               }
  715.             }
  716.  
  717.             this.stopLookup();
  718.  
  719.             if (!this.noMatch)
  720.               this.autoComplete();
  721.  
  722.             this.closeResultPopup();
  723.           }
  724.           
  725.           this.mNeedToComplete = false;
  726.           this.clearTimer();
  727.  
  728.           if (aFireTextCommand)
  729.             this._fireEvent("textcommand", this.userAction, aTriggeringEvent);
  730.         ]]></body>
  731.       </method>
  732.  
  733.       <!--  when the user clicks an entry in the autocomplete popup -->
  734.       <method name="onResultClick">
  735.         <body><![CDATA[
  736.          // set textbox value to either override value, or the clicked result
  737.           var errItem=null;
  738.           var val = this.resultsPopup.getOverrideValue();
  739.             if (val)
  740.               this.value = val;
  741.           else if (this.resultsPopup.selectedIndex != null &&
  742.                    !this.noMatch) {
  743.             if (this.getSessionStatusAt(this.resultsPopup.selectedIndex) ==
  744.                 Components.interfaces.nsIAutoCompleteStatus.failureItems) {
  745.               this.value = this.currentSearchString;
  746.               this.mTransientValue = true;
  747.               errItem = this.getResultAt(this.resultsPopup.selectedIndex);
  748.             } else { 
  749.               this.value = this.getResultValueAt(
  750.                                              this.resultsPopup.selectedIndex);
  751.             }
  752.           }
  753.  
  754.           if (!this.noMatch)
  755.             this.autoComplete();
  756.  
  757.           this.mNeedToFinish = false;
  758.           this.mNeedToComplete = false;
  759.           
  760.           this.closeResultPopup();
  761.  
  762.           this.currentSearchString = "";
  763.  
  764.           if (errItem)
  765.             this._fireEvent("errorcommand", errItem);
  766.           this._fireEvent("textcommand", "clicking");
  767.         ]]></body>
  768.       </method>
  769.  
  770.       <!-- when the user hits escape, revert the previously typed value in the textbox -->
  771.       <method name="undoAutoComplete">
  772.         <body><![CDATA[
  773.           var val = this.currentSearchString;
  774.  
  775.           var ok = this._fireEvent("textrevert");
  776.           if ((ok || ok == undefined) && val)
  777.             this.value = val;
  778.  
  779.           this.userAction = "typing";
  780.  
  781.           this.currentSearchString = this.value;
  782.           this.mNeedToComplete = false;
  783.         ]]></body>
  784.       </method>
  785.  
  786.       <!-- convert an absolute result index into a session name/index pair -->
  787.       <method name="convertIndexToSession">
  788.         <parameter name="aIndex"/>
  789.         <body><![CDATA[
  790.           var idx = 0;
  791.           for (var name in this.mLastResults) {
  792.             if (this.mLastResults[name]) {
  793.               if ((idx+this.mLastResults[name].items.Count())-1 >= aIndex) {
  794.                 return {session: name, index: aIndex-idx};
  795.               }
  796.               idx += this.mLastResults[name].items.Count();
  797.             }
  798.           }
  799.           return null;
  800.         ]]></body>
  801.       </method>
  802.  
  803.       <!-- ::::::::::::: user input handling ::::::::::::: -->
  804.  
  805.       <!--  -->
  806.       <method name="processInput">
  807.         <body><![CDATA[
  808.           // stop current lookup in case it's async.
  809.           this.stopLookup();
  810.           // stop the queued up lookup on a timer
  811.           this.clearTimer();
  812.  
  813.           if (this.ignoreInputEvent)
  814.             return;
  815.           
  816.           if (this.disableAutocomplete)
  817.             return;
  818.  
  819.           this.userAction = "typing";
  820.           this.mNeedToFinish = true;
  821.           this.mTransientValue = false;
  822.           this.mNeedToComplete = true;
  823.           var str = this.value;
  824.           this.currentSearchString = str;
  825.           this.resultsPopup.selectedIndex = null;
  826.           
  827.           // We want to autocomplete only if the user is editing at the end of the text
  828.           if (this.mInputElt.selectionEnd >= str.length)
  829.             this.mAutoCompleteTimer = setTimeout(this.callListener, this.timeout, this, "startLookup");
  830.           else
  831.             this.noMatch = true;
  832.         ]]></body>
  833.       </method>
  834.  
  835.       <!--  -->
  836.       <method name="processKeyPress">
  837.         <parameter name="aEvent"/>
  838.         <body><![CDATA[
  839.           this.mLastKeyCode = aEvent.keyCode;
  840.           
  841.           var killEvent = false;
  842.           
  843.           switch (aEvent.keyCode) {
  844.             case KeyEvent.DOM_VK_TAB:
  845.               if (this.tabScrolling) {
  846.                 // don't kill this event if alt-tab or ctrl-tab is hit
  847.                 if (!aEvent.altKey && !aEvent.ctrlKey) {
  848.                   killEvent = this.mMenuOpen;
  849.                   if (killEvent)
  850.                     this.keyNavigation(aEvent);
  851.                 }
  852.               } 
  853.               break;              
  854.               
  855.             case KeyEvent.DOM_VK_RETURN:
  856.  
  857.               // if this is a failure item, save it for fireErrorCommand
  858.               var errItem = null;
  859.               if (this.resultsPopup.selectedIndex != null && 
  860.                   this.getSessionStatusAt(this.resultsPopup.selectedIndex) ==
  861.                   Components.interfaces.nsIAutoCompleteStatus.failureItems) {
  862.                 errItem = this.getResultAt(this.resultsPopup.selectedIndex);
  863.               }
  864.  
  865.               killEvent = this.mMenuOpen;
  866.               this.finishAutoComplete(true, true, aEvent);
  867.               this.closeResultPopup();
  868.               if (errItem) {
  869.                   this._fireEvent("errorcommand", errItem);
  870.               }
  871.               break;
  872.  
  873.             case KeyEvent.DOM_VK_ESCAPE:
  874.               this.clearTimer();
  875.               killEvent = this.mMenuOpen;
  876.               this.undoAutoComplete();
  877.               this.closeResultPopup();
  878.               break;
  879.   
  880.             case KeyEvent.DOM_VK_LEFT:
  881.             case KeyEvent.DOM_VK_RIGHT:
  882.               this.finishAutoComplete(false, false, aEvent);
  883.               this.clearTimer();
  884.               this.closeResultPopup();
  885.               break;
  886.  
  887.             case KeyEvent.DOM_VK_DOWN:
  888.               if (!aEvent.altKey) {
  889.                 this.clearTimer();
  890.                 killEvent = this.keyNavigation(aEvent);
  891.                 break;
  892.               }
  893.             // Alt+Down falls through to history popup toggling code
  894.               
  895.             case KeyEvent.DOM_VK_F4:
  896.               if (!aEvent.ctrlKey && !aEvent.shiftKey && this.getAttribute("disablehistory") != "true") {
  897.                 var historyPopup = document.getAnonymousElementByAttribute(this, "anonid", "historydropmarker");
  898.                 if (historyPopup)
  899.                   historyPopup.showPopup();
  900.                 else
  901.                   historyPopup.hidePopup();
  902.               }
  903.               break;
  904.             case KeyEvent.DOM_VK_PAGE_UP:
  905.             case KeyEvent.DOM_VK_PAGE_DOWN:
  906.             case KeyEvent.DOM_VK_UP:
  907.               if (!aEvent.ctrlKey && !aEvent.metaKey) {
  908.                 this.clearTimer();
  909.                 killEvent = this.keyNavigation(aEvent);
  910.               }
  911.               break;
  912.           }
  913.           
  914.           if (killEvent) {
  915.             aEvent.preventDefault();
  916.             aEvent.preventBubble();
  917.           }
  918.           
  919.           return true;
  920.         ]]></body>
  921.       </method>
  922.  
  923.       <!--  -->
  924.       <method name="keyNavigation">
  925.         <parameter name="aEvent"/>
  926.         <body><![CDATA[
  927.           var k = aEvent.keyCode;
  928.           if (k == KeyEvent.DOM_VK_TAB ||
  929.               k == KeyEvent.DOM_VK_UP || k == KeyEvent.DOM_VK_DOWN ||
  930.               k == KeyEvent.DOM_VK_PAGE_UP || k == KeyEvent.DOM_VK_PAGE_DOWN)
  931.           {
  932.             // up/down keys while menu is closed will open menu
  933.             if (!this.mMenuOpen) {
  934.               if (!this.view.rowCount && !this.alwaysOpenPopup)
  935.                 return false;
  936.  
  937.               this.mNeedToFinish = true;
  938.               this.openResultPopup();
  939.               return true;
  940.             }
  941.             
  942.             this.userAction = "scrolling";
  943.             this.mNeedToComplete = false;
  944.             
  945.             var dir = k == KeyEvent.DOM_VK_DOWN ||
  946.                       k == KeyEvent.DOM_VK_PAGE_DOWN ||
  947.                      (k == KeyEvent.DOM_VK_TAB && !aEvent.shiftKey) ? 1 : -1;
  948.             var amt = k == KeyEvent.DOM_VK_PAGE_UP ||
  949.                       k == KeyEvent.DOM_VK_PAGE_DOWN ? this.resultsPopup.pageCount-1 : 1;
  950.             var selected = this.resultsPopup.selectBy(dir, amt);
  951.           
  952.             // determine which value to place in the textbox
  953.             this.ignoreInputEvent = true;
  954.             if (selected != null) {
  955.               if (this.getSessionStatusAt(selected) == 
  956.                   Components.interfaces.nsIAutoCompleteStatus.failureItems) { 
  957.                 if (this.currentSearchString)
  958.                   this.value = this.currentSearchString;
  959.               } else {
  960.                 this.value = this.getResultValueAt(selected);
  961.               }
  962.               this.mTransientValue = true;
  963.             } else {
  964.               if (this.currentSearchString)
  965.                 this.value = this.currentSearchString;
  966.               this.mTransientValue = false;
  967.             }
  968.             
  969.             // move cursor to the end
  970.             this.mInputElt.setSelectionRange(this.value.length, this.value.length);
  971.             this.ignoreInputEvent = false;
  972.           }
  973.           return true;
  974.         ]]></body>
  975.       </method>
  976.  
  977.       <!-- while the user is typing, fill the textbox with the "default" value
  978.            if one can be assumed, and select the end of the text -->
  979.       <method name="autoFillInput">
  980.         <parameter name="aSessionName"/>
  981.         <parameter name="aResults"/>
  982.         <parameter name="aUseFirstMatchIfNoDefault"/>
  983.         <body><![CDATA[
  984.           if (this.mDefaultMatchFilled) return;
  985.             
  986.           if (!this.mFinishAfterSearch && this.autoFill && 
  987.               this.mLastKeyCode != KeyEvent.DOM_VK_BACK_SPACE &&
  988.               this.mLastKeyCode != KeyEvent.DOM_VK_DELETE) {
  989.             var indexToUse = aResults.defaultItemIndex;
  990.             if (aUseFirstMatchIfNoDefault && indexToUse == -1)
  991.               indexToUse = 0;      
  992.               
  993.             if (indexToUse != -1) {
  994.               var resultValue = this.getSessionValueAt(aSessionName, indexToUse);
  995.               var match = resultValue.toLowerCase();
  996.               var entry = this.currentSearchString.toLowerCase();
  997.               this.ignoreInputEvent = true;
  998.               if (match.indexOf(entry) == 0) {
  999.                 var endPoint = this.value.length;
  1000.                 this.value = this.value + resultValue.substr(endPoint);
  1001.                 this.mInputElt.setSelectionRange(endPoint, this.value.length);
  1002.               } else {
  1003.                 if (this.autoFillAfterMatch) {
  1004.                   this.value = this.value + " >> " + resultValue;
  1005.                   this.mInputElt.setSelectionRange(entry.length, this.value.length);
  1006.                 } else {
  1007.                   var postIndex = resultValue.indexOf(this.value);
  1008.                   if (postIndex >= 0) {
  1009.                     var startPt = this.value.length;
  1010.                     this.value = this.value + resultValue.substr(startPt+postIndex);
  1011.                     this.mInputElt.setSelectionRange(startPt, this.value.length);
  1012.                   }
  1013.                 }
  1014.               }
  1015.               this.mNeedToComplete = true;
  1016.               this.ignoreInputEvent = false;
  1017.               this.mDefaultMatchFilled = true;
  1018.             }
  1019.           } 
  1020.         ]]></body>
  1021.       </method>
  1022.  
  1023.       <!-- ::::::::::::: popup and tree ::::::::::::: -->
  1024.  
  1025.       <!--  -->
  1026.       <method name="openResultPopup">
  1027.         <body><![CDATA[
  1028.           if (!this.mMenuOpen && this.showPopup && this.focused &&
  1029.                (this.getResultCount("") >= this.minResultsForPopup
  1030.                 || this.mFailureItems)) {
  1031.             var w = this.boxObject.width;
  1032.             if (w != this.resultsPopup.boxObject.width)
  1033.               this.resultsPopup.setAttribute("width", w);
  1034.             this.resultsPopup.removeAttribute("hidden");
  1035.             this.resultsPopup.showPopup(this, -1, -1, "popup", "bottomleft", "topleft");
  1036.             this.mMenuOpen = true;
  1037.           }
  1038.         ]]></body>
  1039.       </method>
  1040.  
  1041.       <!--  -->
  1042.       <method name="closeResultPopup">
  1043.         <body><![CDATA[
  1044.           if (this.resultsPopup && this.mMenuOpen) {
  1045.             this.resultsPopup.hidePopup();
  1046.             this.resultsPopup.setAttribute("hidden", "true");
  1047.             this.mMenuOpen = false;
  1048.           }
  1049.         ]]></body>
  1050.       </method>
  1051.  
  1052.       <!--  -->
  1053.       <method name="addResultElements">
  1054.         <parameter name="aSessionName"/>
  1055.         <parameter name="aResults"/>
  1056.         <body><![CDATA[
  1057.           if (this.focused) {
  1058.             this.view.addResults(aResults);
  1059.             this.resultsPopup.adjustHeight();
  1060.           }
  1061.         ]]></body>
  1062.       </method>
  1063.  
  1064.       <!--  -->
  1065.       <method name="clearResultElements">
  1066.         <parameter name="aInvalidate"/>
  1067.         <body><![CDATA[
  1068.           this.view.clearResults();
  1069.           if (aInvalidate)
  1070.             this.resultsPopup.adjustHeight();
  1071.  
  1072.           this.noMatch = true;
  1073.         ]]></body>
  1074.       </method>
  1075.  
  1076.       <!--  -->
  1077.       <method name="clearResultData">
  1078.         <body><![CDATA[
  1079.           const nsIAutoCompleteItem =
  1080.             Components.interfaces.nsIAutoCompleteItem;
  1081.           for (var name in this.mSessions) {
  1082.             // clearing out mLastResults[name] does not guarantee that
  1083.             // each result will go away right now (it might be gc'ed later)
  1084.             // so we have to clear the 'param' element manually
  1085.             var session = this.mLastResults[name];
  1086.             if (session) {
  1087.               const resultCount = session.items.Count();
  1088.               for (var i=0; i<resultCount; i++)
  1089.                   session.items.QueryElementAt(i, nsIAutoCompleteItem).param = null;
  1090.               this.mLastResults[name] = null;
  1091.             }
  1092.             this.mLastStatus[name] = null;
  1093.           }
  1094.         ]]></body>
  1095.       </method>
  1096.  
  1097.       <!-- ::::::::::::: miscellaneous ::::::::::::: -->
  1098.  
  1099.       <!--  -->
  1100.       <method name="ifSetAttribute">
  1101.         <parameter name="aAttr"/>
  1102.         <parameter name="aVal"/>
  1103.         <body><![CDATA[
  1104.           if (this.getAttribute(aAttr) == "")
  1105.             this.setAttribute(aAttr, aVal);
  1106.         ]]></body>
  1107.       </method>
  1108.  
  1109.       <!--  -->
  1110.       <method name="clearTimer">
  1111.         <parameter name=""/>
  1112.         <body><![CDATA[
  1113.           if (this.mAutoCompleteTimer) {
  1114.             clearTimeout(this.mAutoCompleteTimer);
  1115.             this.mAutoCompleteTimer = 0;
  1116.           }
  1117.         ]]></body>
  1118.       </method>
  1119.  
  1120.       <!-- ::::::::::::: event dispatching ::::::::::::: -->
  1121.  
  1122.       <method name="_fireEvent">
  1123.         <parameter name="aEventType"/>
  1124.         <parameter name="aEventParam"/>
  1125.         <parameter name="aTriggeringEvent"/>
  1126.         <body>
  1127.         <![CDATA[
  1128.           var noCancel = true;
  1129.           // handle any xml attribute event handlers
  1130.           var handler = this.getAttribute("on"+aEventType);
  1131.           if (handler) {
  1132.             var fn = new Function("eventParam", "domEvent", handler);
  1133.             var returned = fn.apply(this, [aEventParam, aTriggeringEvent]);
  1134.             if (returned == false)
  1135.               noCancel = false;
  1136.           }
  1137.           
  1138.           return noCancel;
  1139.         ]]>
  1140.         </body>
  1141.       </method>
  1142.  
  1143.       <!-- =================== TREE VIEW =================== -->
  1144.  
  1145.       <field name="view"><![CDATA[
  1146.         ({
  1147.           mTextbox: this,
  1148.           mTree: null,
  1149.           mBoxObject: null,
  1150.           mResults: [],
  1151.           mRowCount: 0,
  1152.           mSelectedIndex: null,
  1153.           
  1154.           get treeBoxObject()
  1155.           {
  1156.             return this.mTree;
  1157.           },
  1158.           
  1159.           set selectedIndex(aRow)
  1160.           {
  1161.             if (!this.mBoxObject)
  1162.               return;
  1163.               
  1164.             if (this.mSelectedIndex != null)
  1165.               this.mBoxObject.invalidateRow(this.mSelectedIndex);
  1166.               
  1167.             this.mSelectedIndex = aRow;
  1168.             
  1169.             this.mBoxObject.invalidateRow(aRow);
  1170.             
  1171.             if (aRow != null)
  1172.               this.mBoxObject.ensureRowIsVisible(aRow);
  1173.           },
  1174.           
  1175.           get selectedIndex()
  1176.           {
  1177.             return this.mSelectedIndex;
  1178.           },
  1179.           
  1180.           clearResults: function()
  1181.           {
  1182.             var oldCount = this.mRowCount;
  1183.             this.mRowCount = 0;  
  1184.             this.mResults = [];
  1185.             
  1186.             if (this.mTree)
  1187.               this.mTree.rowCountChanged(0, -oldCount);
  1188.           },
  1189.           
  1190.           addResults: function(aResults)
  1191.           {
  1192.             this.mResults.push(aResults);
  1193.             var oldCount = this.mRowCount;
  1194.             this.mRowCount += aResults.items.Count();
  1195.             
  1196.             if (this.mTree)
  1197.               this.mTree.rowCountChanged(oldCount, this.mRowCount - oldCount);
  1198.           },
  1199.           
  1200.           createAtom: function(aVal)
  1201.           {
  1202.             try {
  1203.               var i = Components.interfaces.nsIAtomService;
  1204.               var svc = Components.classes["@mozilla.org/atom-service;1"].getService(i);
  1205.               return svc.getAtom(aVal);
  1206.             } catch(ex) { }
  1207.             return null; // XXX equivalent to falling off the end?
  1208.           },
  1209.  
  1210.           //////////////////////////////////////////////////////////
  1211.           // nsITreeView interface
  1212.           
  1213.           get rowCount() {
  1214.             return this.mRowCount;
  1215.           },
  1216.           
  1217.           // implementing these results in a crash
  1218.           // get selection() {},
  1219.           // set selection(aVal) { },
  1220.           
  1221.           setTree: function(aTree)
  1222.           {
  1223.             this.mTree = aTree;
  1224.             
  1225.             if (aTree) {
  1226.               this.mBoxObject = this.mTextbox.resultsPopup.tree.treeBoxObject;
  1227.             
  1228.               // cache atoms for pseudoelement properties
  1229.               this.mAtomSelected = this.createAtom("menuactive")
  1230.             }
  1231.           },
  1232.           
  1233.           getCellText: function(aRow, aColId)
  1234.           {
  1235.             var result = this.mTextbox.getResultAt(aRow);
  1236.             if (!result) return "";
  1237.             return aColId == "value" ? result.value : (aColId == "comment" ? result.comment : "");
  1238.           },
  1239.  
  1240.           getRowProperties: function(aIndex, aProperties) 
  1241.           {
  1242.             if (aIndex == this.mSelectedIndex)
  1243.               aProperties.AppendElement(this.mAtomSelected);
  1244.           },
  1245.  
  1246.           getCellProperties: function(aIndex, aColId, aProperties)
  1247.           {
  1248.             this.getRowProperties(aIndex, aProperties);
  1249.  
  1250.             // for the value column, append nsIAutoCompleteItem::className
  1251.             // to the property list so that we can style this column
  1252.             // using that property
  1253.             try { 
  1254.               if (aColId == "value") {
  1255.                 var className = this.mTextbox.getResultAt(aIndex).className;
  1256.                   if ( className != "" ) {
  1257.                     aProperties.AppendElement(this.createAtom(className));
  1258.                   }
  1259.               }
  1260.             } catch (ex) {
  1261.               // the ability to style here is a frill, so don't abort
  1262.               // if there's a problem
  1263.             } 
  1264.           },
  1265.  
  1266.           getColumnProperties: function(aColId, aColElt, aProperties) 
  1267.           {
  1268.           },
  1269.           
  1270.           getParentIndex: function(aRowIndex) { },
  1271.           hasNextSibling: function(aRowIndex, aAfterIndex) { },
  1272.           getLevel: function(aIndex) {},
  1273.           getImageSrc: function(aRow, aColId) {},
  1274.           getProgressMode: function(aRow, aColId) {},
  1275.           getCellValue: function(aRow, aColId) {},
  1276.           isContainer: function(aIndex) {},
  1277.           isContainerOpen: function(aIndex) {},
  1278.           isContainerEmpty: function(aIndex) {},
  1279.           isSeparator: function(aIndex) {},
  1280.           isSorted: function() {},
  1281.           toggleOpenState: function(aIndex) {},
  1282.           selectionChanged: function() {},
  1283.           cycleHeader: function(aColId, aElt) {},
  1284.           cycleCell: function(aRow, aColId) {},
  1285.           isEditable: function(aRow, aColId) {},
  1286.           setCellText: function(aRow, aColId, aValue) {},
  1287.           performAction: function(aAction) {},
  1288.           performActionOnRow: function(aAction, aRow) {},
  1289.           performActionOnCell: function(aAction, aRow, aColId) {}
  1290.         });
  1291.       ]]></field>
  1292.  
  1293.     </implementation>
  1294.  
  1295.     <handlers>
  1296.       <handler event="input"
  1297.                action="this.processInput();"/>
  1298.  
  1299.       <handler event="keypress" phase="capturing"
  1300.                 action="return this.processKeyPress(event);"/>
  1301.  
  1302.       <handler event="focus" phase="capturing"
  1303.                action="this.userAction = 'typing';"/>
  1304.  
  1305.       <handler event="blur" phase="capturing"
  1306.                action="if ( !(this.ignoreBlurWhileSearching && this.isSearching) ) {this.userAction = 'none'; this.finishAutoComplete(false, false, event);}"/>
  1307.  
  1308.       <handler event="mousedown" phase="capturing"
  1309.                action="if ( !this.mMenuOpen ) this.finishAutoComplete(false, false, event);"/>
  1310.     </handlers>
  1311.   </binding> 
  1312.  
  1313.   <binding id="autocomplete-result-popup" extends="chrome://global/content/bindings/popup.xml#popup">
  1314.     <content>
  1315.       <xul:tree anonid="tree" class="autocomplete-tree plain" flex="1">
  1316.         <xul:treecols anonid="treecols"/>
  1317.         <xul:treechildren anonid="treebody" class="autocomplete-treebody"/>
  1318.       </xul:tree>
  1319.     </content>
  1320.     
  1321.     <implementation>
  1322.       <constructor><![CDATA[
  1323.         if (this.textbox && this.textbox.view)
  1324.           this.initialize();
  1325.       ]]></constructor>
  1326.  
  1327.      <property name="textbox"
  1328.                 onget="return '__AUTOCOMPLETE_BOX__' in this ? this.__AUTOCOMPLETE_BOX__ : null;"/>
  1329.       
  1330.       <field name="tree">
  1331.         document.getAnonymousElementByAttribute(this, "anonid", "tree");
  1332.       </field>
  1333.  
  1334.       <field name="treecols">
  1335.         document.getAnonymousElementByAttribute(this, "anonid", "treecols");
  1336.       </field>
  1337.  
  1338.       <field name="treebody">
  1339.         document.getAnonymousElementByAttribute(this, "anonid", "treebody");
  1340.       </field>
  1341.  
  1342.       <property name="view" onget="return this.mView;">
  1343.         <setter><![CDATA[
  1344.           this.mView = val;
  1345.           var bx = this.tree.boxObject;
  1346.           bx = bx.QueryInterface(Components.interfaces.nsITreeBoxObject);
  1347.           bx.view = val;
  1348.         ]]></setter>
  1349.       </property>
  1350.  
  1351.      <property name="pageCount"
  1352.                onget="return this.tree.treeBoxObject.getPageCount();"/>
  1353.  
  1354.       <property name="selectedIndex"
  1355.                 onget="return this.textbox.view.selectedIndex"
  1356.                 onset="this.textbox.view.selectedIndex = val; return val;"/>
  1357.  
  1358.      <field name="mLastRows">0</field>
  1359.  
  1360.       <method name="initialize">
  1361.         <body><![CDATA[
  1362.         this.tree.__AUTOCOMPLETE_BOX__ = this.textbox;
  1363.         this.treebody.__AUTOCOMPLETE_BOX__ = this.textbox;
  1364.  
  1365.         this._selectedIndex = null;
  1366.  
  1367.         this.initColumns();
  1368.         this.view = this.textbox.view;
  1369.         ]]></body>
  1370.       </method>
  1371.  
  1372.       <!-- initialize the columns in the tree -->
  1373.       <method name="initColumns">
  1374.         <body><![CDATA[
  1375.           if (this.textbox.showCommentColumn) {
  1376.             this.addColumn({id: "value", flex: 2});
  1377.             this.addColumn({id: "comment", flex: 1});
  1378.           } else
  1379.             this.addColumn({id: "value", flex: 1});
  1380.         ]]></body>
  1381.       </method>
  1382.  
  1383.       <method name="addColumn">
  1384.         <parameter name="aAttrs"/>
  1385.         <body><![CDATA[
  1386.           var col = document.createElement("treecol");
  1387.           col.setAttribute("class", "autocomplete-treecol");
  1388.           for (var name in aAttrs)
  1389.             col.setAttribute(name, aAttrs[name]);
  1390.           this.treecols.appendChild(col);
  1391.           return col;
  1392.         ]]></body>
  1393.       </method>
  1394.  
  1395.       <!-- remove a single column from the tree, specified by
  1396.            element id -->
  1397.       <method name="removeColumn">
  1398.         <parameter name="aColId"/>
  1399.         <body><![CDATA[
  1400.           return this.tree.removeChild(document.getElementById(aColId));
  1401.         ]]></body>
  1402.       </method>
  1403.  
  1404.       <method name="adjustHeight">
  1405.         <body><![CDATA[
  1406.           // detect the desired height of the tree
  1407.           var bx = this.tree.treeBoxObject;
  1408.           var view = this.textbox.view;
  1409.           var rows = this.textbox.maxRows;
  1410.           if (!view.rowCount || (rows && view.rowCount < rows))
  1411.             rows = view.rowCount;
  1412.           
  1413.           var height = rows * bx.rowHeight;
  1414.           
  1415.           if (height == 0)
  1416.             this.tree.setAttribute("collapsed", "true");
  1417.           else {
  1418.             if (this.tree.hasAttribute("collapsed"))
  1419.               this.tree.removeAttribute("collapsed");
  1420.             this.tree.setAttribute("height", height);
  1421.           }
  1422.         ]]></body>
  1423.       </method>
  1424.  
  1425.       <method name="selectBy">
  1426.         <parameter name="aDir"/>
  1427.         <parameter name="aAmount"/>
  1428.         <body><![CDATA[
  1429.           try {
  1430.             var bx = this.tree.treeBoxObject;
  1431.             var view = bx.view;
  1432.             this.selectedIndex = this.getNextIndex(aDir, aAmount, this.selectedIndex, view.rowCount-1);
  1433.   
  1434.             return this.selectedIndex;
  1435.           } catch (ex) {
  1436.             // do nothing - occasionally timer-related js errors happen here
  1437.             // e.g. "this.selectedIndex has no properties", when you type fast and hit a
  1438.             // navigation key before this popup has opened
  1439.             return null;
  1440.           }
  1441.         ]]></body>
  1442.       </method>
  1443.  
  1444.       <method name="getNextIndex">
  1445.         <parameter name="aDir"/>
  1446.         <parameter name="aAmount"/>
  1447.         <parameter name="aIndex"/>
  1448.         <parameter name="aMaxRow"/>
  1449.         <body><![CDATA[
  1450.           if (aMaxRow < 0)
  1451.             return null;
  1452.             
  1453.           var newIdx = aIndex + aDir*aAmount;
  1454.           if (aDir < 0 && aIndex == null || newIdx > aMaxRow && aIndex != aMaxRow)
  1455.             newIdx = aMaxRow;
  1456.           else if (aDir > 0 && aIndex == null || newIdx < 0 && aIndex != 0)
  1457.             newIdx = 0;
  1458.           
  1459.           if (newIdx < 0 && aIndex == 0 || newIdx > aMaxRow && aIndex == aMaxRow)
  1460.             aIndex = null;
  1461.           else
  1462.             aIndex = newIdx;
  1463.           
  1464.           return aIndex;
  1465.         ]]></body>
  1466.       </method>
  1467.  
  1468.       <!-- This method is meant to be overriden by bindings extending this one.  When the 
  1469.            user selects an item from the list by hitting enter or clicking, this method
  1470.            can set the value of the textbox to a different value if it wants to. -->
  1471.       <method name="getOverrideValue">
  1472.         <body><![CDATA[
  1473.           return null;
  1474.         ]]></body>
  1475.       </method>
  1476.  
  1477.     </implementation>
  1478.  
  1479.     <handlers>
  1480.       <handler event="popupshowing">
  1481.         this.textbox.mMenuOpen = true;
  1482.       </handler>
  1483.  
  1484.       <handler event="popuphiding">
  1485.         this.textbox.mMenuOpen = false;
  1486.         this.selectedIndex = null;
  1487.       </handler>
  1488.  
  1489.       <handler event="mousedown" phase="capturing" preventdefault="true"/>
  1490.     </handlers>
  1491.   </binding>
  1492.  
  1493.   <binding id="autocomplete-tree" extends="chrome://global/content/bindings/tree.xml#tree">
  1494.     <implementation>
  1495.       <property name="textbox"
  1496.                 onget="return this.__AUTOCOMPLETE_BOX__;"/>
  1497.     </implementation>
  1498.   </binding>
  1499.  
  1500.   <binding id="autocomplete-treebody">
  1501.     <implementation>
  1502.       <property name="textbox"
  1503.                 onget="return this.__AUTOCOMPLETE_BOX__;"/>
  1504.  
  1505.       <field name="mLastMoveTime">new Date()</field>
  1506.  
  1507.       <method name="getHoverCell">
  1508.         <parameter name="aEvent"/>
  1509.         <body><![CDATA[
  1510.            var row = {}; var col = {}; var obj = {};
  1511.            var x = aEvent.screenX - this.boxObject.screenX + this.boxObject.x;
  1512.            var y = aEvent.screenY - this.boxObject.screenY + this.boxObject.y;
  1513.            this.textbox.view.treeBoxObject.getCellAt(x, y, row, col, obj);
  1514.            if (row.value >= 0)
  1515.              return {row: row.value, column: col.value};
  1516.            else
  1517.             return null;
  1518.         ]]></body>
  1519.       </method>
  1520.     </implementation>
  1521.     
  1522.     <handlers>
  1523.       <handler event="mouseout" action="this.textbox.view.selectedIndex = null;"/>
  1524.       <handler event="mouseup" action="this.textbox.onResultClick();"/>
  1525.  
  1526.       <handler event="mousemove"><![CDATA[
  1527.         if (new Date() - this.mLastMoveTime > 30) {
  1528.          var rc = this.getHoverCell(event);
  1529.          if (rc && rc.row != this.textbox.view.selectedIndex) 
  1530.             this.textbox.view.selectedIndex = rc.row;
  1531.           this.mLastMoveTime = new Date();
  1532.         }
  1533.       ]]></handler>
  1534.     </handlers>
  1535.   </binding>
  1536.  
  1537.   <binding id="autocomplete-history-popup"
  1538.            extends="chrome://global/content/bindings/popup.xml#popup-scrollbars">
  1539.     <resources>
  1540.       <stylesheet src="chrome://global/content/autocomplete.css"/>
  1541.       <stylesheet src="chrome://global/skin/autocomplete.css"/>
  1542.     </resources>
  1543.     
  1544.     <implementation>
  1545.       <method name="removeOpenAttribute">
  1546.         <parameter name="parentNode"/>
  1547.         <body><![CDATA[
  1548.           parentNode.removeAttribute("open");
  1549.         ]]></body>
  1550.       </method>
  1551.     </implementation>
  1552.  
  1553.     <handlers>
  1554.       <handler event="popuphiding"><![CDATA[
  1555.         setTimeout(this.removeOpenAttribute, 0, this.parentNode);
  1556.       ]]></handler>
  1557.     </handlers>
  1558.   </binding>
  1559.  
  1560.   <binding id="history-dropmarker" extends="xul:button">
  1561.     <content>
  1562.       <xul:image class="dropmarker-image"/>
  1563.     </content>
  1564.       
  1565.     <implementation implements="nsIAccessibleProvider">
  1566.       <property name="accessible">
  1567.         <getter>
  1568.           <![CDATA[
  1569.             var accService = Components.classes["@mozilla.org/accessibilityService;1"].getService(Components.interfaces.nsIAccessibilityService);
  1570.             return accService.createXULDropmarkerAccessible(this);
  1571.           ]]>
  1572.         </getter>
  1573.       </property>
  1574.  
  1575.       <method name="showPopup">
  1576.         <body><![CDATA[
  1577.           var textbox = document.getBindingParent(this);
  1578.           var kids = textbox.getElementsByTagName("menupopup");
  1579.           if (kids.item(0) && textbox.getAttribute("open") != "true") { // Open history popup
  1580.             kids[0].showPopup(textbox, -1, -1, "popup", "bottomleft", "topleft");
  1581.             textbox.setAttribute("open", "true");
  1582.           }
  1583.         ]]></body>
  1584.       </method>
  1585.     </implementation>
  1586.     
  1587.     <handlers>
  1588.       <handler event="mousedown"><![CDATA[
  1589.         this.showPopup();
  1590.       ]]></handler>
  1591.     </handlers>
  1592.   </binding>
  1593.  
  1594. </bindings>
  1595.  
  1596.