Back to index

lightning-sunbird  0.9+nobinonly
nsAutoCompleteController.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla Communicator client code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Joe Hewitt <hewitt@netscape.com> (Original Author)
00024  *   Dean Tessman <dean_tessman@hotmail.com>
00025  *   Johnny Stenback <jst@mozilla.jstenback.com>
00026  *   Masayuki Nakano <masayuki@d-toybox.com>
00027  *
00028  * Alternatively, the contents of this file may be used under the terms of
00029  * either the GNU General Public License Version 2 or later (the "GPL"), or
00030  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00031  * in which case the provisions of the GPL or the LGPL are applicable instead
00032  * of those above. If you wish to allow use of your version of this file only
00033  * under the terms of either the GPL or the LGPL, and not to allow others to
00034  * use your version of this file under the terms of the MPL, indicate your
00035  * decision by deleting the provisions above and replace them with the notice
00036  * and other provisions required by the GPL or the LGPL. If you do not delete
00037  * the provisions above, a recipient may use your version of this file under
00038  * the terms of any one of the MPL, the GPL or the LGPL.
00039  *
00040  * ***** END LICENSE BLOCK ***** */
00041 
00042 #include "nsAutoCompleteController.h"
00043 
00044 #include "nsIServiceManager.h"
00045 #include "nsIDOMNode.h"
00046 #include "nsIDOMElement.h"
00047 #include "nsIDOMDocument.h"
00048 #include "nsIDocument.h"
00049 #include "nsIContent.h"
00050 #include "nsIFrame.h"
00051 #include "nsIView.h"
00052 #include "nsIPresShell.h"
00053 #include "nsIAtomService.h"
00054 #include "nsReadableUtils.h"
00055 #include "nsUnicharUtils.h"
00056 #include "nsITreeColumns.h"
00057 #include "nsNetCID.h"
00058 #include "nsIIOService.h"
00059 #include "nsIObserverService.h"
00060 
00061 static const char *kAutoCompleteSearchCID = "@mozilla.org/autocomplete/search;1?name=";
00062 
00063 NS_IMPL_ISUPPORTS6(nsAutoCompleteController, nsIAutoCompleteController,
00064                                              nsIAutoCompleteController_MOZILLA_1_8_BRANCH,
00065                                              nsIAutoCompleteObserver,
00066                                              nsIRollupListener,
00067                                              nsITimerCallback,
00068                                              nsITreeView)
00069 
00070 nsAutoCompleteController::nsAutoCompleteController() :
00071   mEnterAfterSearch(PR_FALSE),
00072   mDefaultIndexCompleted(PR_FALSE),
00073   mBackspaced(PR_FALSE),
00074   mPopupClosedByCompositionStart(PR_FALSE),
00075   mIsIMEComposing(PR_FALSE),
00076   mIgnoreHandleText(PR_FALSE),
00077   mIsOpen(PR_FALSE),
00078   mSearchStatus(0),
00079   mRowCount(0),
00080   mSearchesOngoing(0)
00081 {
00082   mSearches = do_CreateInstance("@mozilla.org/supports-array;1");
00083   mResults = do_CreateInstance("@mozilla.org/supports-array;1");
00084 }
00085 
00086 nsAutoCompleteController::~nsAutoCompleteController()
00087 {
00088   SetInput(nsnull);
00089 }
00090 
00093 
00094 NS_IMETHODIMP
00095 nsAutoCompleteController::GetSearchStatus(PRUint16 *aSearchStatus)
00096 {
00097   *aSearchStatus = mSearchStatus;
00098   return NS_OK;
00099 }
00100 
00101 NS_IMETHODIMP
00102 nsAutoCompleteController::GetMatchCount(PRUint32 *aMatchCount)
00103 {
00104   *aMatchCount = mRowCount;
00105   return NS_OK;
00106 }
00107 
00108 NS_IMETHODIMP
00109 nsAutoCompleteController::GetInput(nsIAutoCompleteInput **aInput)
00110 {
00111   *aInput = mInput;
00112   NS_IF_ADDREF(*aInput);
00113   return NS_OK;
00114 }
00115 
00116 NS_IMETHODIMP
00117 nsAutoCompleteController::SetInput(nsIAutoCompleteInput *aInput)
00118 {
00119   // Don't do anything if the input isn't changing.
00120   if (mInput == aInput)
00121     return NS_OK;
00122 
00123   // Clear out the current search context
00124   if (mInput) {
00125     ClearSearchTimer();
00126     ClearResults();
00127     if (mIsOpen) {
00128       ClosePopup();
00129     }
00130     mSearches->Clear();
00131   }
00132     
00133   mInput = aInput;
00134 
00135   // Nothing more to do if the input was just being set to null.
00136   if (!aInput)
00137     return NS_OK;
00138 
00139   nsAutoString newValue;
00140   mInput->GetTextValue(newValue);
00141   
00142   // Reset all search state members to default values
00143   mSearchString = newValue;
00144   mEnterAfterSearch = PR_FALSE;
00145   mDefaultIndexCompleted = PR_FALSE;
00146   mBackspaced = PR_FALSE;
00147   mSearchStatus = nsIAutoCompleteController::STATUS_NONE;
00148   mRowCount = 0;
00149   mSearchesOngoing = 0;
00150   
00151   // Initialize our list of search objects
00152   PRUint32 searchCount;
00153   mInput->GetSearchCount(&searchCount);
00154   mResults->SizeTo(searchCount);
00155   mSearches->SizeTo(searchCount);
00156   
00157   const char *searchCID = kAutoCompleteSearchCID;
00158 
00159   for (PRUint32 i = 0; i < searchCount; ++i) {
00160     // Use the search name to create the contract id string for the search service
00161     nsCAutoString searchName;
00162     mInput->GetSearchAt(i, searchName);
00163     nsCAutoString cid(searchCID);
00164     cid.Append(searchName);
00165     
00166     // Use the created cid to get a pointer to the search service and store it for later
00167     nsCOMPtr<nsIAutoCompleteSearch> search = do_GetService(cid.get());
00168     if (search)
00169       mSearches->AppendElement(search);
00170   }
00171 
00172   return NS_OK;
00173 }
00174 
00175 NS_IMETHODIMP
00176 nsAutoCompleteController::StartSearch(const nsAString &aSearchString)
00177 { 
00178   mSearchString = aSearchString;
00179   StartSearchTimer();
00180 
00181   return NS_OK;
00182 }
00183 
00184 NS_IMETHODIMP
00185 nsAutoCompleteController::HandleText(PRBool aIgnoreSelection)
00186 {
00187   if (!mInput) {
00188     // Stop current search in case it's async.
00189     StopSearch();
00190     // Stop the queued up search on a timer
00191     ClearSearchTimer();
00192     // Note: if now is after blur and IME end composition,
00193     // check mInput before calling.
00194     // See https://bugzilla.mozilla.org/show_bug.cgi?id=193544#c31
00195     NS_ERROR("Called before attaching to the control or after detaching from the control");
00196     return NS_OK;
00197   }
00198 
00199   nsAutoString newValue;
00200   mInput->GetTextValue(newValue);
00201 
00202   // Note: the events occur in the following order when IME is used.
00203   // 1. composition start event(HandleStartComposition)
00204   // 2. composition end event(HandleEndComposition)
00205   // 3. input event(HandleText)
00206   // Note that the input event occurs if IME composition is cancelled, as well.
00207   // In HandleEndComposition, we are processing the popup properly.
00208   // Therefore, the input event after composition end event should do nothing.
00209   // (E.g., calling StopSearch(), ClearSearchTimer() and ClosePopup().)
00210   // If it is not, popup is always closed after composition end.
00211   if (mIgnoreHandleText) {
00212     mIgnoreHandleText = PR_FALSE;
00213     if (newValue.Equals(mSearchString))
00214       return NS_OK;
00215     NS_ERROR("Now is after composition end event. But the value was changed.");
00216   }
00217 
00218   // Stop current search in case it's async.
00219   StopSearch();
00220   // Stop the queued up search on a timer
00221   ClearSearchTimer();
00222 
00223   PRBool disabled;
00224   mInput->GetDisableAutoComplete(&disabled);
00225   NS_ENSURE_TRUE(!disabled, NS_OK);
00226 
00227   // Don't search again if the new string is the same as the last search
00228   if (newValue.Length() > 0 && newValue.Equals(mSearchString))
00229     return NS_OK;
00230 
00231   // Determine if the user has removed text from the end (probably by backspacing)
00232   if (newValue.Length() < mSearchString.Length() &&
00233       Substring(mSearchString, 0, newValue.Length()).Equals(newValue))
00234   {
00235     // We need to throw away previous results so we don't try to search through them again
00236     ClearResults();
00237     mBackspaced = PR_TRUE;
00238   } else
00239     mBackspaced = PR_FALSE;
00240 
00241   if (mRowCount == 0)
00242     // XXX Handle the case where we have no results because of an ignored prefix. 
00243     // This is just a hack. I have no idea what I'm doing. Hewitt, fix this the right
00244     // way when you get a chance. -dwh
00245     ClearResults();
00246 
00247   mSearchString = newValue;
00248 
00249   // Don't search if the value is empty
00250   if (newValue.Length() == 0) {
00251     ClosePopup();
00252     return NS_OK;
00253   }
00254 
00255   if (aIgnoreSelection) {
00256     StartSearchTimer();
00257   } else {
00258     // Kick off the search only if the cursor is at the end of the textbox
00259   PRInt32 selectionStart;
00260   mInput->GetSelectionStart(&selectionStart);
00261   PRInt32 selectionEnd;
00262   mInput->GetSelectionEnd(&selectionEnd);
00263 
00264   if (selectionStart == selectionEnd && selectionStart == (PRInt32) mSearchString.Length())
00265     StartSearchTimer();
00266   }
00267 
00268   return NS_OK;
00269 }
00270 
00271 NS_IMETHODIMP
00272 nsAutoCompleteController::HandleEnter(PRBool *_retval)
00273 {
00274   *_retval = PR_FALSE;
00275   if (!mInput)
00276     return NS_OK;
00277 
00278   // allow the event through unless there is something selected in the popup
00279   mInput->GetPopupOpen(_retval);
00280   if (*_retval) {
00281     nsCOMPtr<nsIAutoCompletePopup> popup;
00282     mInput->GetPopup(getter_AddRefs(popup));
00283 
00284     if (popup) {
00285       PRInt32 selectedIndex;
00286       popup->GetSelectedIndex(&selectedIndex);
00287       *_retval = selectedIndex >= 0;
00288     }
00289   }
00290   
00291   ClearSearchTimer();
00292   EnterMatch();
00293   
00294   return NS_OK;
00295 }
00296 
00297 NS_IMETHODIMP
00298 nsAutoCompleteController::HandleEscape(PRBool *_retval)
00299 {
00300   *_retval = PR_FALSE;
00301   if (!mInput)
00302     return NS_OK;
00303 
00304   // allow the event through if the popup is closed
00305   mInput->GetPopupOpen(_retval);
00306   
00307   ClearSearchTimer();
00308   ClearResults();
00309   RevertTextValue();
00310   ClosePopup();
00311 
00312   return NS_OK;
00313 }
00314 
00315 NS_IMETHODIMP
00316 nsAutoCompleteController::HandleStartComposition()
00317 {
00318   NS_ENSURE_TRUE(!mIsIMEComposing, NS_OK);
00319 
00320   mPopupClosedByCompositionStart = PR_FALSE;
00321   mIsIMEComposing = PR_TRUE;
00322 
00323   if (!mInput)
00324     return NS_OK;
00325 
00326   PRBool disabled;
00327   mInput->GetDisableAutoComplete(&disabled);
00328   if (disabled)
00329     return NS_OK;
00330 
00331   StopSearch();
00332   ClearSearchTimer();
00333 
00334   PRBool isOpen;
00335   mInput->GetPopupOpen(&isOpen);
00336   if (isOpen)
00337     ClosePopup();
00338   mPopupClosedByCompositionStart = isOpen;
00339   return NS_OK;
00340 }
00341 
00342 NS_IMETHODIMP
00343 nsAutoCompleteController::HandleEndComposition()
00344 {
00345   NS_ENSURE_TRUE(mIsIMEComposing, NS_OK);
00346 
00347   mIsIMEComposing = PR_FALSE;
00348   PRBool forceOpenPopup = mPopupClosedByCompositionStart;
00349   mPopupClosedByCompositionStart = PR_FALSE;
00350 
00351   if (!mInput)
00352     return NS_OK;
00353 
00354   nsAutoString value;
00355   mInput->GetTextValue(value);
00356   SetSearchString(EmptyString());
00357   if (!value.IsEmpty()) {
00358     // Show the popup with a filtered result set
00359     HandleText(PR_TRUE);
00360   } else if (forceOpenPopup) {
00361     PRBool cancel;
00362     HandleKeyNavigation(nsIAutoCompleteController::KEY_DOWN, &cancel);
00363   }
00364   // On here, |value| and |mSearchString| are same. Therefore, next HandleText should be
00365   // ignored. Because there are no reason to research.
00366   mIgnoreHandleText = PR_TRUE;
00367 
00368   return NS_OK;
00369 }
00370 
00371 NS_IMETHODIMP
00372 nsAutoCompleteController::HandleTab()
00373 {
00374   PRBool cancel;
00375   return HandleEnter(&cancel);
00376 }
00377 
00378 NS_IMETHODIMP
00379 nsAutoCompleteController::HandleKeyNavigation(PRUint16 aKey, PRBool *_retval)
00380 {
00381   // By default, don't cancel the event
00382   *_retval = PR_FALSE;
00383 
00384   if (!mInput) {
00385     // Note: if now is after blur and IME end composition,
00386     // check mInput before calling.
00387     // See https://bugzilla.mozilla.org/show_bug.cgi?id=193544#c31
00388     NS_ERROR("Called before attaching to the control or after detaching from the control");
00389     return NS_OK;
00390   }
00391 
00392   nsCOMPtr<nsIAutoCompletePopup> popup;
00393   mInput->GetPopup(getter_AddRefs(popup));
00394   NS_ENSURE_TRUE(popup != nsnull, NS_ERROR_FAILURE);
00395 
00396   PRBool disabled;
00397   mInput->GetDisableAutoComplete(&disabled);
00398   NS_ENSURE_TRUE(!disabled, NS_OK);
00399 
00400   if (aKey == nsIAutoCompleteController::KEY_UP ||
00401       aKey == nsIAutoCompleteController::KEY_DOWN || 
00402       aKey == nsIAutoCompleteController::KEY_PAGE_UP || 
00403       aKey == nsIAutoCompleteController::KEY_PAGE_DOWN)
00404   {
00405     // Prevent the input from handling up/down events, as it may move
00406     // the cursor to home/end on some systems
00407     *_retval = PR_TRUE;
00408     
00409     PRBool isOpen;
00410     mInput->GetPopupOpen(&isOpen);
00411     if (isOpen) {
00412       PRBool reverse = aKey == nsIAutoCompleteController::KEY_UP ||
00413                       aKey == nsIAutoCompleteController::KEY_PAGE_UP ? PR_TRUE : PR_FALSE;
00414       PRBool page = aKey == nsIAutoCompleteController::KEY_PAGE_UP ||
00415                     aKey == nsIAutoCompleteController::KEY_PAGE_DOWN ? PR_TRUE : PR_FALSE;
00416       
00417       // Fill in the value of the textbox with whatever is selected in the popup
00418       // if the completeSelectedIndex attribute is set.  We check this before
00419       // calling SelectBy since that can null out mInput in some cases.
00420       PRBool completeSelection;
00421       mInput->GetCompleteSelectedIndex(&completeSelection);
00422 
00423       // Instruct the result view to scroll by the given amount and direction
00424       popup->SelectBy(reverse, page);
00425 
00426       if (completeSelection)
00427       {
00428         PRInt32 selectedIndex;
00429         popup->GetSelectedIndex(&selectedIndex);
00430         if (selectedIndex >= 0) {
00431           //  A result is selected, so fill in its value
00432           nsAutoString value;
00433           if (NS_SUCCEEDED(GetResultValueAt(selectedIndex, PR_TRUE, value))) {
00434             mInput->SetTextValue(value);
00435             mInput->SelectTextRange(value.Length(), value.Length());
00436           }
00437         } else {
00438           // Nothing is selected, so fill in the last typed value
00439           mInput->SetTextValue(mSearchString);
00440           mInput->SelectTextRange(mSearchString.Length(), mSearchString.Length());
00441         }
00442       }
00443     } else {
00444       // Open the popup if there has been a previous search, or else kick off a new search
00445       PRUint32 resultCount;
00446       mResults->Count(&resultCount);
00447       if (resultCount) {
00448         if (mRowCount) {
00449           OpenPopup();
00450         }
00451       } else
00452         StartSearchTimer();
00453     }    
00454   } else if (   aKey == nsIAutoCompleteController::KEY_LEFT 
00455              || aKey == nsIAutoCompleteController::KEY_RIGHT 
00456 #ifndef XP_MACOSX
00457              || aKey == nsIAutoCompleteController::KEY_HOME
00458 #endif
00459             )
00460   {
00461     // The user hit a text-navigation key.
00462     PRBool isOpen;
00463     mInput->GetPopupOpen(&isOpen);
00464     if (isOpen) {
00465       PRInt32 selectedIndex;
00466       popup->GetSelectedIndex(&selectedIndex);
00467       if (selectedIndex >= 0) {
00468         // The pop-up is open and has a selection, take its value
00469         nsAutoString value;
00470         if (NS_SUCCEEDED(GetResultValueAt(selectedIndex, PR_TRUE, value))) {
00471           mInput->SetTextValue(value);
00472           mInput->SelectTextRange(value.Length(), value.Length());
00473         }
00474       }
00475       // Close the pop-up even if nothing was selected
00476       ClearSearchTimer();
00477       ClosePopup();
00478     }
00479     // Update last-searched string to the current input, since the input may
00480     // have changed.  Without this, subsequent backspaces look like text
00481     // additions, not text deletions.
00482     nsAutoString value;
00483     mInput->GetTextValue(value);
00484     mSearchString = value;
00485   }
00486   
00487   return NS_OK;
00488 }
00489 
00490 NS_IMETHODIMP
00491 nsAutoCompleteController::HandleDelete(PRBool *_retval)
00492 {
00493   *_retval = PR_FALSE;
00494   if (!mInput)
00495     return NS_OK;
00496 
00497   PRBool isOpen = PR_FALSE;
00498   mInput->GetPopupOpen(&isOpen);
00499   if (!isOpen || mRowCount <= 0) {
00500     // Nothing left to delete, proceed as normal
00501     HandleText(PR_FALSE);
00502     return NS_OK;
00503   }
00504   
00505   nsCOMPtr<nsIAutoCompletePopup> popup;
00506   mInput->GetPopup(getter_AddRefs(popup));
00507 
00508   PRInt32 index, searchIndex, rowIndex;
00509   popup->GetSelectedIndex(&index);
00510   RowIndexToSearch(index, &searchIndex, &rowIndex);
00511   NS_ENSURE_TRUE(searchIndex >= 0 && rowIndex >= 0, NS_ERROR_FAILURE);
00512 
00513   nsCOMPtr<nsIAutoCompleteResult> result;
00514   mResults->GetElementAt(searchIndex, getter_AddRefs(result));
00515   NS_ENSURE_TRUE(result, NS_ERROR_FAILURE);
00516 
00517   nsAutoString search;
00518   mInput->GetSearchParam(search);
00519 
00520   // Clear the row in our result and in the DB.
00521   result->RemoveValueAt(rowIndex, PR_TRUE);
00522   --mRowCount;
00523 
00524   // Unselect the current item.
00525   popup->SetSelectedIndex(-1);
00526 
00527   // Tell the tree that the row count changed. 
00528   if (mTree)
00529     mTree->RowCountChanged(mRowCount, -1);
00530 
00531   // Adjust index, if needed.
00532   if (index >= (PRInt32)mRowCount)
00533     index = mRowCount - 1;
00534 
00535   if (mRowCount > 0) {
00536     // There are still rows in the popup, select the current index again.
00537     popup->SetSelectedIndex(index);
00538 
00539     // Complete to the new current value.
00540     nsAutoString value;
00541     if (NS_SUCCEEDED(GetResultValueAt(index, PR_TRUE, value))) {
00542       CompleteValue(value, PR_FALSE);
00543 
00544       // Make sure we cancel the event that triggerd this call.
00545       *_retval = PR_TRUE;
00546     }
00547 
00548     // Invalidate the popup.
00549     popup->Invalidate();
00550   } else {
00551     // Nothing left in the popup, clear any pending search timers and
00552     // close the popup.
00553     ClearSearchTimer();
00554     ClosePopup();
00555   }
00556 
00557   return NS_OK;
00558 }
00559 
00560 NS_IMETHODIMP
00561 nsAutoCompleteController::GetValueAt(PRInt32 aIndex, nsAString & _retval)
00562 {
00563   GetResultValueAt(aIndex, PR_FALSE, _retval);
00564   
00565   return NS_OK;
00566 }
00567 
00568 NS_IMETHODIMP
00569 nsAutoCompleteController::GetCommentAt(PRInt32 aIndex, nsAString & _retval)
00570 {
00571   PRInt32 searchIndex;
00572   PRInt32 rowIndex;
00573   RowIndexToSearch(aIndex, &searchIndex, &rowIndex);
00574   NS_ENSURE_TRUE(searchIndex >= 0 && rowIndex >= 0, NS_ERROR_FAILURE);
00575   
00576   nsCOMPtr<nsIAutoCompleteResult> result;
00577   mResults->GetElementAt(searchIndex, getter_AddRefs(result));
00578   NS_ENSURE_TRUE(result, NS_ERROR_FAILURE);
00579 
00580   result->GetCommentAt(rowIndex, _retval);
00581 
00582   return NS_OK;
00583 }
00584 
00585 NS_IMETHODIMP
00586 nsAutoCompleteController::GetStyleAt(PRInt32 aIndex, nsAString & _retval)
00587 {
00588   PRInt32 searchIndex;
00589   PRInt32 rowIndex;
00590   RowIndexToSearch(aIndex, &searchIndex, &rowIndex);
00591   NS_ENSURE_TRUE(searchIndex >= 0 && rowIndex >= 0, NS_ERROR_FAILURE);
00592   
00593   nsCOMPtr<nsIAutoCompleteResult> result;
00594   mResults->GetElementAt(searchIndex, getter_AddRefs(result));
00595   NS_ENSURE_TRUE(result, NS_ERROR_FAILURE);
00596 
00597   result->GetStyleAt(rowIndex, _retval);
00598 
00599   return NS_OK;
00600 }
00601 
00602 NS_IMETHODIMP
00603 nsAutoCompleteController::SetSearchString(const nsAString &aSearchString)
00604 { 
00605   mSearchString = aSearchString;
00606   
00607   return NS_OK;
00608 }
00609 
00610 NS_IMETHODIMP
00611 nsAutoCompleteController::AttachRollupListener()
00612 {
00613   nsIWidget* widget = GetPopupWidget();
00614   NS_ENSURE_TRUE(widget, NS_ERROR_FAILURE);
00615   NS_ASSERTION(mInput, "mInput must not be null.");
00616   PRBool consumeRollupEvent = PR_FALSE;
00617   nsCOMPtr<nsIAutoCompleteInput_MOZILLA_1_8_BRANCH> input =
00618     do_QueryInterface(mInput);
00619   NS_ASSERTION(input, "mInput must have nsIAutoCompleteInput_MOZILLA_1_8_BRANCH interface.");
00620   input->GetConsumeRollupEvent(&consumeRollupEvent);
00621   return widget->CaptureRollupEvents((nsIRollupListener*)this,
00622                                      PR_TRUE, consumeRollupEvent);
00623 }
00624 
00625 NS_IMETHODIMP
00626 nsAutoCompleteController::DetachRollupListener()
00627 {
00628   nsIWidget* widget = GetPopupWidget();
00629   NS_ENSURE_TRUE(widget, NS_ERROR_FAILURE);
00630   return widget->CaptureRollupEvents((nsIRollupListener*)this,
00631                                      PR_FALSE, PR_FALSE);
00632 }
00633 
00636 
00637 NS_IMETHODIMP
00638 nsAutoCompleteController::OnSearchResult(nsIAutoCompleteSearch *aSearch, nsIAutoCompleteResult* aResult)
00639 {
00640   // look up the index of the search which is returning
00641   PRUint32 count;
00642   mSearches->Count(&count);
00643   for (PRUint32 i = 0; i < count; ++i) {
00644     nsCOMPtr<nsIAutoCompleteSearch> search;
00645     mSearches->GetElementAt(i, getter_AddRefs(search));
00646     if (search == aSearch) {
00647       ProcessResult(i, aResult);
00648     }
00649   }
00650   
00651   return NS_OK;
00652 }
00653 
00656 
00657 NS_IMETHODIMP
00658 nsAutoCompleteController::Rollup()
00659 {
00660   ClearSearchTimer();
00661   ClearResults();
00662   ClosePopup();
00663   return NS_OK;
00664 }
00665 
00666 NS_IMETHODIMP
00667 nsAutoCompleteController::ShouldRollupOnMouseWheelEvent(PRBool *aShouldRollup)
00668 {
00669   *aShouldRollup = PR_TRUE;
00670   return NS_OK;
00671 }
00672 
00673 NS_IMETHODIMP
00674 nsAutoCompleteController::ShouldRollupOnMouseActivate(PRBool *aShouldRollup)
00675 {
00676   *aShouldRollup = PR_FALSE;
00677   return NS_OK;
00678 }
00679 
00682 
00683 NS_IMETHODIMP
00684 nsAutoCompleteController::Notify(nsITimer *timer)
00685 {
00686   mTimer = nsnull;
00687   StartSearch();
00688   return NS_OK;
00689 }
00690 
00692 // nsITreeView
00693 
00694 NS_IMETHODIMP
00695 nsAutoCompleteController::GetRowCount(PRInt32 *aRowCount)
00696 {
00697   *aRowCount = mRowCount;
00698   return NS_OK;
00699 }
00700 
00701 NS_IMETHODIMP
00702 nsAutoCompleteController::GetRowProperties(PRInt32 index, nsISupportsArray *properties)
00703 {
00704   // XXX This is a hack because the tree doesn't seem to be painting the selected row
00705   //     the normal way.  Please remove this ASAP.
00706   PRInt32 currentIndex;
00707   mSelection->GetCurrentIndex(&currentIndex);
00708   
00709   /*
00710   if (index == currentIndex) {
00711     nsCOMPtr<nsIAtomService> atomSvc = do_GetService("@mozilla.org/atom-service;1");
00712     nsCOMPtr<nsIAtom> atom;
00713     atomSvc->GetAtom(NS_LITERAL_STRING("menuactive").get(), getter_AddRefs(atom));
00714     properties->AppendElement(atom);
00715   }
00716   */
00717 
00718   return NS_OK;
00719 }
00720 
00721 NS_IMETHODIMP
00722 nsAutoCompleteController::GetCellProperties(PRInt32 row, nsITreeColumn* col, nsISupportsArray* properties)
00723 {
00724   GetRowProperties(row, properties);
00725   
00726   if (row >= 0) {
00727     nsAutoString className;
00728     GetStyleAt(row, className);
00729     if (!className.IsEmpty()) {
00730       nsCOMPtr<nsIAtomService> atomSvc = do_GetService("@mozilla.org/atom-service;1");
00731       nsCOMPtr<nsIAtom> atom;
00732       atomSvc->GetAtom(className.get(), getter_AddRefs(atom));
00733       properties->AppendElement(atom);
00734     }
00735   }
00736   
00737   return NS_OK;
00738 }
00739 
00740 NS_IMETHODIMP
00741 nsAutoCompleteController::GetColumnProperties(nsITreeColumn* col, nsISupportsArray* properties)
00742 {
00743   return NS_OK;
00744 }
00745 
00746 NS_IMETHODIMP
00747 nsAutoCompleteController::GetImageSrc(PRInt32 row, nsITreeColumn* col, nsAString& _retval)
00748 {
00749   return NS_OK;
00750 }
00751 
00752 NS_IMETHODIMP
00753 nsAutoCompleteController::GetProgressMode(PRInt32 row, nsITreeColumn* col, PRInt32* _retval)
00754 {
00755   NS_NOTREACHED("tree has no progress cells");
00756   return NS_OK;
00757 }
00758 
00759 NS_IMETHODIMP
00760 nsAutoCompleteController::GetCellValue(PRInt32 row, nsITreeColumn* col, nsAString& _retval)
00761 {  
00762   NS_NOTREACHED("all of our cells are text");
00763   return NS_OK;
00764 }
00765 
00766 NS_IMETHODIMP
00767 nsAutoCompleteController::GetCellText(PRInt32 row, nsITreeColumn* col, nsAString& _retval)
00768 {
00769   const PRUnichar* colID;
00770   col->GetIdConst(&colID);
00771   
00772   if (NS_LITERAL_STRING("treecolAutoCompleteValue").Equals(colID))
00773     GetValueAt(row, _retval);
00774   else if (NS_LITERAL_STRING("treecolAutoCompleteComment").Equals(colID))
00775     GetCommentAt(row, _retval);
00776      
00777   return NS_OK;
00778 }
00779 
00780 NS_IMETHODIMP
00781 nsAutoCompleteController::IsContainer(PRInt32 index, PRBool *_retval)
00782 {
00783   *_retval = PR_FALSE;
00784   return NS_OK;
00785 }
00786 
00787 NS_IMETHODIMP
00788 nsAutoCompleteController::IsContainerOpen(PRInt32 index, PRBool *_retval)
00789 {
00790   NS_NOTREACHED("no container cells");
00791   return NS_OK;
00792 }
00793 
00794 NS_IMETHODIMP
00795 nsAutoCompleteController::IsContainerEmpty(PRInt32 index, PRBool *_retval)
00796 {
00797   NS_NOTREACHED("no container cells");
00798   return NS_OK;
00799 }
00800 
00801 NS_IMETHODIMP
00802 nsAutoCompleteController::GetLevel(PRInt32 index, PRInt32 *_retval)
00803 {
00804   *_retval = 0;
00805   return NS_OK;
00806 }
00807 
00808 NS_IMETHODIMP
00809 nsAutoCompleteController::GetParentIndex(PRInt32 rowIndex, PRInt32 *_retval)
00810 {
00811   *_retval = 0;
00812   return NS_OK;
00813 }
00814 
00815 NS_IMETHODIMP
00816 nsAutoCompleteController::HasNextSibling(PRInt32 rowIndex, PRInt32 afterIndex, PRBool *_retval)
00817 {
00818   *_retval = PR_FALSE;
00819   return NS_OK;
00820 }
00821 
00822 NS_IMETHODIMP
00823 nsAutoCompleteController::ToggleOpenState(PRInt32 index)
00824 {
00825   return NS_OK;
00826 }
00827 
00828 NS_IMETHODIMP
00829 nsAutoCompleteController::SetTree(nsITreeBoxObject *tree)
00830 {
00831   mTree = tree;
00832   return NS_OK;
00833 }
00834 
00835 NS_IMETHODIMP
00836 nsAutoCompleteController::GetSelection(nsITreeSelection * *aSelection)
00837 {
00838   *aSelection = mSelection;
00839   NS_IF_ADDREF(*aSelection);
00840   return NS_OK;
00841 }
00842 
00843 NS_IMETHODIMP nsAutoCompleteController::SetSelection(nsITreeSelection * aSelection)
00844 {
00845   mSelection = aSelection;
00846   return NS_OK;
00847 }
00848 
00849 NS_IMETHODIMP
00850 nsAutoCompleteController::SelectionChanged()
00851 {
00852   return NS_OK;
00853 }
00854 
00855 NS_IMETHODIMP
00856 nsAutoCompleteController::SetCellValue(PRInt32 row, nsITreeColumn* col, const nsAString& value)
00857 {
00858   return NS_OK;
00859 }
00860 
00861 NS_IMETHODIMP
00862 nsAutoCompleteController::SetCellText(PRInt32 row, nsITreeColumn* col, const nsAString& value)
00863 {
00864   return NS_OK;
00865 }
00866 
00867 NS_IMETHODIMP
00868 nsAutoCompleteController::CycleHeader(nsITreeColumn* col)
00869 {
00870   return NS_OK;
00871 }
00872 
00873 NS_IMETHODIMP
00874 nsAutoCompleteController::CycleCell(PRInt32 row, nsITreeColumn* col)
00875 {
00876   return NS_OK;
00877 }
00878 
00879 NS_IMETHODIMP
00880 nsAutoCompleteController::IsEditable(PRInt32 row, nsITreeColumn* col, PRBool *_retval)
00881 {
00882   *_retval = PR_FALSE;
00883   return NS_OK;
00884 }
00885 
00886 NS_IMETHODIMP
00887 nsAutoCompleteController::IsSeparator(PRInt32 index, PRBool *_retval)
00888 {
00889   *_retval = PR_FALSE;
00890   return NS_OK;
00891 }
00892 
00893 NS_IMETHODIMP
00894 nsAutoCompleteController::IsSorted(PRBool *_retval)
00895 {
00896   *_retval = PR_FALSE;
00897   return NS_OK;
00898 }
00899 
00900 NS_IMETHODIMP
00901 nsAutoCompleteController::CanDrop(PRInt32 index, PRInt32 orientation, PRBool *_retval)
00902 {
00903   return NS_OK;
00904 }
00905 
00906 NS_IMETHODIMP
00907 nsAutoCompleteController::Drop(PRInt32 row, PRInt32 orientation)
00908 {
00909   return NS_OK;
00910 }
00911 
00912 NS_IMETHODIMP
00913 nsAutoCompleteController::PerformAction(const PRUnichar *action)
00914 {
00915   return NS_OK;
00916 }
00917 
00918 NS_IMETHODIMP
00919 nsAutoCompleteController::PerformActionOnRow(const PRUnichar *action, PRInt32 row)
00920 {
00921   return NS_OK;
00922 }
00923 
00924 NS_IMETHODIMP
00925 nsAutoCompleteController::PerformActionOnCell(const PRUnichar* action, PRInt32 row, nsITreeColumn* col)
00926 {
00927   return NS_OK;
00928 }
00929 
00932 
00933 nsresult
00934 nsAutoCompleteController::OpenPopup()
00935 {
00936   PRUint32 minResults;
00937   mInput->GetMinResultsForPopup(&minResults);
00938 
00939   if (mRowCount >= minResults) {
00940     mIsOpen = PR_TRUE;
00941     return mInput->SetPopupOpen(PR_TRUE);
00942   }
00943   
00944   return NS_OK;
00945 }
00946 
00947 nsresult
00948 nsAutoCompleteController::ClosePopup()
00949 {
00950   if (!mInput) {
00951     return NS_OK;
00952   }
00953   nsCOMPtr<nsIAutoCompletePopup> popup;
00954   mInput->GetPopup(getter_AddRefs(popup));
00955   NS_ENSURE_TRUE(popup != nsnull, NS_ERROR_FAILURE);
00956   popup->SetSelectedIndex(-1);
00957   mIsOpen = PR_FALSE;
00958   return mInput->SetPopupOpen(PR_FALSE);
00959 }
00960 
00961 nsresult
00962 nsAutoCompleteController::StartSearch()
00963 {
00964   NS_ENSURE_STATE(mInput);
00965   mSearchStatus = nsIAutoCompleteController::STATUS_SEARCHING;
00966   mDefaultIndexCompleted = PR_FALSE;
00967   
00968   PRUint32 count;
00969   mSearches->Count(&count);
00970   mSearchesOngoing = count;
00971 
00972   PRUint32 searchesFailed = 0;
00973   for (PRUint32 i = 0; i < count; ++i) {
00974     nsCOMPtr<nsIAutoCompleteSearch> search;
00975     mSearches->GetElementAt(i, getter_AddRefs(search));
00976     nsCOMPtr<nsIAutoCompleteResult> result;
00977     mResults->GetElementAt(i, getter_AddRefs(result));
00978     
00979     if (result) {
00980       PRUint16 searchResult;
00981       result->GetSearchResult(&searchResult);
00982       if (searchResult != nsIAutoCompleteResult::RESULT_SUCCESS)
00983         result = nsnull;
00984     }
00985     
00986     nsAutoString searchParam;
00987     // XXXben - can yank this when we discover what's causing this to 
00988     // fail & crash. 
00989     nsresult rv = mInput->GetSearchParam(searchParam);
00990     if (NS_FAILED(rv))
00991         return rv;
00992     
00993     rv = search->StartSearch(mSearchString, searchParam, result, NS_STATIC_CAST(nsIAutoCompleteObserver *, this));
00994     if (NS_FAILED(rv)) {
00995       ++searchesFailed;
00996       --mSearchesOngoing;
00997     }
00998   }
00999   
01000   if (searchesFailed == count) {
01001     PostSearchCleanup();
01002   }
01003   return NS_OK;
01004 }
01005 
01006 nsresult
01007 nsAutoCompleteController::StopSearch()
01008 {
01009   // Stop the timer if there is one
01010   ClearSearchTimer();
01011 
01012   // Stop any ongoing asynchronous searches
01013   if (mSearchStatus == nsIAutoCompleteController::STATUS_SEARCHING) {
01014     PRUint32 count;
01015     mSearches->Count(&count);
01016   
01017     for (PRUint32 i = 0; i < count; ++i) {
01018       nsCOMPtr<nsIAutoCompleteSearch> search;
01019       mSearches->GetElementAt(i, getter_AddRefs(search));
01020       search->StopSearch();
01021     }
01022   }
01023   return NS_OK;
01024 }
01025 
01026 nsresult
01027 nsAutoCompleteController::StartSearchTimer()
01028 {
01029   // Don't create a new search timer if we're already waiting for one to fire.
01030   // If we don't check for this, we won't be able to cancel the original timer
01031   // and may crash when it fires (bug 236659).
01032   if (mTimer || !mInput)
01033     return NS_OK;
01034 
01035   PRUint32 timeout;
01036   mInput->GetTimeout(&timeout);
01037 
01038   mTimer = do_CreateInstance("@mozilla.org/timer;1");
01039   mTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT);
01040   return NS_OK;
01041 }
01042 
01043 nsresult
01044 nsAutoCompleteController::ClearSearchTimer()
01045 {
01046   if (mTimer) {
01047     mTimer->Cancel();
01048     mTimer = nsnull;
01049   }
01050   return NS_OK;
01051 }
01052 
01053 nsresult
01054 nsAutoCompleteController::EnterMatch()
01055 {
01056   // If a search is still ongoing, bail out of this function
01057   // and let the search finish, and tell it to come back here when it's done
01058   if (mSearchStatus == nsIAutoCompleteController::STATUS_SEARCHING) {
01059     mEnterAfterSearch = PR_TRUE;
01060     return NS_OK;
01061   }
01062   mEnterAfterSearch = PR_FALSE;
01063   
01064   nsCOMPtr<nsIAutoCompletePopup> popup;
01065   mInput->GetPopup(getter_AddRefs(popup));
01066   NS_ENSURE_TRUE(popup != nsnull, NS_ERROR_FAILURE);
01067   
01068   PRBool forceComplete;
01069   mInput->GetForceComplete(&forceComplete);
01070   
01071   // Ask the popup if it wants to enter a special value into the textbox
01072   nsAutoString value;
01073   popup->GetOverrideValue(value);
01074   if (value.IsEmpty()) {
01075     // If a row is selected in the popup, enter it into the textbox
01076     PRInt32 selectedIndex;
01077     popup->GetSelectedIndex(&selectedIndex);
01078     if (selectedIndex >= 0)
01079       GetResultValueAt(selectedIndex, PR_TRUE, value);
01080     
01081     if (forceComplete && value.IsEmpty()) {
01082       // Since nothing was selected, and forceComplete is specified, that means
01083       // we have to find find the first default match and enter it instead
01084       PRUint32 count;
01085       mResults->Count(&count);
01086       for (PRUint32 i = 0; i < count; ++i) {
01087         nsCOMPtr<nsIAutoCompleteResult> result;
01088         mResults->GetElementAt(i, getter_AddRefs(result));
01089 
01090         if (result) {
01091           PRInt32 defaultIndex;
01092           result->GetDefaultIndex(&defaultIndex);
01093           if (defaultIndex >= 0) {
01094             result->GetValueAt(defaultIndex, value);
01095             break;
01096           }
01097         }
01098       }
01099     }
01100   }
01101   
01102   nsCOMPtr<nsIObserverService> obsSvc =
01103     do_GetService("@mozilla.org/observer-service;1");
01104   NS_ENSURE_STATE(obsSvc);
01105   obsSvc->NotifyObservers(mInput, "autocomplete-will-enter-text", nsnull);
01106 
01107   if (!value.IsEmpty()) {
01108     mInput->SetTextValue(value);
01109     mInput->SelectTextRange(value.Length(), value.Length());
01110     mSearchString = value;
01111   }
01112   
01113   obsSvc->NotifyObservers(mInput, "autocomplete-did-enter-text", nsnull);
01114   ClosePopup();
01115   
01116   PRBool cancel;
01117   mInput->OnTextEntered(&cancel);
01118   
01119   return NS_OK;
01120 }
01121 
01122 nsresult
01123 nsAutoCompleteController::RevertTextValue()
01124 {
01125   nsAutoString oldValue(mSearchString);
01126   
01127   PRBool cancel = PR_FALSE;
01128   mInput->OnTextReverted(&cancel);  
01129 
01130   if (!cancel) {
01131     nsCOMPtr<nsIObserverService> obsSvc =
01132       do_GetService("@mozilla.org/observer-service;1");
01133     NS_ENSURE_STATE(obsSvc);
01134     obsSvc->NotifyObservers(mInput, "autocomplete-will-revert-text", nsnull);
01135 
01136     mInput->SetTextValue(oldValue);
01137 
01138     obsSvc->NotifyObservers(mInput, "autocomplete-did-revert-text", nsnull);
01139   }
01140 
01141   return NS_OK;
01142 }
01143 
01144 nsresult
01145 nsAutoCompleteController::ProcessResult(PRInt32 aSearchIndex, nsIAutoCompleteResult *aResult)
01146 {
01147   NS_ENSURE_STATE(mInput);
01148   // If this is the first search to return, we should clear out the previous cached results
01149   PRUint32 searchCount;
01150   mSearches->Count(&searchCount);
01151   if (mSearchesOngoing == searchCount)
01152     ClearResults();
01153 
01154   --mSearchesOngoing;
01155   
01156   // Cache the result
01157   mResults->AppendElement(aResult);
01158 
01159   // If the search failed, increase the match count to include the error description
01160   PRUint16 result = 0;
01161   PRUint32 oldRowCount = mRowCount;
01162 
01163   if (aResult)
01164     aResult->GetSearchResult(&result);
01165   if (result == nsIAutoCompleteResult::RESULT_FAILURE) {
01166     nsAutoString error;
01167     aResult->GetErrorDescription(error);
01168     if (!error.IsEmpty())
01169       ++mRowCount;
01170   } else if (result == nsIAutoCompleteResult::RESULT_SUCCESS) {
01171     // Increase the match count for all matches in this result
01172     PRUint32 matchCount = 0;
01173     aResult->GetMatchCount(&matchCount);
01174     mRowCount += matchCount;
01175 
01176     // Try to autocomplete the default index for this search
01177     CompleteDefaultIndex(aSearchIndex);
01178   }
01179 
01180   if (oldRowCount != mRowCount && mTree)
01181     mTree->RowCountChanged(oldRowCount, mRowCount - oldRowCount);
01182 
01183   // Refresh the popup view to display the new search results
01184   nsCOMPtr<nsIAutoCompletePopup> popup;
01185   mInput->GetPopup(getter_AddRefs(popup));
01186   NS_ENSURE_TRUE(popup != nsnull, NS_ERROR_FAILURE);
01187   popup->Invalidate();
01188   
01189   // Make sure the popup is open, if necessary, since we now
01190   // have at least one search result ready to display
01191   if (mRowCount)
01192     OpenPopup();
01193   else
01194     ClosePopup();
01195 
01196   // If this is the last search to return, cleanup
01197   if (mSearchesOngoing == 0)
01198     PostSearchCleanup();
01199 
01200   return NS_OK;
01201 }
01202 
01203 nsresult
01204 nsAutoCompleteController::PostSearchCleanup()
01205 {  
01206   if (mRowCount) {
01207     OpenPopup();
01208     mSearchStatus = nsIAutoCompleteController::STATUS_COMPLETE_MATCH;
01209   } else {
01210     mSearchStatus = nsIAutoCompleteController::STATUS_COMPLETE_NO_MATCH;
01211     ClosePopup();
01212   }
01213   
01214   // notify the input that the search is complete
01215   mInput->OnSearchComplete();
01216   
01217   // if mEnterAfterSearch was set, then the user hit enter while the search was ongoing,
01218   // so we need to enter a match now that the search is done
01219   if (mEnterAfterSearch)
01220     EnterMatch();
01221 
01222   return NS_OK;
01223 }
01224 
01225 nsresult
01226 nsAutoCompleteController::ClearResults()
01227 {
01228   PRInt32 oldRowCount = mRowCount;
01229   mRowCount = 0;
01230   mResults->Clear();
01231   if (oldRowCount != 0 && mTree)
01232     mTree->RowCountChanged(0, -oldRowCount);
01233   return NS_OK;
01234 }
01235 
01236 nsresult
01237 nsAutoCompleteController::CompleteDefaultIndex(PRInt32 aSearchIndex)
01238 {
01239   if (mDefaultIndexCompleted || mEnterAfterSearch || mBackspaced || mRowCount == 0 || mSearchString.Length() == 0)
01240     return NS_OK;
01241 
01242   PRBool shouldComplete;
01243   mInput->GetCompleteDefaultIndex(&shouldComplete);
01244   if (!shouldComplete)
01245     return NS_OK;
01246 
01247   nsCOMPtr<nsIAutoCompleteSearch> search;
01248   mSearches->GetElementAt(aSearchIndex, getter_AddRefs(search));
01249   nsCOMPtr<nsIAutoCompleteResult> result;
01250   mResults->GetElementAt(aSearchIndex, getter_AddRefs(result));
01251   NS_ENSURE_TRUE(result != nsnull, NS_ERROR_FAILURE);
01252   
01253   // The search must explicitly provide a default index in order
01254   // for us to be able to complete 
01255   PRInt32 defaultIndex;
01256   result->GetDefaultIndex(&defaultIndex);
01257   NS_ENSURE_TRUE(defaultIndex >= 0, NS_OK);
01258 
01259   nsAutoString resultValue;
01260   result->GetValueAt(defaultIndex, resultValue);
01261   CompleteValue(resultValue, PR_TRUE);
01262   
01263   mDefaultIndexCompleted = PR_TRUE;
01264 
01265   return NS_OK;
01266 }
01267 
01268 nsresult
01269 nsAutoCompleteController::CompleteValue(nsString &aValue, 
01270                                         PRBool selectDifference)
01271 /* mInput contains mSearchString, which we want to autocomplete to aValue.  If
01272  * selectDifference is true, select the remaining portion of aValue not 
01273  * contained in mSearchString. */
01274 {
01275   const PRInt32 mSearchStringLength = mSearchString.Length();
01276   PRInt32 endSelect = aValue.Length();  // By default, select all of aValue.
01277 
01278   if (aValue.IsEmpty() || 
01279       StringBeginsWith(aValue, mSearchString,
01280                        nsCaseInsensitiveStringComparator())) {
01281     // aValue is empty (we were asked to clear mInput), or mSearchString 
01282     // matches the beginning of aValue.  In either case we can simply 
01283     // autocomplete to aValue.
01284     mInput->SetTextValue(aValue);
01285   } else {
01286     PRInt32 findIndex;  // Offset of mSearchString within aValue.
01287 
01288     nsresult rv;
01289     nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
01290     NS_ENSURE_SUCCESS(rv, rv);
01291     nsCAutoString scheme;
01292     if (NS_SUCCEEDED(ios->ExtractScheme(NS_ConvertUTF16toUTF8(aValue), scheme))) {
01293       // Trying to autocomplete a URI from somewhere other than the beginning.
01294       // Only succeed if the missing portion is "http://"; otherwise do not
01295       // autocomplete.  This prevents us from "helpfully" autocompleting to a
01296       // URI that isn't equivalent to what the user expected.
01297       findIndex = 7; // length of "http://"
01298 
01299       if ((endSelect < findIndex + mSearchStringLength) ||
01300           !scheme.LowerCaseEqualsLiteral("http") ||
01301           !Substring(aValue, findIndex, mSearchStringLength).Equals(
01302             mSearchString, nsCaseInsensitiveStringComparator())) {
01303         return NS_OK;
01304       }
01305     } else {
01306       // Autocompleting something other than a URI from the middle.  Assume we
01307       // can just go ahead and autocomplete the missing final portion; this
01308       // seems like a problematic assumption...
01309       nsString::const_iterator iter, end;
01310       aValue.BeginReading(iter);
01311       aValue.EndReading(end);
01312       const nsString::const_iterator::pointer start = iter.get();
01313       ++iter;  // Skip past beginning since we know that doesn't match
01314 
01315       FindInReadable(mSearchString, iter, end, 
01316                      nsCaseInsensitiveStringComparator());
01317 
01318       findIndex = iter.get() - start;
01319     }
01320 
01321     mInput->SetTextValue(mSearchString + 
01322                          Substring(aValue, mSearchStringLength + findIndex,
01323                                    endSelect));
01324 
01325     endSelect -= findIndex; // We're skipping this many characters of aValue.
01326   }
01327 
01328   mInput->SelectTextRange(selectDifference ? 
01329                           mSearchStringLength : endSelect, endSelect);
01330 
01331   return NS_OK;
01332 }
01333 
01334 nsresult
01335 nsAutoCompleteController::GetResultValueAt(PRInt32 aIndex, PRBool aValueOnly, nsAString & _retval)
01336 {
01337   NS_ENSURE_TRUE(aIndex >= 0 && (PRUint32) aIndex < mRowCount, NS_ERROR_ILLEGAL_VALUE);
01338 
01339   PRInt32 searchIndex;
01340   PRInt32 rowIndex;
01341   RowIndexToSearch(aIndex, &searchIndex, &rowIndex);
01342   NS_ENSURE_TRUE(searchIndex >= 0 && rowIndex >= 0, NS_ERROR_FAILURE);
01343   
01344   nsCOMPtr<nsIAutoCompleteResult> result;
01345   mResults->GetElementAt(searchIndex, getter_AddRefs(result));
01346   NS_ENSURE_TRUE(result != nsnull, NS_ERROR_FAILURE);
01347   
01348   PRUint16 searchResult;
01349   result->GetSearchResult(&searchResult);
01350   
01351   if (searchResult == nsIAutoCompleteResult::RESULT_FAILURE) {
01352     if (aValueOnly)
01353       return NS_ERROR_FAILURE;
01354     result->GetErrorDescription(_retval);
01355   } else if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS) {
01356     result->GetValueAt(rowIndex, _retval);
01357   }
01358   
01359   return NS_OK;
01360 }
01361 
01362 nsresult
01363 nsAutoCompleteController::RowIndexToSearch(PRInt32 aRowIndex, PRInt32 *aSearchIndex, PRInt32 *aItemIndex)
01364 {
01365   *aSearchIndex = -1;
01366   *aItemIndex = -1;
01367   
01368   PRUint32 count;
01369   mSearches->Count(&count);
01370   PRUint32 index = 0;
01371   for (PRUint32 i = 0; i < count; ++i) {
01372     nsCOMPtr<nsIAutoCompleteResult> result;
01373     mResults->GetElementAt(i, getter_AddRefs(result));
01374     if (!result)
01375       continue;
01376       
01377     PRUint16 searchResult;
01378     result->GetSearchResult(&searchResult);
01379     
01380     PRUint32 rowCount = 1;
01381     if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS) {
01382       result->GetMatchCount(&rowCount);
01383     }
01384     
01385     if (index + rowCount-1 >= (PRUint32) aRowIndex) {
01386       *aSearchIndex = i;
01387       *aItemIndex = aRowIndex - index;
01388       return NS_OK;
01389     }
01390 
01391     index += rowCount;
01392   }
01393 
01394   return NS_OK;
01395 }
01396 
01397 nsIWidget*
01398 nsAutoCompleteController::GetPopupWidget()
01399 {
01400   NS_ENSURE_TRUE(mInput, nsnull);
01401 
01402   nsCOMPtr<nsIAutoCompletePopup> autoCompletePopup;
01403   mInput->GetPopup(getter_AddRefs(autoCompletePopup));
01404   NS_ENSURE_TRUE(autoCompletePopup, nsnull);
01405 
01406   nsCOMPtr<nsIDOMNode> popup = do_QueryInterface(autoCompletePopup);
01407   NS_ENSURE_TRUE(popup, nsnull);
01408 
01409   nsCOMPtr<nsIDOMDocument> domDoc;
01410   popup->GetOwnerDocument(getter_AddRefs(domDoc));
01411 
01412   nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
01413   nsIPresShell* presShell = doc->GetShellAt(0);
01414   nsCOMPtr<nsIContent> content = do_QueryInterface(popup);
01415   nsIFrame* frame;
01416   presShell->GetPrimaryFrameFor(content, &frame);
01417   while (frame) {
01418     nsIView* view = frame->GetViewExternal();
01419     if (view && view->HasWidget())
01420       return view->GetWidget();
01421     frame = frame->GetParent();
01422   }
01423 
01424   NS_ERROR("widget wasn't found!");
01425 
01426   return nsnull;
01427 }