mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-06-06 16:38:55 +00:00
1647 lines
58 KiB
XML
1647 lines
58 KiB
XML
<?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/. -->
|
|
|
|
|
|
<bindings id="autocompleteBindings"
|
|
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="autocomplete" role="xul:combobox"
|
|
extends="chrome://global/content/bindings/textbox.xml#textbox">
|
|
<resources>
|
|
<stylesheet src="chrome://communicator/content/autocomplete.css"/>
|
|
<stylesheet src="chrome://global/skin/autocomplete.css"/>
|
|
</resources>
|
|
|
|
<content>
|
|
<children includes="menupopup"/>
|
|
|
|
<xul:hbox class="autocomplete-textbox-container" flex="1" align="center">
|
|
<children includes="image|deck|stack|box">
|
|
<xul:image class="autocomplete-icon" allowevents="true"/>
|
|
</children>
|
|
|
|
<xul:hbox class="textbox-input-box" flex="1" xbl:inherits="context,tooltiptext=inputtooltiptext">
|
|
<children/>
|
|
<html:input anonid="input" class="autocomplete-textbox textbox-input"
|
|
allowevents="true"
|
|
xbl:inherits="tooltiptext=inputtooltiptext,value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,mozactionhint,userAction"/>
|
|
</xul:hbox>
|
|
<children includes="hbox"/>
|
|
</xul:hbox>
|
|
|
|
<xul:dropmarker class="autocomplete-history-dropmarker" allowevents="true"
|
|
xbl:inherits="open,enablehistory" anonid="historydropmarker"/>
|
|
|
|
<xul:popupset>
|
|
<xul:panel type="autocomplete" anonid="popup"
|
|
ignorekeys="true" noautofocus="true" level="top"
|
|
xbl:inherits="for=id,nomatch"/>
|
|
</xul:popupset>
|
|
</content>
|
|
|
|
<implementation implements="nsIDOMXULMenuListElement">
|
|
|
|
<constructor><![CDATA[
|
|
// XXX bug 90337 band-aid until we figure out what's going on here
|
|
if (this.value != this.mInputElt.value)
|
|
this.mInputElt.value = this.value;
|
|
delete this.value;
|
|
|
|
// listen for pastes
|
|
this.mInputElt.controllers.insertControllerAt(0, this.mPasteController);
|
|
|
|
// listen for menubar activation
|
|
window.top.addEventListener("DOMMenuBarActive", this.mMenuBarListener, true);
|
|
|
|
// set default property values
|
|
this.ifSetAttribute("timeout", 50);
|
|
this.ifSetAttribute("pastetimeout", 1000);
|
|
this.ifSetAttribute("maxrows", 5);
|
|
this.ifSetAttribute("showpopup", true);
|
|
this.ifSetAttribute("disableKeyNavigation", true);
|
|
|
|
// initialize the search sessions
|
|
if (this.hasAttribute("autocompletesearch"))
|
|
this.initAutoCompleteSearch();
|
|
|
|
// hack to work around lack of bottom-up constructor calling
|
|
if ("initialize" in this.popup)
|
|
this.popup.initialize();
|
|
]]></constructor>
|
|
|
|
<destructor><![CDATA[
|
|
this.clearResults(false);
|
|
window.top.removeEventListener("DOMMenuBarActive", this.mMenuBarListener, true);
|
|
this.mInputElt.controllers.removeController(this.mPasteController);
|
|
]]></destructor>
|
|
|
|
<!-- =================== nsIAutoCompleteInput =================== -->
|
|
<!-- XXX: This implementation is currently incomplete. -->
|
|
|
|
<!-- reference to the results popup element -->
|
|
<field name="popup"><![CDATA[
|
|
document.getAnonymousElementByAttribute(this, "anonid", "popup");
|
|
]]></field>
|
|
|
|
<property name="popupOpen"
|
|
onget="return this.mMenuOpen;"
|
|
onset="if (val) this.openPopup(); else this.closePopup(); return val;"/>
|
|
|
|
<!-- option to turn off autocomplete -->
|
|
<property name="disableAutoComplete"
|
|
onset="this.setAttribute('disableautocomplete', val); return val;"
|
|
onget="return this.getAttribute('disableautocomplete') == 'true';"/>
|
|
|
|
<!-- if the resulting match string is not at the beginning of the typed string,
|
|
this will optionally autofill like this "bar |>> foobar|" -->
|
|
<property name="completeDefaultIndex"
|
|
onset="this.setAttribute('completedefaultindex', val); return val;"
|
|
onget="return this.getAttribute('completedefaultindex') == 'true';"/>
|
|
|
|
<!-- option for completing to the default result whenever the user hits
|
|
enter or the textbox loses focus -->
|
|
<property name="forceComplete"
|
|
onset="this.setAttribute('forcecomplete', val); return val;"
|
|
onget="return this.getAttribute('forcecomplete') == 'true';"/>
|
|
|
|
<property name="minResultsForPopup"
|
|
onset="this.setAttribute('minresultsforpopup', val); return val;"
|
|
onget="var t = this.getAttribute('minresultsforpopup'); return t ? parseInt(t) : 1;"/>
|
|
|
|
<!-- maximum number of rows to display -->
|
|
<property name="maxRows"
|
|
onset="this.setAttribute('maxrows', val); return val;"
|
|
onget="return parseInt(this.getAttribute('maxrows')) || 0;"/>
|
|
|
|
<!-- toggles a second column in the results list which contains
|
|
the string in the comment field of each autocomplete result -->
|
|
<property name="showCommentColumn"
|
|
onget="return this.getAttribute('showcommentcolumn') == 'true';">
|
|
<setter><![CDATA[
|
|
this.popup.showCommentColumn = val;
|
|
this.setAttribute('showcommentcolumn', val);
|
|
return val;
|
|
]]></setter>
|
|
</property>
|
|
|
|
<!-- number of milliseconds after a keystroke before a search begins -->
|
|
<property name="timeout"
|
|
onset="this.setAttribute('timeout', val); return val;"
|
|
onget="return parseInt(this.getAttribute('timeout')) || 0;"/>
|
|
|
|
<property name="searchParam"
|
|
onget="return this.getAttribute('autocompletesearchparam') || '';"
|
|
onset="this.setAttribute('autocompletesearchparam', val); return val;"/>
|
|
|
|
<property name="searchCount" readonly="true"
|
|
onget="return this.sessionCount;"/>
|
|
|
|
<method name="getSearchAt">
|
|
<parameter name="aIndex"/>
|
|
<body><![CDATA[
|
|
var idx = -1;
|
|
for (var name in this.mSessions)
|
|
if (++idx == aIndex)
|
|
return name;
|
|
|
|
return null;
|
|
]]></body>
|
|
</method>
|
|
|
|
<property name="textValue"
|
|
onget="return this.value;"
|
|
onset="this.setTextValue(val); return val;"/>
|
|
|
|
<method name="onSearchBegin">
|
|
<body><![CDATA[
|
|
this._fireEvent("searchbegin");
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="onSearchComplete">
|
|
<body><![CDATA[
|
|
if (this.noMatch)
|
|
this.setAttribute("nomatch", "true");
|
|
else
|
|
this.removeAttribute("nomatch");
|
|
|
|
this._fireEvent("searchcomplete");
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="onTextReverted">
|
|
<body><![CDATA[
|
|
return this._fireEvent("textreverted");
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- =================== nsIDOMXULMenuListElement =================== -->
|
|
|
|
<property name="editable" readonly="true"
|
|
onget="return true;" />
|
|
|
|
<property name="crop"
|
|
onset="this.setAttribute('crop', val); return val;"
|
|
onget="return this.getAttribute('crop');"/>
|
|
|
|
<property name="label" readonly="true"
|
|
onget="return this.mInputElt.value;"/>
|
|
|
|
<property name="open"
|
|
onget="return this.getAttribute('open') == 'true';">
|
|
<setter>
|
|
<![CDATA[
|
|
var historyPopup = document.getAnonymousElementByAttribute(this, "anonid", "historydropmarker");
|
|
if (val) {
|
|
this.setAttribute('open', true);
|
|
historyPopup.showPopup();
|
|
} else {
|
|
this.removeAttribute('open');
|
|
historyPopup.hidePopup();
|
|
}
|
|
]]>
|
|
</setter>
|
|
</property>
|
|
|
|
<!-- =================== PUBLIC PROPERTIES =================== -->
|
|
|
|
<property name="value"
|
|
onget="return this.mInputElt.value;">
|
|
<setter><![CDATA[
|
|
this.ignoreInputEvent = true;
|
|
this.mInputElt.value = val;
|
|
this.ignoreInputEvent = false;
|
|
var event = document.createEvent('Events');
|
|
event.initEvent('ValueChange', true, true);
|
|
this.mInputElt.dispatchEvent(event);
|
|
return val;
|
|
]]></setter>
|
|
</property>
|
|
|
|
<property name="focused"
|
|
onget="return this.getAttribute('focused') == 'true';"/>
|
|
|
|
<method name="initAutoCompleteSearch">
|
|
<body><![CDATA[
|
|
var list = this.getAttribute("autocompletesearch").split(" ");
|
|
for (var i = 0; i < list.length; i++) {
|
|
var name = list[i];
|
|
var contractid = "@mozilla.org/autocomplete/search;1?name=" + name;
|
|
if (contractid in Components.classes) {
|
|
try {
|
|
this.mSessions[name] =
|
|
Components.classes[contractid].getService(Components.interfaces.nsIAutoCompleteSearch);
|
|
this.mLastResults[name] = null;
|
|
this.mLastRows[name] = 0;
|
|
++this.sessionCount;
|
|
} catch (e) {
|
|
dump("### ERROR - unable to create search \"" + name + "\".\n");
|
|
}
|
|
} else {
|
|
dump("search \"" + name + "\" not found - skipping.\n");
|
|
}
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- the number of sessions currently in use -->
|
|
<field name="sessionCount">0</field>
|
|
|
|
<!-- number of milliseconds after a paste before a search begins -->
|
|
<property name="pasteTimeout"
|
|
onset="this.setAttribute('pastetimeout', val); return val;"
|
|
onget="var t = parseInt(this.getAttribute('pastetimeout')); return t ? t : 0;"/>
|
|
|
|
<!-- option for filling the textbox with the best match while typing
|
|
and selecting the difference -->
|
|
<property name="autoFill"
|
|
onset="this.setAttribute('autofill', val); return val;"
|
|
onget="return this.getAttribute('autofill') == 'true';"/>
|
|
|
|
<!-- if this attribute is set, allow different style for
|
|
non auto-completed lines -->
|
|
<property name="highlightNonMatches"
|
|
onset="this.setAttribute('highlightnonmatches', val); return val;"
|
|
onget="return this.getAttribute('highlightnonmatches') == 'true';"/>
|
|
|
|
<!-- option to show the popup containing the results -->
|
|
<property name="showPopup"
|
|
onset="this.setAttribute('showpopup', val); return val;"
|
|
onget="return this.getAttribute('showpopup') == 'true';"/>
|
|
|
|
<!-- option to allow scrolling through the list via the tab key, rather than
|
|
tab moving focus out of the textbox -->
|
|
<property name="tabScrolling"
|
|
onset="return this.setAttribute('tabscrolling', val); return val;"
|
|
onget="return this.getAttribute('tabscrolling') == 'true';"/>
|
|
|
|
<!-- option to completely ignore any blur events while
|
|
searches are still going on. This is useful so that nothing
|
|
gets autopicked if the window is required to lose focus for
|
|
some reason (eg in LDAP autocomplete, another window may be
|
|
brought up so that the user can enter a password to authenticate
|
|
to an LDAP server). -->
|
|
<property name="ignoreBlurWhileSearching"
|
|
onset="this.setAttribute('ignoreblurwhilesearching', val); return val;"
|
|
onget="return this.getAttribute('ignoreblurwhilesearching') == 'true';"/>
|
|
|
|
<!-- state which indicates the current action being performed by the user.
|
|
Possible values are : none, typing, scrolling -->
|
|
<property name="userAction"
|
|
onset="this.setAttribute('userAction', val); return val;"
|
|
onget="return this.getAttribute('userAction');"/>
|
|
|
|
<!-- state which indicates if the last search had no matches -->
|
|
<field name="noMatch">true</field>
|
|
|
|
<!-- state which indicates a search is currently happening -->
|
|
<field name="isSearching">false</field>
|
|
|
|
<!-- state which indicates a search timeout is current waiting -->
|
|
<property name="isWaiting"
|
|
onget="return this.mAutoCompleteTimer != 0;"/>
|
|
|
|
<!-- =================== PRIVATE PROPERTIES =================== -->
|
|
|
|
<field name="mSessions">({})</field>
|
|
<field name="mLastResults">({})</field>
|
|
<field name="mLastRows">({})</field>
|
|
<field name="mLastKeyCode">null</field>
|
|
<field name="mAutoCompleteTimer">0</field>
|
|
<field name="mMenuOpen">false</field>
|
|
<field name="mFireAfterSearch">false</field>
|
|
<field name="mFinishAfterSearch">false</field>
|
|
<field name="mNeedToFinish">false</field>
|
|
<field name="mNeedToComplete">false</field>
|
|
<field name="mTransientValue">false</field>
|
|
<field name="mView">null</field>
|
|
<field name="currentSearchString">""</field>
|
|
<field name="ignoreInputEvent">false</field>
|
|
<field name="oninit">null</field>
|
|
<field name="mDefaultMatchFilled">false</field>
|
|
<field name="mFirstReturn">true</field>
|
|
<field name="mIsPasting">false</field>
|
|
|
|
<field name="mPasteController"><![CDATA[
|
|
({
|
|
self: this,
|
|
kGlobalClipboard: Components.interfaces.nsIClipboard.kGlobalClipboard,
|
|
supportsCommand: function(aCommand) {
|
|
return aCommand == "cmd_paste";
|
|
},
|
|
isCommandEnabled: function(aCommand) {
|
|
return aCommand == "cmd_paste" &&
|
|
this.self.editor.isSelectionEditable &&
|
|
this.self.editor.canPaste(this.kGlobalClipboard);
|
|
},
|
|
doCommand: function(aCommand) {
|
|
if (aCommand == "cmd_paste") {
|
|
this.self.mIsPasting = true;
|
|
this.self.editor.paste(this.kGlobalClipboard);
|
|
this.self.mIsPasting = false;
|
|
}
|
|
},
|
|
onEvent: function() {}
|
|
})
|
|
]]></field>
|
|
|
|
<field name="mMenuBarListener"><![CDATA[
|
|
({
|
|
self: this,
|
|
handleEvent: function(aEvent) {
|
|
try {
|
|
this.self.finishAutoComplete(false, false, aEvent);
|
|
this.self.clearTimer();
|
|
this.self.closePopup();
|
|
} catch (e) {
|
|
window.top.removeEventListener("DOMMenuBarActive", this, true);
|
|
}
|
|
}
|
|
})
|
|
]]></field>
|
|
|
|
<field name="mAutoCompleteObserver"><![CDATA[
|
|
({
|
|
self: this,
|
|
onSearchResult: function(aSearch, aResult) {
|
|
for (var name in this.self.mSessions)
|
|
if (this.self.mSessions[name] == aSearch)
|
|
this.self.processResults(name, aResult);
|
|
}
|
|
})
|
|
]]></field>
|
|
|
|
<field name="mInputElt"><![CDATA[
|
|
document.getAnonymousElementByAttribute(this, "anonid", "input");
|
|
]]></field>
|
|
|
|
<field name="mMenuAccessKey"><![CDATA[
|
|
Components.classes["@mozilla.org/preferences-service;1"]
|
|
.getService(Components.interfaces.nsIPrefBranch)
|
|
.getIntPref("ui.key.menuAccessKey");
|
|
]]></field>
|
|
|
|
<!-- =================== PUBLIC METHODS =================== -->
|
|
|
|
<method name="getErrorAt">
|
|
<parameter name="aIndex"/>
|
|
<body><![CDATA[
|
|
var obj = aIndex < 0 ? null : this.convertIndexToSession(aIndex);
|
|
return obj && this.mLastResults[obj.session] &&
|
|
this.mLastResults[obj.session].errorDescription;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- get a value from the autocomplete results as a string via an absolute index-->
|
|
<method name="getResultValueAt">
|
|
<parameter name="aIndex"/>
|
|
<body><![CDATA[
|
|
var obj = this.convertIndexToSession(aIndex);
|
|
return obj ? this.getSessionValueAt(obj.session, obj.index) : null;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- get a value from the autocomplete results as a string from a specific session -->
|
|
<method name="getSessionValueAt">
|
|
<parameter name="aSession"/>
|
|
<parameter name="aIndex"/>
|
|
<body><![CDATA[
|
|
var result = this.mLastResults[aSession];
|
|
return result.errorDescription || result.getValueAt(aIndex);
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- get the total number of results overall -->
|
|
<method name="getResultCount">
|
|
<body><![CDATA[
|
|
return this.view.rowCount;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- get the first session that has results -->
|
|
<method name="getDefaultSession">
|
|
<body><![CDATA[
|
|
for (var name in this.mLastResults) {
|
|
var results = this.mLastResults[name];
|
|
if (results && results.matchCount > 0 && !results.errorDescription)
|
|
return name;
|
|
}
|
|
return null;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- empty the cached result data and empty the results popup -->
|
|
<method name="clearResults">
|
|
<parameter name="aInvalidate"/>
|
|
<body><![CDATA[
|
|
this.clearResultData();
|
|
this.clearResultElements(aInvalidate);
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- =================== PRIVATE METHODS =================== -->
|
|
|
|
<!-- ::::::::::::: session searching ::::::::::::: -->
|
|
|
|
<!-- -->
|
|
<method name="callListener">
|
|
<parameter name="me"/>
|
|
<parameter name="aAction"/>
|
|
<body><![CDATA[
|
|
// bail if the binding was detached or the element removed from
|
|
// document during the timeout
|
|
if (!("startLookup" in me) || !me.ownerDocument || !me.parentNode)
|
|
return;
|
|
|
|
me.clearTimer();
|
|
|
|
if (me.disableAutoComplete)
|
|
return;
|
|
|
|
switch (aAction) {
|
|
case "startLookup":
|
|
me.startLookup();
|
|
break;
|
|
|
|
case "stopLookup":
|
|
me.stopLookup();
|
|
break;
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- -->
|
|
<method name="startLookup">
|
|
<body><![CDATA[
|
|
var str = this.currentSearchString;
|
|
if (!str) {
|
|
this.clearResults(false);
|
|
this.closePopup();
|
|
return;
|
|
}
|
|
|
|
this.isSearching = true;
|
|
this.mFirstReturn = true;
|
|
this.mSessionReturns = this.sessionCount;
|
|
this.mFailureItems = 0;
|
|
this.mDefaultMatchFilled = false; // clear out our prefill state.
|
|
|
|
// Notify the input that the search is beginning.
|
|
this.onSearchBegin();
|
|
|
|
// tell each session to start searching...
|
|
for (var name in this.mSessions)
|
|
try {
|
|
this.mSessions[name].startSearch(str, this.searchParam, this.mLastResults[name], this.mAutoCompleteObserver);
|
|
} catch (e) {
|
|
--this.mSessionReturns;
|
|
this.searchFailed();
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- -->
|
|
<method name="stopLookup">
|
|
<body><![CDATA[
|
|
for (var name in this.mSessions)
|
|
this.mSessions[name].stopSearch();
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- -->
|
|
<method name="processResults">
|
|
<parameter name="aSessionName"/>
|
|
<parameter name="aResults"/>
|
|
<body><![CDATA[
|
|
if (this.disableAutoComplete)
|
|
return;
|
|
|
|
const ACR = Components.interfaces.nsIAutoCompleteResult;
|
|
var status = aResults.searchResult;
|
|
if (status != ACR.RESULT_NOMATCH_ONGOING &&
|
|
status != ACR.RESULT_SUCCESS_ONGOING)
|
|
--this.mSessionReturns;
|
|
|
|
// check the many criteria for failure
|
|
if (aResults.errorDescription)
|
|
++this.mFailureItems;
|
|
else if (status == ACR.RESULT_IGNORED ||
|
|
status == ACR.RESULT_FAILURE ||
|
|
status == ACR.RESULT_NOMATCH ||
|
|
status == ACR.RESULT_NOMATCH_ONGOING ||
|
|
aResults.matchCount == 0 ||
|
|
aResults.searchString != this.currentSearchString)
|
|
{
|
|
this.mLastResults[aSessionName] = null;
|
|
if (this.mFirstReturn)
|
|
this.clearResultElements(false);
|
|
this.mFirstReturn = false;
|
|
this.searchFailed();
|
|
return;
|
|
}
|
|
|
|
if (this.mFirstReturn) {
|
|
if (this.view.mTree)
|
|
this.view.mTree.beginUpdateBatch();
|
|
this.clearResultElements(false); // clear results, but don't repaint yet
|
|
}
|
|
|
|
// always call openPopup...we may not have opened it
|
|
// if a previous search session didn't return enough search results.
|
|
// it's smart and doesn't try to open itself multiple times...
|
|
// be sure to add our result elements before calling openPopup as we need
|
|
// to know the total # of results found so far.
|
|
this.addResultElements(aSessionName, aResults);
|
|
|
|
this.autoFillInput(aSessionName, aResults, false);
|
|
if (this.mFirstReturn && this.view.mTree)
|
|
this.view.mTree.endUpdateBatch();
|
|
this.openPopup();
|
|
this.mFirstReturn = false;
|
|
|
|
// if this is the last session to return...
|
|
if (this.mSessionReturns == 0)
|
|
this.postSearchCleanup();
|
|
|
|
if (this.mFinishAfterSearch)
|
|
this.finishAutoComplete(false, this.mFireAfterSearch, null);
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- called each time a search fails, except when failure items need
|
|
to be displayed. If all searches have failed, clear the list
|
|
and close the popup -->
|
|
<method name="searchFailed">
|
|
<body><![CDATA[
|
|
// if all searches are done and they all failed...
|
|
if (this.mSessionReturns == 0 && this.getResultCount() == 0) {
|
|
if (this.minResultsForPopup == 0) {
|
|
this.clearResults(true); // clear data and repaint empty
|
|
this.openPopup();
|
|
} else {
|
|
this.closePopup();
|
|
}
|
|
}
|
|
|
|
// if it's the last session to return, time to clean up...
|
|
if (this.mSessionReturns == 0)
|
|
this.postSearchCleanup();
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- does some stuff after a search is done (success or failure) -->
|
|
<method name="postSearchCleanup">
|
|
<body><![CDATA[
|
|
this.isSearching = false;
|
|
|
|
// figure out if there are no matches in all search sessions
|
|
var failed = true;
|
|
for (var name in this.mSessions) {
|
|
if (this.mLastResults[name])
|
|
failed = this.mLastResults[name].errorDescription ||
|
|
this.mLastResults[name].matchCount == 0;
|
|
if (!failed)
|
|
break;
|
|
}
|
|
this.noMatch = failed;
|
|
|
|
// if we have processed all of our searches, and none of them gave us a default index,
|
|
// then we should try to auto fill the input field with the first match.
|
|
// note: autoFillInput is smart enough to kick out if we've already prefilled something...
|
|
if (!this.noMatch) {
|
|
var defaultSession = this.getDefaultSession();
|
|
if (defaultSession)
|
|
this.autoFillInput(defaultSession, this.mLastResults[defaultSession], true);
|
|
}
|
|
|
|
// Notify the input that the search is complete.
|
|
this.onSearchComplete();
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- when the focus exits the widget or user hits return,
|
|
determine what value to leave in the textbox -->
|
|
<method name="finishAutoComplete">
|
|
<parameter name="aForceComplete"/>
|
|
<parameter name="aFireTextCommand"/>
|
|
<parameter name="aTriggeringEvent"/>
|
|
<body><![CDATA[
|
|
this.mFinishAfterSearch = false;
|
|
this.mFireAfterSearch = false;
|
|
if (this.mNeedToFinish && !this.disableAutoComplete) {
|
|
// set textbox value to either override value, or default search result
|
|
var val = this.popup.overrideValue;
|
|
if (val) {
|
|
this.setTextValue(val);
|
|
this.mNeedToFinish = false;
|
|
} else if (this.mTransientValue ||
|
|
!(this.forceComplete ||
|
|
(aForceComplete &&
|
|
this.mDefaultMatchFilled &&
|
|
this.mNeedToComplete))) {
|
|
this.mNeedToFinish = false;
|
|
} else if (this.isWaiting) {
|
|
// if the user typed, the search results are out of date, so let
|
|
// the search finish, and tell it to come back here when it's done
|
|
this.mFinishAfterSearch = true;
|
|
this.mFireAfterSearch = aFireTextCommand;
|
|
return;
|
|
} else {
|
|
// we want to use the default item index for the first session which gave us a valid
|
|
// default item index...
|
|
for (var name in this.mLastResults) {
|
|
var results = this.mLastResults[name];
|
|
if (results && results.matchCount > 0 &&
|
|
!results.errorDescription && results.defaultIndex != -1)
|
|
{
|
|
val = results.getValueAt(results.defaultIndex);
|
|
this.setTextValue(val);
|
|
this.mDefaultMatchFilled = true;
|
|
this.mNeedToFinish = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (this.mNeedToFinish) {
|
|
// if a search is happening at this juncture, bail out of this function
|
|
// and let the search finish, and tell it to come back here when it's done
|
|
if (this.isSearching) {
|
|
this.mFinishAfterSearch = true;
|
|
this.mFireAfterSearch = aFireTextCommand;
|
|
return;
|
|
}
|
|
|
|
this.mNeedToFinish = false;
|
|
var defaultSession = this.getDefaultSession();
|
|
if (defaultSession)
|
|
{
|
|
// preselect the first one
|
|
var first = this.getSessionValueAt(defaultSession, 0);
|
|
this.setTextValue(first);
|
|
this.mDefaultMatchFilled = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
this.stopLookup();
|
|
|
|
this.closePopup();
|
|
}
|
|
|
|
this.mNeedToComplete = false;
|
|
this.clearTimer();
|
|
|
|
if (aFireTextCommand)
|
|
this._fireEvent("textentered", this.userAction, aTriggeringEvent);
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- when the user clicks an entry in the autocomplete popup -->
|
|
<method name="onResultClick">
|
|
<body><![CDATA[
|
|
// set textbox value to either override value, or the clicked result
|
|
var errItem = this.getErrorAt(this.popup.selectedIndex);
|
|
var val = this.popup.overrideValue;
|
|
if (val)
|
|
this.setTextValue(val);
|
|
else if (this.popup.selectedIndex != -1) {
|
|
if (errItem) {
|
|
this.setTextValue(this.currentSearchString);
|
|
this.mTransientValue = true;
|
|
} else {
|
|
this.setTextValue(this.getResultValueAt(
|
|
this.popup.selectedIndex));
|
|
}
|
|
}
|
|
|
|
this.mNeedToFinish = false;
|
|
this.mNeedToComplete = false;
|
|
|
|
this.closePopup();
|
|
|
|
this.currentSearchString = "";
|
|
|
|
if (errItem)
|
|
this._fireEvent("errorcommand", errItem);
|
|
this._fireEvent("textentered", "clicking");
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- when the user hits escape, revert the previously typed value in the textbox -->
|
|
<method name="undoAutoComplete">
|
|
<body><![CDATA[
|
|
var val = this.currentSearchString;
|
|
|
|
var ok = this.onTextReverted();
|
|
if ((ok || ok == undefined) && val)
|
|
this.setTextValue(val);
|
|
|
|
this.userAction = "typing";
|
|
|
|
this.currentSearchString = this.value;
|
|
this.mNeedToComplete = false;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- convert an absolute result index into a session name/index pair -->
|
|
<method name="convertIndexToSession">
|
|
<parameter name="aIndex"/>
|
|
<body><![CDATA[
|
|
for (var name in this.mLastRows) {
|
|
if (aIndex < this.mLastRows[name])
|
|
return { session: name, index: aIndex };
|
|
aIndex -= this.mLastRows[name];
|
|
}
|
|
return null;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- ::::::::::::: user input handling ::::::::::::: -->
|
|
|
|
<!-- -->
|
|
<method name="processInput">
|
|
<body><![CDATA[
|
|
// stop current lookup in case it's async.
|
|
this.stopLookup();
|
|
// stop the queued up lookup on a timer
|
|
this.clearTimer();
|
|
|
|
if (this.disableAutoComplete)
|
|
return;
|
|
|
|
this.userAction = "typing";
|
|
this.mFinishAfterSearch = false;
|
|
this.mNeedToFinish = true;
|
|
this.mTransientValue = false;
|
|
this.mNeedToComplete = true;
|
|
var str = this.value;
|
|
this.currentSearchString = str;
|
|
this.popup.clearSelection();
|
|
|
|
var timeout = this.mIsPasting ? this.pasteTimeout : this.timeout;
|
|
this.mAutoCompleteTimer = setTimeout(this.callListener, timeout, this, "startLookup");
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- -->
|
|
<method name="processKeyPress">
|
|
<parameter name="aEvent"/>
|
|
<body><![CDATA[
|
|
this.mLastKeyCode = aEvent.keyCode;
|
|
|
|
var killEvent = false;
|
|
|
|
switch (aEvent.keyCode) {
|
|
case KeyEvent.DOM_VK_TAB:
|
|
if (this.tabScrolling) {
|
|
// don't kill this event if alt-tab or ctrl-tab is hit
|
|
if (!aEvent.altKey && !aEvent.ctrlKey) {
|
|
killEvent = this.mMenuOpen;
|
|
if (killEvent)
|
|
this.keyNavigation(aEvent);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KeyEvent.DOM_VK_RETURN:
|
|
|
|
// if this is a failure item, save it for fireErrorCommand
|
|
var errItem = this.getErrorAt(this.popup.selectedIndex);
|
|
|
|
killEvent = this.mMenuOpen;
|
|
this.finishAutoComplete(true, true, aEvent);
|
|
this.closePopup();
|
|
if (errItem) {
|
|
this._fireEvent("errorcommand", errItem);
|
|
}
|
|
break;
|
|
|
|
case KeyEvent.DOM_VK_ESCAPE:
|
|
this.clearTimer();
|
|
killEvent = this.mMenuOpen;
|
|
this.undoAutoComplete();
|
|
this.closePopup();
|
|
break;
|
|
|
|
case KeyEvent.DOM_VK_LEFT:
|
|
case KeyEvent.DOM_VK_RIGHT:
|
|
case KeyEvent.DOM_VK_HOME:
|
|
case KeyEvent.DOM_VK_END:
|
|
this.finishAutoComplete(true, false, aEvent);
|
|
this.clearTimer();
|
|
this.closePopup();
|
|
break;
|
|
|
|
case KeyEvent.DOM_VK_DOWN:
|
|
if (!aEvent.altKey) {
|
|
this.clearTimer();
|
|
killEvent = this.keyNavigation(aEvent);
|
|
break;
|
|
}
|
|
// Alt+Down falls through to history popup toggling code
|
|
|
|
case KeyEvent.DOM_VK_F4:
|
|
if (!aEvent.ctrlKey && !aEvent.shiftKey && this.getAttribute("enablehistory") == "true") {
|
|
var historyPopup = document.getAnonymousElementByAttribute(this, "anonid", "historydropmarker");
|
|
if (historyPopup)
|
|
historyPopup.showPopup();
|
|
else
|
|
historyPopup.hidePopup();
|
|
}
|
|
break;
|
|
case KeyEvent.DOM_VK_PAGE_UP:
|
|
case KeyEvent.DOM_VK_PAGE_DOWN:
|
|
case KeyEvent.DOM_VK_UP:
|
|
if (!aEvent.ctrlKey && !aEvent.metaKey) {
|
|
this.clearTimer();
|
|
killEvent = this.keyNavigation(aEvent);
|
|
}
|
|
break;
|
|
|
|
case KeyEvent.DOM_VK_BACK_SPACE:
|
|
if (!aEvent.ctrlKey && !aEvent.altKey && !aEvent.shiftKey &&
|
|
this.selectionStart == this.currentSearchString.length &&
|
|
this.selectionEnd == this.value.length &&
|
|
this.mDefaultMatchFilled) {
|
|
this.mDefaultMatchFilled = false;
|
|
this.value = this.currentSearchString;
|
|
}
|
|
|
|
if (!/Mac/.test(navigator.platform))
|
|
break;
|
|
case KeyEvent.DOM_VK_DELETE:
|
|
if (/Mac/.test(navigator.platform) && !aEvent.shiftKey)
|
|
break;
|
|
|
|
if (this.mMenuOpen && this.popup.selectedIndex != -1) {
|
|
var obj = this.convertIndexToSession(this.popup.selectedIndex);
|
|
if (obj) {
|
|
var result = this.mLastResults[obj.session];
|
|
if (!result.errorDescription) {
|
|
var count = result.matchCount;
|
|
result.removeValueAt(obj.index, true);
|
|
this.view.updateResults(this.popup.selectedIndex, result.matchCount - count);
|
|
killEvent = true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (killEvent) {
|
|
aEvent.preventDefault();
|
|
aEvent.stopPropagation();
|
|
}
|
|
|
|
return true;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- -->
|
|
<method name="processStartComposition">
|
|
<body><![CDATA[
|
|
this.finishAutoComplete(false, false, null);
|
|
this.clearTimer();
|
|
this.closePopup();
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- -->
|
|
<method name="keyNavigation">
|
|
<parameter name="aEvent"/>
|
|
<body><![CDATA[
|
|
var k = aEvent.keyCode;
|
|
if (k == KeyEvent.DOM_VK_TAB ||
|
|
k == KeyEvent.DOM_VK_UP || k == KeyEvent.DOM_VK_DOWN ||
|
|
k == KeyEvent.DOM_VK_PAGE_UP || k == KeyEvent.DOM_VK_PAGE_DOWN)
|
|
{
|
|
if (!this.mMenuOpen) {
|
|
// Original xpfe style was to allow the up and down keys to have
|
|
// their default Mac action if the popup could not be opened.
|
|
// For compatibility for toolkit we now have to predict which
|
|
// keys have a default action that we can always allow to fire.
|
|
if (/Mac/.test(navigator.platform) &&
|
|
((k == KeyEvent.DOM_VK_UP &&
|
|
(this.selectionStart != 0 ||
|
|
this.selectionEnd != 0)) ||
|
|
(k == KeyEvent.DOM_VK_DOWN &&
|
|
(this.selectionStart != this.value.length ||
|
|
this.selectionEnd != this.value.length))))
|
|
return false;
|
|
if (this.currentSearchString != this.value) {
|
|
this.processInput();
|
|
return true;
|
|
}
|
|
if (this.view.rowCount < this.minResultsForPopup)
|
|
return true; // used to be false, see above
|
|
|
|
this.mNeedToFinish = true;
|
|
this.openPopup();
|
|
return true;
|
|
}
|
|
|
|
this.userAction = "scrolling";
|
|
this.mNeedToComplete = false;
|
|
|
|
var reverse = k == KeyEvent.DOM_VK_TAB && aEvent.shiftKey ||
|
|
k == KeyEvent.DOM_VK_UP ||
|
|
k == KeyEvent.DOM_VK_PAGE_UP;
|
|
var page = k == KeyEvent.DOM_VK_PAGE_UP ||
|
|
k == KeyEvent.DOM_VK_PAGE_DOWN;
|
|
var selected = this.popup.selectBy(reverse, page);
|
|
|
|
// determine which value to place in the textbox
|
|
this.ignoreInputEvent = true;
|
|
if (selected != -1) {
|
|
if (this.getErrorAt(selected)) {
|
|
if (this.currentSearchString)
|
|
this.setTextValue(this.currentSearchString);
|
|
} else {
|
|
this.setTextValue(this.getResultValueAt(selected));
|
|
}
|
|
this.mTransientValue = true;
|
|
} else {
|
|
if (this.currentSearchString)
|
|
this.setTextValue(this.currentSearchString);
|
|
this.mTransientValue = false;
|
|
}
|
|
|
|
// move cursor to the end
|
|
this.mInputElt.setSelectionRange(this.value.length, this.value.length);
|
|
this.ignoreInputEvent = false;
|
|
}
|
|
return true;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- while the user is typing, fill the textbox with the "default" value
|
|
if one can be assumed, and select the end of the text -->
|
|
<method name="autoFillInput">
|
|
<parameter name="aSessionName"/>
|
|
<parameter name="aResults"/>
|
|
<parameter name="aUseFirstMatchIfNoDefault"/>
|
|
<body><![CDATA[
|
|
if (this.mInputElt.selectionEnd < this.currentSearchString.length ||
|
|
this.mDefaultMatchFilled)
|
|
return;
|
|
|
|
if (!this.mFinishAfterSearch &&
|
|
(this.autoFill || this.completeDefaultIndex) &&
|
|
this.mLastKeyCode != KeyEvent.DOM_VK_BACK_SPACE &&
|
|
this.mLastKeyCode != KeyEvent.DOM_VK_DELETE) {
|
|
var indexToUse = aResults.defaultIndex;
|
|
if (aUseFirstMatchIfNoDefault && indexToUse == -1)
|
|
indexToUse = 0;
|
|
|
|
if (indexToUse != -1) {
|
|
var resultValue = this.getSessionValueAt(aSessionName, indexToUse);
|
|
var match = resultValue.toLowerCase();
|
|
var entry = this.currentSearchString.toLowerCase();
|
|
this.ignoreInputEvent = true;
|
|
if (match.indexOf(entry) == 0) {
|
|
var endPoint = this.value.length;
|
|
this.setTextValue(this.value + resultValue.substr(endPoint));
|
|
this.mInputElt.setSelectionRange(endPoint, this.value.length);
|
|
} else {
|
|
if (this.completeDefaultIndex) {
|
|
this.setTextValue(this.value + " >> " + resultValue);
|
|
this.mInputElt.setSelectionRange(entry.length, this.value.length);
|
|
} else {
|
|
var postIndex = resultValue.indexOf(this.value);
|
|
if (postIndex >= 0) {
|
|
var startPt = this.value.length;
|
|
this.setTextValue(this.value +
|
|
resultValue.substr(startPt+postIndex));
|
|
this.mInputElt.setSelectionRange(startPt, this.value.length);
|
|
}
|
|
}
|
|
}
|
|
this.mNeedToComplete = true;
|
|
this.ignoreInputEvent = false;
|
|
this.mDefaultMatchFilled = true;
|
|
}
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- ::::::::::::: popup and tree ::::::::::::: -->
|
|
|
|
<!-- -->
|
|
<method name="openPopup">
|
|
<body><![CDATA[
|
|
if (!this.mMenuOpen && this.focused &&
|
|
(this.getResultCount() >= this.minResultsForPopup ||
|
|
this.mFailureItems)) {
|
|
var w = this.boxObject.width;
|
|
if (w != this.popup.boxObject.width)
|
|
this.popup.setAttribute("width", w);
|
|
this.popup.showPopup(this, -1, -1, "popup", "bottomleft", "topleft");
|
|
this.mMenuOpen = true;
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- -->
|
|
<method name="closePopup">
|
|
<body><![CDATA[
|
|
if (this.popup && this.mMenuOpen) {
|
|
this.popup.hidePopup();
|
|
this.mMenuOpen = false;
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- -->
|
|
<method name="addResultElements">
|
|
<parameter name="aSession"/>
|
|
<parameter name="aResults"/>
|
|
<body><![CDATA[
|
|
var count = aResults.errorDescription ? 1 : aResults.matchCount;
|
|
if (this.focused && this.showPopup) {
|
|
var row = 0;
|
|
for (var name in this.mSessions) {
|
|
row += this.mLastRows[name];
|
|
if (name == aSession)
|
|
break;
|
|
}
|
|
this.view.updateResults(row, count - this.mLastRows[name]);
|
|
this.popup.adjustHeight();
|
|
}
|
|
this.mLastResults[aSession] = aResults;
|
|
this.mLastRows[aSession] = count;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- -->
|
|
<method name="clearResultElements">
|
|
<parameter name="aInvalidate"/>
|
|
<body><![CDATA[
|
|
for (var name in this.mSessions)
|
|
this.mLastRows[name] = 0;
|
|
this.view.clearResults();
|
|
if (aInvalidate)
|
|
this.popup.adjustHeight();
|
|
|
|
this.noMatch = true;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- -->
|
|
<method name="setTextValue">
|
|
<parameter name="aValue"/>
|
|
<body><![CDATA[
|
|
this.value = aValue;
|
|
|
|
// Completing a result should simulate the user typing the result,
|
|
// so fire an input event.
|
|
var evt = document.createEvent("UIEvents");
|
|
evt.initUIEvent("input", true, false, window, 0);
|
|
var oldIgnoreInput = this.ignoreInputEvent;
|
|
this.ignoreInputEvent = true;
|
|
this.dispatchEvent(evt);
|
|
this.ignoreInputEvent = oldIgnoreInput;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- -->
|
|
<method name="clearResultData">
|
|
<body><![CDATA[
|
|
for (var name in this.mSessions)
|
|
this.mLastResults[name] = null;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- ::::::::::::: miscellaneous ::::::::::::: -->
|
|
|
|
<!-- -->
|
|
<method name="ifSetAttribute">
|
|
<parameter name="aAttr"/>
|
|
<parameter name="aVal"/>
|
|
<body><![CDATA[
|
|
if (!this.hasAttribute(aAttr))
|
|
this.setAttribute(aAttr, aVal);
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- -->
|
|
<method name="clearTimer">
|
|
<body><![CDATA[
|
|
if (this.mAutoCompleteTimer) {
|
|
clearTimeout(this.mAutoCompleteTimer);
|
|
this.mAutoCompleteTimer = 0;
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- ::::::::::::: event dispatching ::::::::::::: -->
|
|
|
|
<method name="_fireEvent">
|
|
<parameter name="aEventType"/>
|
|
<parameter name="aEventParam"/>
|
|
<parameter name="aTriggeringEvent"/>
|
|
<body>
|
|
<![CDATA[
|
|
var noCancel = true;
|
|
// handle any xml attribute event handlers
|
|
var handler = this.getAttribute("on"+aEventType);
|
|
if (handler) {
|
|
var fn = new Function("eventParam", "domEvent", handler);
|
|
var returned = fn.apply(this, [aEventParam, aTriggeringEvent]);
|
|
if (returned == false)
|
|
noCancel = false;
|
|
}
|
|
|
|
return noCancel;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- =================== TREE VIEW =================== -->
|
|
|
|
<field name="view"><![CDATA[
|
|
({
|
|
mTextbox: this,
|
|
mTree: null,
|
|
mSelection: null,
|
|
mRowCount: 0,
|
|
|
|
clearResults: function()
|
|
{
|
|
var oldCount = this.mRowCount;
|
|
this.mRowCount = 0;
|
|
|
|
if (this.mTree) {
|
|
this.mTree.rowCountChanged(0, -oldCount);
|
|
this.mTree.scrollToRow(0);
|
|
}
|
|
},
|
|
|
|
updateResults: function(aRow, aCount)
|
|
{
|
|
this.mRowCount += aCount;
|
|
|
|
if (this.mTree)
|
|
this.mTree.rowCountChanged(aRow, aCount);
|
|
},
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// nsIAutoCompleteController interface
|
|
|
|
// this is the only method required by the treebody mouseup handler
|
|
handleEnter: function(aIsPopupSelection) {
|
|
this.mTextbox.onResultClick();
|
|
},
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// nsITreeView interface
|
|
|
|
get rowCount() {
|
|
return this.mRowCount;
|
|
},
|
|
|
|
get selection() {
|
|
return this.mSelection;
|
|
},
|
|
|
|
set selection(aVal) {
|
|
return this.mSelection = aVal;
|
|
},
|
|
|
|
setTree: function(aTree)
|
|
{
|
|
this.mTree = aTree;
|
|
},
|
|
|
|
getCellText: function(aRow, aCol)
|
|
{
|
|
for (var name in this.mTextbox.mSessions) {
|
|
if (aRow < this.mTextbox.mLastRows[name]) {
|
|
var result = this.mTextbox.mLastResults[name];
|
|
switch (aCol.id) {
|
|
case "treecolAutoCompleteValue":
|
|
return result.errorDescription || result.getLabelAt(aRow);
|
|
case "treecolAutoCompleteComment":
|
|
if (!result.errorDescription)
|
|
return result.getCommentAt(aRow);
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
aRow -= this.mTextbox.mLastRows[name];
|
|
}
|
|
return "";
|
|
},
|
|
|
|
getRowProperties: function(aIndex)
|
|
{
|
|
return "";
|
|
},
|
|
|
|
getCellProperties: function(aIndex, aCol)
|
|
{
|
|
// for the value column, append nsIAutoCompleteItem::className
|
|
// to the property list so that we can style this column
|
|
// using that property
|
|
if (aCol.id == "treecolAutoCompleteValue") {
|
|
for (var name in this.mTextbox.mSessions) {
|
|
if (aIndex < this.mTextbox.mLastRows[name]) {
|
|
var result = this.mTextbox.mLastResults[name];
|
|
if (result.errorDescription)
|
|
return "";
|
|
return result.getStyleAt(aIndex);
|
|
}
|
|
aIndex -= this.mTextbox.mLastRows[name];
|
|
}
|
|
}
|
|
return "";
|
|
},
|
|
|
|
getColumnProperties: function(aCol)
|
|
{
|
|
return "";
|
|
},
|
|
|
|
getImageSrc: function(aRow, aCol)
|
|
{
|
|
if (aCol.id == "treecolAutoCompleteValue") {
|
|
for (var name in this.mTextbox.mSessions) {
|
|
if (aRow < this.mTextbox.mLastRows[name]) {
|
|
var result = this.mTextbox.mLastResults[name];
|
|
if (result.errorDescription)
|
|
return "";
|
|
return result.getImageAt(aRow);
|
|
}
|
|
aRow -= this.mTextbox.mLastRows[name];
|
|
}
|
|
}
|
|
return "";
|
|
},
|
|
|
|
getParentIndex: function(aRowIndex) { },
|
|
hasNextSibling: function(aRowIndex, aAfterIndex) { },
|
|
getLevel: function(aIndex) {},
|
|
getProgressMode: function(aRow, aCol) {},
|
|
getCellValue: function(aRow, aCol) {},
|
|
isContainer: function(aIndex) {},
|
|
isContainerOpen: function(aIndex) {},
|
|
isContainerEmpty: function(aIndex) {},
|
|
isSeparator: function(aIndex) {},
|
|
isSorted: function() {},
|
|
toggleOpenState: function(aIndex) {},
|
|
selectionChanged: function() {},
|
|
cycleHeader: function(aCol) {},
|
|
cycleCell: function(aRow, aCol) {},
|
|
isEditable: function(aRow, aCol) {},
|
|
isSelectable: function(aRow, aCol) {},
|
|
setCellValue: function(aRow, aCol, aValue) {},
|
|
setCellText: function(aRow, aCol, aValue) {},
|
|
performAction: function(aAction) {},
|
|
performActionOnRow: function(aAction, aRow) {},
|
|
performActionOnCell: function(aAction, aRow, aCol) {}
|
|
});
|
|
]]></field>
|
|
|
|
</implementation>
|
|
|
|
<handlers>
|
|
<handler event="input"
|
|
action="if (!this.ignoreInputEvent) this.processInput();"/>
|
|
|
|
<handler event="keypress" phase="capturing"
|
|
action="return this.processKeyPress(event);"/>
|
|
|
|
<handler event="compositionstart" phase="capturing"
|
|
action="this.processStartComposition();"/>
|
|
|
|
<handler event="focus" phase="capturing"
|
|
action="this.userAction = 'typing';"/>
|
|
|
|
<handler event="blur" phase="capturing"
|
|
action="if ( !(this.ignoreBlurWhileSearching && this.isSearching) ) {this.userAction = 'none'; this.finishAutoComplete(false, false, event);}"/>
|
|
|
|
<handler event="mousedown" phase="capturing"
|
|
action="if ( !this.mMenuOpen ) this.finishAutoComplete(false, false, event);"/>
|
|
</handlers>
|
|
</binding>
|
|
|
|
<binding id="autocomplete-result-popup" extends="chrome://global/content/bindings/popup.xml#popup">
|
|
<resources>
|
|
<stylesheet src="chrome://communicator/content/autocomplete.css"/>
|
|
<stylesheet src="chrome://global/skin/autocomplete.css"/>
|
|
</resources>
|
|
|
|
<content ignorekeys="true" level="top">
|
|
<xul:tree anonid="tree" class="autocomplete-tree plain" flex="1">
|
|
<xul:treecols anonid="treecols">
|
|
<xul:treecol class="autocomplete-treecol" id="treecolAutoCompleteValue" flex="2"/>
|
|
<xul:treecol class="autocomplete-treecol" id="treecolAutoCompleteComment" flex="1" hidden="true"/>
|
|
</xul:treecols>
|
|
<xul:treechildren anonid="treebody" class="autocomplete-treebody"/>
|
|
</xul:tree>
|
|
</content>
|
|
|
|
<implementation implements="nsIAutoCompletePopup">
|
|
<constructor><![CDATA[
|
|
if (this.textbox && this.textbox.view)
|
|
this.initialize();
|
|
]]></constructor>
|
|
|
|
<destructor><![CDATA[
|
|
if (this.view)
|
|
this.tree.view = null;
|
|
]]></destructor>
|
|
|
|
<field name="textbox">
|
|
document.getBindingParent(this);
|
|
</field>
|
|
|
|
<field name="tree">
|
|
document.getAnonymousElementByAttribute(this, "anonid", "tree");
|
|
</field>
|
|
|
|
<field name="treecols">
|
|
document.getAnonymousElementByAttribute(this, "anonid", "treecols");
|
|
</field>
|
|
|
|
<field name="treebody">
|
|
document.getAnonymousElementByAttribute(this, "anonid", "treebody");
|
|
</field>
|
|
|
|
<field name="view">
|
|
null
|
|
</field>
|
|
|
|
<!-- Setting tree.view doesn't always immediately create a selection,
|
|
so we ensure the selection by asking the tree for the view. Note:
|
|
this.view.selection is quicker if we know the selection exists. -->
|
|
<property name="selection" onget="return this.tree.view.selection;"/>
|
|
|
|
<property name="pageCount"
|
|
onget="return this.tree.treeBoxObject.getPageLength();"/>
|
|
|
|
<field name="maxRows">0</field>
|
|
<field name="mLastRows">0</field>
|
|
|
|
<method name="initialize">
|
|
<body><![CDATA[
|
|
this.showCommentColumn = this.textbox.showCommentColumn;
|
|
this.tree.view = this.textbox.view;
|
|
this.view = this.textbox.view;
|
|
this.maxRows = this.textbox.maxRows;
|
|
]]></body>
|
|
</method>
|
|
|
|
<property name="showCommentColumn"
|
|
onget="return !this.treecols.lastChild.hidden;"
|
|
onset="this.treecols.lastChild.hidden = !val; return val;"/>
|
|
|
|
<method name="adjustHeight">
|
|
<body><![CDATA[
|
|
// detect the desired height of the tree
|
|
var bx = this.tree.treeBoxObject;
|
|
var view = this.view;
|
|
var rows = this.maxRows || 6;
|
|
if (!view.rowCount || (rows && view.rowCount < rows))
|
|
rows = view.rowCount;
|
|
|
|
var height = rows * bx.rowHeight;
|
|
|
|
if (height == 0)
|
|
this.tree.setAttribute("collapsed", "true");
|
|
else {
|
|
if (this.tree.hasAttribute("collapsed"))
|
|
this.tree.removeAttribute("collapsed");
|
|
this.tree.setAttribute("height", height);
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="clearSelection">
|
|
<body>
|
|
this.selection.clearSelection();
|
|
</body>
|
|
</method>
|
|
|
|
<method name="getNextIndex">
|
|
<parameter name="aReverse"/>
|
|
<parameter name="aPage"/>
|
|
<parameter name="aIndex"/>
|
|
<parameter name="aMaxRow"/>
|
|
<body><![CDATA[
|
|
if (aMaxRow < 0)
|
|
return -1;
|
|
|
|
if (aIndex == -1)
|
|
return aReverse ? aMaxRow : 0;
|
|
if (aIndex == (aReverse ? 0 : aMaxRow))
|
|
return -1;
|
|
|
|
var amount = aPage ? this.pageCount - 1 : 1;
|
|
aIndex = aReverse ? aIndex - amount : aIndex + amount;
|
|
if (aIndex > aMaxRow)
|
|
return aMaxRow;
|
|
if (aIndex < 0)
|
|
return 0;
|
|
return aIndex;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- =================== nsIAutoCompletePopup =================== -->
|
|
|
|
<field name="input">
|
|
null
|
|
</field>
|
|
|
|
<!-- This property is meant to be overriden by bindings extending
|
|
this one. When the user selects an item from the list by
|
|
hitting enter or clicking, this method can set the value
|
|
of the textbox to a different value if it wants to. -->
|
|
<property name="overrideValue" readonly="true" onget="return null;"/>
|
|
|
|
<property name="selectedIndex">
|
|
<getter>
|
|
if (!this.view || !this.selection.count)
|
|
return -1;
|
|
var start = {}, end = {};
|
|
this.view.selection.getRangeAt(0, start, end);
|
|
return start.value;
|
|
</getter>
|
|
<setter>
|
|
if (this.view) {
|
|
this.selection.select(val);
|
|
if (val >= 0) {
|
|
this.view.selection.currentIndex = -1;
|
|
this.tree.treeBoxObject.ensureRowIsVisible(val);
|
|
}
|
|
}
|
|
return val;
|
|
</setter>
|
|
</property>
|
|
|
|
<property name="popupOpen" onget="return !!this.input;" readonly="true"/>
|
|
|
|
<method name="openAutocompletePopup">
|
|
<parameter name="aInput"/>
|
|
<parameter name="aElement"/>
|
|
<body><![CDATA[
|
|
if (!this.input) {
|
|
this.tree.view = aInput.controller;
|
|
this.view = this.tree.view;
|
|
this.showCommentColumn = aInput.showCommentColumn;
|
|
this.maxRows = aInput.maxRows;
|
|
this.invalidate();
|
|
|
|
var viewer = aElement
|
|
.ownerDocument
|
|
.defaultView
|
|
.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
|
.getInterface(Components.interfaces.nsIWebNavigation)
|
|
.QueryInterface(Components.interfaces.nsIDocShell)
|
|
.contentViewer;
|
|
var rect = aElement.getBoundingClientRect();
|
|
var width = Math.round((rect.right - rect.left) * viewer.fullZoom);
|
|
this.setAttribute("width", width > 100 ? width : 100);
|
|
// Adjust the direction (which is not inherited) of the autocomplete
|
|
// popup list, based on the textbox direction. (Bug 707039)
|
|
this.style.direction = aElement.ownerDocument.defaultView
|
|
.getComputedStyle(aElement)
|
|
.direction;
|
|
this.popupBoxObject.setConsumeRollupEvent(aInput.consumeRollupEvent
|
|
? PopupBoxObject.ROLLUP_CONSUME
|
|
: PopupBoxObject.ROLLUP_NO_CONSUME);
|
|
this.openPopup(aElement, "after_start", 0, 0, false, false);
|
|
if (this.state != "closed")
|
|
this.input = aInput;
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="closePopup">
|
|
<body>
|
|
this.hidePopup();
|
|
</body>
|
|
</method>
|
|
|
|
<method name="invalidate">
|
|
<body>
|
|
if (this.view)
|
|
this.adjustHeight();
|
|
this.tree.treeBoxObject.invalidate();
|
|
</body>
|
|
</method>
|
|
|
|
<method name="selectBy">
|
|
<parameter name="aReverse"/>
|
|
<parameter name="aPage"/>
|
|
<body><![CDATA[
|
|
try {
|
|
return this.selectedIndex = this.getNextIndex(aReverse, aPage, this.selectedIndex, this.view.rowCount - 1);
|
|
} catch (ex) {
|
|
// do nothing - occasionally timer-related js errors happen here
|
|
// e.g. "this.selectedIndex has no properties", when you type fast and hit a
|
|
// navigation key before this popup has opened
|
|
return -1;
|
|
}
|
|
]]></body>
|
|
</method>
|
|
</implementation>
|
|
|
|
<handlers>
|
|
<handler event="popupshowing">
|
|
if (this.textbox)
|
|
this.textbox.mMenuOpen = true;
|
|
</handler>
|
|
|
|
<handler event="popuphiding">
|
|
if (this.textbox)
|
|
this.textbox.mMenuOpen = false;
|
|
this.clearSelection();
|
|
this.input = null;
|
|
</handler>
|
|
</handlers>
|
|
</binding>
|
|
|
|
<binding id="autocomplete-treebody">
|
|
<implementation>
|
|
<field name="popup">document.getBindingParent(this);</field>
|
|
|
|
<field name="mLastMoveTime">Date.now()</field>
|
|
</implementation>
|
|
|
|
<handlers>
|
|
<handler event="mouseout" action="this.popup.selectedIndex = -1;"/>
|
|
|
|
<handler event="mouseup"><![CDATA[
|
|
var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY);
|
|
if (rc != -1) {
|
|
this.popup.selectedIndex = rc;
|
|
this.popup.view.handleEnter(true);
|
|
}
|
|
]]></handler>
|
|
|
|
<handler event="mousemove"><![CDATA[
|
|
if (Date.now() - this.mLastMoveTime > 30) {
|
|
var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY);
|
|
if (rc != -1 && rc != this.popup.selectedIndex)
|
|
this.popup.selectedIndex = rc;
|
|
this.mLastMoveTime = Date.now();
|
|
}
|
|
]]></handler>
|
|
</handlers>
|
|
</binding>
|
|
|
|
<binding id="autocomplete-history-popup"
|
|
extends="chrome://global/content/bindings/popup.xml#popup-scrollbars">
|
|
<resources>
|
|
<stylesheet src="chrome://communicator/content/autocomplete.css"/>
|
|
<stylesheet src="chrome://global/skin/autocomplete.css"/>
|
|
</resources>
|
|
|
|
<implementation>
|
|
<method name="removeOpenAttribute">
|
|
<parameter name="parentNode"/>
|
|
<body><![CDATA[
|
|
parentNode.removeAttribute("open");
|
|
]]></body>
|
|
</method>
|
|
</implementation>
|
|
|
|
<handlers>
|
|
<handler event="popuphiding"><![CDATA[
|
|
setTimeout(this.removeOpenAttribute, 0, this.parentNode);
|
|
]]></handler>
|
|
</handlers>
|
|
</binding>
|
|
|
|
<binding id="history-dropmarker" extends="chrome://global/content/bindings/general.xml#dropmarker">
|
|
|
|
<implementation>
|
|
<method name="showPopup">
|
|
<body><![CDATA[
|
|
var textbox = document.getBindingParent(this);
|
|
var kids = textbox.getElementsByClassName("autocomplete-history-popup");
|
|
if (kids.item(0) && textbox.getAttribute("open") != "true") { // Open history popup
|
|
var w = textbox.boxObject.width;
|
|
if (w != kids[0].boxObject.width)
|
|
kids[0].width = w;
|
|
kids[0].showPopup(textbox, -1, -1, "popup", "bottomleft", "topleft");
|
|
textbox.setAttribute("open", "true");
|
|
}
|
|
]]></body>
|
|
</method>
|
|
</implementation>
|
|
|
|
<handlers>
|
|
<handler event="mousedown"><![CDATA[
|
|
this.showPopup();
|
|
]]></handler>
|
|
</handlers>
|
|
</binding>
|
|
|
|
</bindings>
|