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