xpfe/components/autocomplete/resources/content/autocomplete.xml

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

mercurial