1001 lines
26 KiB
C++
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(¤tIndex);
|
|
|
|
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;
|
|
}
|