Files
Mozilla/mozilla/toolkit/components/autocomplete/src/nsAutoCompleteController.cpp
hewitt%netscape.com f6c8a0072f 165955 - landing new autocomplete api and satchel for phoenix. not built yet.
git-svn-id: svn://10.0.0.236/trunk@130624 18797224-902f-48f8-a5cc-f745e15eee43
2002-09-28 00:38:20 +00:00

1001 lines
26 KiB
C++

/* ***** BEGIN LICENSE BLOCK *****
* Version: NPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Netscape Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Communicator client code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Joe Hewitt <hewitt@netscape.com> (Original Author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the NPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the NPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsAutoCompleteController.h"
#include "nsIServiceManager.h"
#include "nsIDOMKeyEvent.h"
#include "nsIDOMNode.h"
#include "nsIDOMEventTarget.h"
#include "nsIAtomService.h"
#include "nsReadableUtils.h"
static const char *kAutoCompleteSearchCID = "@mozilla.org/autocomplete/search;1?name=";
static const char *kCompleteConcatSeparator = " >> ";
NS_IMPL_ISUPPORTS4(nsAutoCompleteController, nsIAutoCompleteController, nsIAutoCompleteObserver, nsITimerCallback, nsITreeView)
nsAutoCompleteController::nsAutoCompleteController() :
mNeedToComplete(PR_FALSE),
mEnterAfterSearch(PR_FALSE),
mDefaultIndexCompleted(PR_FALSE),
mBackspaced(PR_FALSE),
mSearchStatus(0),
mRowCount(0),
mSearchesOngoing(0)
{
NS_INIT_ISUPPORTS();
mSearches = do_CreateInstance("@mozilla.org/supports-array;1");
mResults = do_CreateInstance("@mozilla.org/supports-array;1");
}
nsAutoCompleteController::~nsAutoCompleteController()
{
DetachFromInput();
}
////////////////////////////////////////////////////////////////////////
//// nsIAutoCompleteController
NS_IMETHODIMP
nsAutoCompleteController::GetSearchStatus(PRUint16 *aSearchStatus)
{
*aSearchStatus = mSearchStatus;
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetMatchCount(PRUint32 *aMatchCount)
{
*aMatchCount = mRowCount;
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::AttachToInput(nsIAutoCompleteInput *aInput)
{
if (!aInput)
return NS_ERROR_ILLEGAL_VALUE;
if (mInput)
DetachFromInput();
mInput = aInput;
// reset all search state members to default values
mSearchString.Truncate(0);
mEnterAfterSearch = PR_FALSE;
mNeedToComplete = PR_FALSE;
mDefaultIndexCompleted = PR_FALSE;
mBackspaced = PR_FALSE;
mSearchStatus = nsIAutoCompleteController::STATUS_NONE;
mRowCount = 0;
mSearchesOngoing = 0;
// initialize our list of search objects
PRUint32 searchCount;
mInput->GetSearchCount(&searchCount);
mResults->SizeTo(searchCount);
mSearches->SizeTo(searchCount);
const char *searchCID = kAutoCompleteSearchCID;
for (PRUint32 i = 0; i < searchCount; ++i) {
// Use the search name to create the contract id string for the search service
nsCAutoString searchName;
mInput->GetSearchAt(i, searchName);
nsCAutoString cid(searchCID);
cid.Append(searchName);
// Use the created cid to get a pointer to the search service and store it for later
nsIAutoCompleteSearch* search = nsnull;
CallGetService(cid.get(), &search);
if (search)
mSearches->AppendElement(search);
}
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::DetachFromInput()
{
if (mInput) {
ClearSearchTimer();
ClearResults();
ClosePopup();
// release refcounted and allocated members
mInput = nsnull;
mSearches->Clear();
mSearchString.Truncate(0);
}
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::HandleText()
{
// Stop current search in case it's async.
StopSearch();
// Stop the queued up search on a timer
ClearSearchTimer();
PRBool disabled;
mInput->GetDisableAutoComplete(&disabled);
NS_ENSURE_TRUE(!disabled, NS_OK;);
mNeedToComplete = PR_TRUE;
nsAutoString newValue;
mInput->GetTextValue(newValue);
// Don't search again if the new string is the same as the last search
if (newValue.Length() > 0 && newValue.Equals(mSearchString))
return NS_OK;
// Determine if the user has removed text from the end (probably by backspacing)
if (newValue.Length() < mSearchString.Length() &&
Substring(mSearchString, 0, newValue.Length()).Equals(newValue))
{
// We need to throw away previous results so we don't try to search through them again
ClearResults();
mBackspaced = PR_TRUE;
} else
mBackspaced = PR_FALSE;
mSearchString = newValue;
// Don't search if the value is empty
if (newValue.Length() == 0) {
ClosePopup();
return NS_OK;
}
// Kick off the search, but only if the cursor is at the end of the textbox
PRBool selectionStart;
mInput->GetSelectionStart(&selectionStart);
PRBool selectionEnd;
mInput->GetSelectionEnd(&selectionEnd);
if (selectionStart == selectionEnd && selectionStart == mSearchString.Length())
StartSearchTimer();
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::HandleEnter(PRBool *_retval)
{
// allow the event through unless there is something selected in the popup
mInput->GetPopupOpen(_retval);
if (*_retval) {
nsCOMPtr<nsIAutoCompletePopup> popup;
mInput->GetPopup(getter_AddRefs(popup));
PRInt32 selectedIndex;
popup->GetSelectedIndex(&selectedIndex);
*_retval = selectedIndex >= 0;
}
ClearSearchTimer();
EnterMatch();
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::HandleEscape(PRBool *_retval)
{
// allow the event through if the popup is closed
mInput->GetPopupOpen(_retval);
ClearSearchTimer();
RevertTextValue();
ClosePopup();
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::HandleKeyNavigation(PRUint16 aKey, PRBool *_retval)
{
// By default, don't cancel the event
*_retval = PR_FALSE;
mNeedToComplete = PR_FALSE;
nsCOMPtr<nsIAutoCompletePopup> popup;
mInput->GetPopup(getter_AddRefs(popup));
NS_ENSURE_TRUE(popup != nsnull, NS_ERROR_FAILURE);
if (aKey == nsIAutoCompleteController::KEY_UP ||
aKey == nsIAutoCompleteController::KEY_DOWN ||
aKey == nsIAutoCompleteController::KEY_PAGE_UP ||
aKey == nsIAutoCompleteController::KEY_PAGE_DOWN)
{
// Prevent the input from handling up/down events, as it may move
// the cursor to home/end on some systems
*_retval = PR_TRUE;
PRBool isOpen;
mInput->GetPopupOpen(&isOpen);
if (isOpen) {
PRBool reverse = aKey == nsIAutoCompleteController::KEY_UP ||
aKey == nsIAutoCompleteController::KEY_PAGE_UP ? PR_TRUE : PR_FALSE;
PRBool page = aKey == nsIAutoCompleteController::KEY_PAGE_UP ||
aKey == nsIAutoCompleteController::KEY_PAGE_DOWN ? PR_TRUE : PR_FALSE;
// Instruct the result view to scroll by the given amount and direction
popup->SelectBy(reverse, page);
// Fill in the value of the textbox with whatever is selected in the popup
PRInt32 selectedIndex;
popup->GetSelectedIndex(&selectedIndex);
if (selectedIndex >= 0) {
// A result is selected, so fill in its value
nsAutoString value;
if (NS_SUCCEEDED(GetResultValueAt(selectedIndex, PR_TRUE, value)))
CompleteValue(value);
} else {
// Nothing is selected, so fill in the last typed value
mInput->SetTextValue(mSearchString);
mInput->SelectTextRange(mSearchString.Length(), mSearchString.Length());
}
} else {
// Open the popup if there has been a previous search, or else kick off a new search
PRUint32 resultCount;
mResults->Count(&resultCount);
if (resultCount) {
if (mRowCount) {
OpenPopup();
}
} else
StartSearchTimer();
}
} else if (aKey == nsIAutoCompleteController::KEY_LEFT ||
aKey == nsIAutoCompleteController::KEY_RIGHT)
{
// When the user arrows to the side, close the popup
ClearSearchTimer();
ClosePopup();
}
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetValueAt(PRInt32 aIndex, nsAString & _retval)
{
GetResultValueAt(aIndex, PR_FALSE, _retval);
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetCommentAt(PRInt32 aIndex, nsAString & _retval)
{
PRInt32 searchIndex;
PRInt32 rowIndex;
RowIndexToSearch(aIndex, &searchIndex, &rowIndex);
NS_ENSURE_TRUE(searchIndex >= 0 && rowIndex >= 0, NS_ERROR_FAILURE);
nsCOMPtr<nsIAutoCompleteResult> result;
mResults->GetElementAt(searchIndex, getter_AddRefs(result));
NS_ENSURE_TRUE(result, NS_ERROR_FAILURE);
result->GetCommentAt(rowIndex, _retval);
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetStyleAt(PRInt32 aIndex, nsAString & _retval)
{
PRInt32 searchIndex;
PRInt32 rowIndex;
RowIndexToSearch(aIndex, &searchIndex, &rowIndex);
NS_ENSURE_TRUE(searchIndex >= 0 && rowIndex >= 0, NS_ERROR_FAILURE);
nsCOMPtr<nsIAutoCompleteResult> result;
mResults->GetElementAt(searchIndex, getter_AddRefs(result));
NS_ENSURE_TRUE(result, NS_ERROR_FAILURE);
result->GetStyleAt(rowIndex, _retval);
return NS_OK;
}
////////////////////////////////////////////////////////////////////////
//// nsIAutoCompleteObserver
NS_IMETHODIMP
nsAutoCompleteController::OnSearchResult(nsIAutoCompleteSearch *aSearch, nsIAutoCompleteResult* aResult)
{
// look up the index of the search which is returning
PRUint32 count;
mSearches->Count(&count);
for (PRUint32 i = 0; i < count; ++i) {
nsCOMPtr<nsIAutoCompleteSearch> search;
mSearches->GetElementAt(i, getter_AddRefs(search));
if (search == aSearch) {
ProcessResult(i, aResult);
}
}
return NS_OK;
}
////////////////////////////////////////////////////////////////////////
//// nsITimerCallback
NS_IMETHODIMP
nsAutoCompleteController::Notify(nsITimer *timer)
{
mTimer = nsnull;
StartSearch();
return NS_OK;
}
////////////////////////////////////////////////////////////////////////
// nsITreeView
NS_IMETHODIMP
nsAutoCompleteController::GetRowCount(PRInt32 *aRowCount)
{
*aRowCount = mRowCount;
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetRowProperties(PRInt32 index, nsISupportsArray *properties)
{
// XXX This is a hack because the tree doesn't seem to be painting the selected row
// the normal way. Please remove this ASAP.
PRInt32 currentIndex;
mSelection->GetCurrentIndex(&currentIndex);
if (index == currentIndex) {
nsCOMPtr<nsIAtomService> atomSvc = do_GetService("@mozilla.org/atom-service;1");
nsCOMPtr<nsIAtom> atom;
atomSvc->GetAtom(NS_LITERAL_STRING("menuactive").get(), getter_AddRefs(atom));
properties->AppendElement(atom);
}
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetCellProperties(PRInt32 row, const PRUnichar *colID, nsISupportsArray *properties)
{
GetRowProperties(row, properties);
if (row >= 0) {
nsAutoString className;
GetStyleAt(row, className);
if (!className.IsEmpty()) {
nsCOMPtr<nsIAtomService> atomSvc = do_GetService("@mozilla.org/atom-service;1");
nsCOMPtr<nsIAtom> atom;
atomSvc->GetAtom(className.get(), getter_AddRefs(atom));
properties->AppendElement(atom);
}
}
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetColumnProperties(const PRUnichar *colID, nsIDOMElement *colElt, nsISupportsArray *properties)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetImageSrc(PRInt32 row, const PRUnichar *colID, nsAString& _retval)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetProgressMode(PRInt32 row, const PRUnichar *colID, PRInt32* _retval)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetCellValue(PRInt32 row, const PRUnichar *colID, nsAString& _retval)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetCellText(PRInt32 row, const PRUnichar *colID, nsAString& _retval)
{
nsDependentString columnId(colID);
if (columnId.Equals(NS_LITERAL_STRING("treecolAutoCompleteValue")))
GetValueAt(row, _retval);
else if(columnId.Equals(NS_LITERAL_STRING("treecolAutoCompleteComment")))
GetCommentAt(row, _retval);
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::IsContainer(PRInt32 index, PRBool *_retval)
{
*_retval = PR_FALSE;
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::IsContainerOpen(PRInt32 index, PRBool *_retval)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::IsContainerEmpty(PRInt32 index, PRBool *_retval)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetLevel(PRInt32 index, PRInt32 *_retval)
{
*_retval = 0;
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetParentIndex(PRInt32 rowIndex, PRInt32 *_retval)
{
*_retval = 0;
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::HasNextSibling(PRInt32 rowIndex, PRInt32 afterIndex, PRBool *_retval)
{
*_retval = PR_FALSE;
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::ToggleOpenState(PRInt32 index)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::SetTree(nsITreeBoxObject *tree)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetSelection(nsITreeSelection * *aSelection)
{
*aSelection = mSelection;
NS_IF_ADDREF(*aSelection);
return NS_OK;
}
NS_IMETHODIMP nsAutoCompleteController::SetSelection(nsITreeSelection * aSelection)
{
mSelection = aSelection;
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::SelectionChanged()
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::SetCellText(PRInt32 row, const PRUnichar *colID, const PRUnichar *value)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::CycleHeader(const PRUnichar *colID, nsIDOMElement *elt)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::CycleCell(PRInt32 row, const PRUnichar *colID)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::IsEditable(PRInt32 row, const PRUnichar *colID, PRBool *_retval)
{
*_retval = PR_FALSE;
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::IsSeparator(PRInt32 index, PRBool *_retval)
{
*_retval = PR_FALSE;
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::IsSorted(PRBool *_retval)
{
*_retval = PR_FALSE;
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::CanDropOn(PRInt32 index, PRBool *_retval)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::CanDropBeforeAfter(PRInt32 index, PRBool before, PRBool *_retval)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::Drop(PRInt32 row, PRInt32 orientation)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::PerformAction(const PRUnichar *action)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::PerformActionOnRow(const PRUnichar *action, PRInt32 row)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::PerformActionOnCell(const PRUnichar *action, PRInt32 row, const PRUnichar *colID)
{
return NS_OK;
}
////////////////////////////////////////////////////////////////////////
//// nsAutoCompleteController
nsresult
nsAutoCompleteController::OpenPopup()
{
PRUint32 minResults;
mInput->GetMinResultsForPopup(&minResults);
if (mRowCount >= minResults)
return mInput->SetPopupOpen(PR_TRUE);
return NS_OK;
}
nsresult
nsAutoCompleteController::ClosePopup()
{
nsCOMPtr<nsIAutoCompletePopup> popup;
mInput->GetPopup(getter_AddRefs(popup));
NS_ENSURE_TRUE(popup != nsnull, NS_ERROR_FAILURE);
popup->SetSelectedIndex(-1);
return mInput->SetPopupOpen(PR_FALSE);
}
nsresult
nsAutoCompleteController::StartSearch()
{
mSearchStatus = nsIAutoCompleteController::STATUS_SEARCHING;
mDefaultIndexCompleted = PR_FALSE;
PRUint32 count;
mSearches->Count(&count);
mSearchesOngoing = count;
PRUint32 searchesFailed = 0;
for (PRUint32 i = 0; i < count; ++i) {
nsCOMPtr<nsIAutoCompleteSearch> search;
mSearches->GetElementAt(i, getter_AddRefs(search));
nsCOMPtr<nsIAutoCompleteResult> result;
mResults->GetElementAt(i, getter_AddRefs(result));
if (result) {
PRUint16 searchResult;
result->GetSearchResult(&searchResult);
if (searchResult != nsIAutoCompleteResult::RESULT_SUCCESS)
result = nsnull;
}
nsAutoString searchParam;
mInput->GetSearchParam(searchParam);
nsresult rv = search->StartSearch(mSearchString, searchParam, result, NS_STATIC_CAST(nsIAutoCompleteObserver *, this));
if (NS_FAILED(rv)) {
++searchesFailed;
--mSearchesOngoing;
}
}
if (searchesFailed == count) {
PostSearchCleanup();
}
return NS_OK;
}
nsresult
nsAutoCompleteController::StopSearch()
{
// Stop the timer if there is one
ClearSearchTimer();
// Stop any ongoing asynchronous searches
if (mSearchStatus == nsIAutoCompleteController::STATUS_SEARCHING) {
PRUint32 count;
mSearches->Count(&count);
for (PRUint32 i = 0; i < count; ++i) {
nsCOMPtr<nsIAutoCompleteSearch> search;
mSearches->GetElementAt(i, getter_AddRefs(search));
search->StopSearch();
}
}
return NS_OK;
}
nsresult
nsAutoCompleteController::StartSearchTimer()
{
PRUint32 timeout;
mInput->GetTimeout(&timeout);
mTimer = do_CreateInstance("@mozilla.org/timer;1");
mTimer->InitWithCallback(this, 0, timeout);
return NS_OK;
}
nsresult
nsAutoCompleteController::ClearSearchTimer()
{
if (mTimer) {
mTimer->Cancel();
mTimer = nsnull;
}
return NS_OK;
}
nsresult
nsAutoCompleteController::EnterMatch()
{
// If a search is still ongoing, bail out of this function
// and let the search finish, and tell it to come back here when it's done
if (mSearchStatus == nsIAutoCompleteController::STATUS_SEARCHING) {
mEnterAfterSearch = PR_TRUE;
return NS_OK;
} else
mEnterAfterSearch = PR_FALSE;
nsCOMPtr<nsIAutoCompletePopup> popup;
mInput->GetPopup(getter_AddRefs(popup));
NS_ENSURE_TRUE(popup != nsnull, NS_ERROR_FAILURE);
PRBool forceComplete;
mInput->GetForceComplete(&forceComplete);
// Ask the popup if it wants to enter a special value into the textbox
nsAutoString value;
popup->GetOverrideValue(value);
if (value.IsEmpty()) {
// If a row is selected in the popup, enter it into the textbox
PRInt32 selectedIndex;
popup->GetSelectedIndex(&selectedIndex);
if (selectedIndex >= 0)
GetResultValueAt(selectedIndex, PR_TRUE, value);
if (forceComplete && value.IsEmpty()) {
// Since nothing was selected, and forceComplete is specified, that means
// we have to find find the first default match and enter it instead
PRUint32 count;
mResults->Count(&count);
for (PRUint32 i = 0; i < count; ++i) {
nsCOMPtr<nsIAutoCompleteResult> result;
mResults->GetElementAt(i, getter_AddRefs(result));
if (result) {
PRInt32 defaultIndex;
result->GetDefaultIndex(&defaultIndex);
if (defaultIndex >= 0) {
result->GetValueAt(defaultIndex, value);
break;
}
}
}
}
}
if (!value.IsEmpty()) {
mInput->SetTextValue(value);
mInput->SelectTextRange(-1, -1);
mSearchString = value;
}
ClosePopup();
PRBool cancel;
mInput->OnTextEntered(&cancel);
return NS_OK;
}
nsresult
nsAutoCompleteController::RevertTextValue()
{
nsAutoString oldValue(mSearchString);
PRBool cancel = PR_FALSE;
mInput->OnTextReverted(&cancel);
if (!cancel)
mInput->SetTextValue(oldValue);
nsAutoString value;
mInput->GetTextValue(value);
mSearchString.Assign(value);
mNeedToComplete = PR_FALSE;
return NS_OK;
}
nsresult
nsAutoCompleteController::ProcessResult(PRInt32 aSearchIndex, nsIAutoCompleteResult *aResult)
{
// If this is the first search to return, we should clear out the previous cached results
PRUint32 searchCount;
mSearches->Count(&searchCount);
if (mSearchesOngoing == searchCount)
ClearResults();
--mSearchesOngoing;
// Cache the result
mResults->AppendElement(aResult);
// If the search failed, increase the match count to include the error description
PRUint16 result = 0;
if (aResult)
aResult->GetSearchResult(&result);
if (result == nsIAutoCompleteResult::RESULT_FAILURE) {
nsAutoString error;
aResult->GetErrorDescription(error);
if (!error.IsEmpty())
++mRowCount;
} else if (result == nsIAutoCompleteResult::RESULT_SUCCESS) {
// Increase the match count for all matches in this result
PRUint32 matchCount = 0;
aResult->GetMatchCount(&matchCount);
mRowCount += matchCount;
// Try to autocomplete the default index for this search
CompleteDefaultIndex(aSearchIndex);
}
// Refresh the popup view to display the new search results
nsCOMPtr<nsIAutoCompletePopup> popup;
mInput->GetPopup(getter_AddRefs(popup));
NS_ENSURE_TRUE(popup != nsnull, NS_ERROR_FAILURE);
popup->Invalidate();
// Make sure the popup is open, if necessary, since we now
// have at least one search result ready to display
if (mRowCount)
OpenPopup();
else
ClosePopup();
// If this is the last search to return, cleanup
if (mSearchesOngoing == 0)
PostSearchCleanup();
return NS_OK;
}
nsresult
nsAutoCompleteController::PostSearchCleanup()
{
if (mRowCount) {
OpenPopup();
mSearchStatus = nsIAutoCompleteController::STATUS_COMPLETE_MATCH;
} else {
mSearchStatus = nsIAutoCompleteController::STATUS_COMPLETE_NO_MATCH;
ClosePopup();
}
// notify the input that the search is complete
mInput->OnSearchComplete();
// if mEnterAfterSearch was set, then the user hit enter while the search was ongoing,
// so we need to enter a match now that the search is done
if (mEnterAfterSearch)
EnterMatch();
return NS_OK;
}
nsresult
nsAutoCompleteController::ClearResults()
{
mRowCount = 0;
mResults->Clear();
return NS_OK;
}
nsresult
nsAutoCompleteController::CompleteDefaultIndex(PRInt32 aSearchIndex)
{
if (mDefaultIndexCompleted || mEnterAfterSearch || mBackspaced || mRowCount == 0 || mSearchString.Length() == 0)
return NS_OK;
PRBool shouldComplete;
mInput->GetCompleteDefaultIndex(&shouldComplete);
if (!shouldComplete)
return NS_OK;
nsCOMPtr<nsIAutoCompleteSearch> search;
mSearches->GetElementAt(aSearchIndex, getter_AddRefs(search));
nsCOMPtr<nsIAutoCompleteResult> result;
mResults->GetElementAt(aSearchIndex, getter_AddRefs(result));
NS_ENSURE_TRUE(result != nsnull, NS_ERROR_FAILURE);
// The search must explicitly provide a default index in order
// for us to be able to complete
PRInt32 defaultIndex;
result->GetDefaultIndex(&defaultIndex);
NS_ENSURE_TRUE(defaultIndex >= 0, NS_OK);
nsAutoString resultValue;
result->GetValueAt(defaultIndex, resultValue);
CompleteValue(resultValue);
mDefaultIndexCompleted = PR_TRUE;
return NS_OK;
}
nsresult
nsAutoCompleteController::CompleteValue(nsString &aValue)
{
PRInt32 findIndex = aValue.Find(mSearchString, PR_FALSE);
if (findIndex == 0) {
// The textbox value matches the beginning of the default value, so we can just
// append the latter portion
mInput->SetTextValue(aValue);
mInput->SelectTextRange(mSearchString.Length(), aValue.Length());
} else {
mInput->SetTextValue(mSearchString + Substring(aValue, mSearchString.Length()+findIndex, aValue.Length()));
mInput->SelectTextRange(mSearchString.Length(), -1);
// XXX There might be a pref someday for doing it this way instead.
// The textbox value does not match the beginning of the default value, so we
// have to append the entire default value
// mInput->SetTextValue(mSearchString + NS_ConvertUTF8toUCS2(kCompleteConcatSeparator) + aValue);
// mInput->SelectTextRange(mSearchString.Length(), -1);
}
return NS_OK;
}
nsresult
nsAutoCompleteController::GetResultValueAt(PRInt32 aIndex, PRBool aValueOnly, nsAString & _retval)
{
NS_ENSURE_TRUE(aIndex >= 0 && aIndex < mRowCount, NS_ERROR_ILLEGAL_VALUE);
PRInt32 searchIndex;
PRInt32 rowIndex;
RowIndexToSearch(aIndex, &searchIndex, &rowIndex);
NS_ENSURE_TRUE(searchIndex >= 0 && rowIndex >= 0, NS_ERROR_FAILURE);
nsCOMPtr<nsIAutoCompleteResult> result;
mResults->GetElementAt(searchIndex, getter_AddRefs(result));
NS_ENSURE_TRUE(result != nsnull, NS_ERROR_FAILURE);
PRUint16 searchResult;
result->GetSearchResult(&searchResult);
if (searchResult == nsIAutoCompleteResult::RESULT_FAILURE) {
if (aValueOnly)
return NS_ERROR_FAILURE;
else
result->GetErrorDescription(_retval);
} else if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS) {
result->GetValueAt(rowIndex, _retval);
}
return NS_OK;
}
nsresult
nsAutoCompleteController::RowIndexToSearch(PRInt32 aRowIndex, PRInt32 *aSearchIndex, PRInt32 *aItemIndex)
{
*aSearchIndex = -1;
*aItemIndex = -1;
PRUint32 count;
mSearches->Count(&count);
PRUint32 index = 0;
for (PRUint32 i = 0; i < count; ++i) {
nsCOMPtr<nsIAutoCompleteResult> result;
mResults->GetElementAt(i, getter_AddRefs(result));
if (!result)
continue;
PRUint16 searchResult;
result->GetSearchResult(&searchResult);
PRUint32 rowCount;
if (searchResult == nsIAutoCompleteResult::RESULT_FAILURE) {
nsAutoString error;
result->GetErrorDescription(error);
if (!error.IsEmpty())
rowCount = 1;
} else if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS) {
result->GetMatchCount(&rowCount);
}
if (index + rowCount-1 >= aRowIndex) {
*aSearchIndex = i;
*aItemIndex = aRowIndex - index;
return NS_OK;
}
index += rowCount;
}
return NS_OK;
}