gecko-dev/toolkit/components/autocomplete/nsAutoCompleteController.cpp

1729 lines
50 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla 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/MPL/
*
* 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)
* Dean Tessman <dean_tessman@hotmail.com>
* Johnny Stenback <jst@mozilla.jstenback.com>
* Masayuki Nakano <masayuki@d-toybox.com>
* Michael Ventnor <m.ventnor@gmail.com>
*
* 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 MPL, 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 MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsAutoCompleteController.h"
#include "nsAutoCompleteSimpleResult.h"
#include "nsAutoPtr.h"
#include "nsNetCID.h"
#include "nsIIOService.h"
#include "nsToolkitCompsCID.h"
#include "nsIServiceManager.h"
#include "nsIAtomService.h"
#include "nsReadableUtils.h"
#include "nsUnicharUtils.h"
#include "nsITreeColumns.h"
#include "nsIObserverService.h"
#include "nsIDOMKeyEvent.h"
#include "mozilla/Services.h"
#include "mozilla/ModuleUtils.h"
static const char *kAutoCompleteSearchCID = "@mozilla.org/autocomplete/search;1?name=";
NS_IMPL_CYCLE_COLLECTION_CLASS(nsAutoCompleteController)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsAutoCompleteController)
tmp->SetInput(nsnull);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsAutoCompleteController)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mInput)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mSearches)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mResults)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsAutoCompleteController)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsAutoCompleteController)
NS_INTERFACE_TABLE_HEAD(nsAutoCompleteController)
NS_INTERFACE_TABLE4(nsAutoCompleteController, nsIAutoCompleteController,
nsIAutoCompleteObserver, nsITimerCallback, nsITreeView)
NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsAutoCompleteController)
NS_INTERFACE_MAP_END
nsAutoCompleteController::nsAutoCompleteController() :
mDefaultIndexCompleted(false),
mBackspaced(false),
mPopupClosedByCompositionStart(false),
mCompositionState(eCompositionState_None),
mSearchStatus(nsAutoCompleteController::STATUS_NONE),
mRowCount(0),
mSearchesOngoing(0),
mSearchesFailed(0),
mFirstSearchResult(false),
mImmediateSearchesCount(0)
{
}
nsAutoCompleteController::~nsAutoCompleteController()
{
SetInput(nsnull);
}
////////////////////////////////////////////////////////////////////////
//// 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::GetInput(nsIAutoCompleteInput **aInput)
{
*aInput = mInput;
NS_IF_ADDREF(*aInput);
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::SetInput(nsIAutoCompleteInput *aInput)
{
// Don't do anything if the input isn't changing.
if (mInput == aInput)
return NS_OK;
// Clear out the current search context
if (mInput) {
// Stop all searches in case they are async.
StopSearch();
ClearResults();
ClosePopup();
mSearches.Clear();
}
mInput = aInput;
// Nothing more to do if the input was just being set to null.
if (!aInput)
return NS_OK;
nsAutoString newValue;
aInput->GetTextValue(newValue);
// Clear out this reference in case the new input's popup has no tree
mTree = nsnull;
// Reset all search state members to default values
mSearchString = newValue;
mDefaultIndexCompleted = false;
mBackspaced = false;
mSearchStatus = nsIAutoCompleteController::STATUS_NONE;
mRowCount = 0;
mSearchesOngoing = 0;
// Initialize our list of search objects
PRUint32 searchCount;
aInput->GetSearchCount(&searchCount);
mResults.SetCapacity(searchCount);
mSearches.SetCapacity(searchCount);
mMatchCounts.SetLength(searchCount);
mImmediateSearchesCount = 0;
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;
aInput->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
nsCOMPtr<nsIAutoCompleteSearch> search = do_GetService(cid.get());
if (search) {
mSearches.AppendObject(search);
// Count immediate searches.
PRUint16 searchType = nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_DELAYED;
nsCOMPtr<nsIAutoCompleteSearchDescriptor> searchDesc =
do_QueryInterface(search);
if (searchDesc && NS_SUCCEEDED(searchDesc->GetSearchType(&searchType)) &&
searchType == nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_IMMEDIATE)
mImmediateSearchesCount++;
}
}
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::StartSearch(const nsAString &aSearchString)
{
mSearchString = aSearchString;
StartSearches();
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::HandleText()
{
// Note: the events occur in the following order when IME is used.
// 1. a compositionstart event(HandleStartComposition)
// 2. some input events (HandleText), eCompositionState_Composing
// 3. a compositionend event(HandleEndComposition)
// 4. an input event(HandleText), eCompositionState_Committing
// We should do nothing during composition.
if (mCompositionState == eCompositionState_Composing) {
return NS_OK;
}
bool handlingCompositionCommit =
(mCompositionState == eCompositionState_Committing);
bool popupClosedByCompositionStart = mPopupClosedByCompositionStart;
if (handlingCompositionCommit) {
mCompositionState = eCompositionState_None;
mPopupClosedByCompositionStart = false;
}
if (!mInput) {
// Stop all searches in case they are async.
StopSearch();
// Note: if now is after blur and IME end composition,
// check mInput before calling.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=193544#c31
NS_ERROR("Called before attaching to the control or after detaching from the control");
return NS_OK;
}
nsAutoString newValue;
nsCOMPtr<nsIAutoCompleteInput> input(mInput);
input->GetTextValue(newValue);
// Stop all searches in case they are async.
StopSearch();
if (!mInput) {
// StopSearch() can call PostSearchCleanup() which might result
// in a blur event, which could null out mInput, so we need to check it
// again. See bug #395344 for more details
return NS_OK;
}
bool disabled;
input->GetDisableAutoComplete(&disabled);
NS_ENSURE_TRUE(!disabled, NS_OK);
// Don't search again if the new string is the same as the last search
// However, if this is called immediately after compositionend event,
// we need to search the same value again since the search was canceled
// at compositionstart event handler.
if (!handlingCompositionCommit && 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 = true;
} else
mBackspaced = false;
mSearchString = newValue;
// Don't search if the value is empty
if (newValue.Length() == 0) {
// If autocomplete popup was closed by compositionstart event handler,
// we should reopen it forcibly even if the value is empty.
if (popupClosedByCompositionStart && handlingCompositionCommit) {
bool cancel;
HandleKeyNavigation(nsIDOMKeyEvent::DOM_VK_DOWN, &cancel);
return NS_OK;
}
ClosePopup();
return NS_OK;
}
StartSearches();
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::HandleEnter(bool aIsPopupSelection, bool *_retval)
{
*_retval = false;
if (!mInput)
return NS_OK;
// 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));
if (popup) {
PRInt32 selectedIndex;
popup->GetSelectedIndex(&selectedIndex);
*_retval = selectedIndex >= 0;
}
}
// Stop the search, and handle the enter.
StopSearch();
EnterMatch(aIsPopupSelection);
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::HandleEscape(bool *_retval)
{
*_retval = false;
if (!mInput)
return NS_OK;
// allow the event through if the popup is closed
mInput->GetPopupOpen(_retval);
// Stop all searches in case they are async.
StopSearch();
ClearResults();
RevertTextValue();
ClosePopup();
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::HandleStartComposition()
{
NS_ENSURE_TRUE(mCompositionState != eCompositionState_Composing, NS_OK);
mPopupClosedByCompositionStart = false;
mCompositionState = eCompositionState_Composing;
if (!mInput)
return NS_OK;
nsCOMPtr<nsIAutoCompleteInput> input(mInput);
bool disabled;
input->GetDisableAutoComplete(&disabled);
if (disabled)
return NS_OK;
// Stop all searches in case they are async.
StopSearch();
bool isOpen = false;
input->GetPopupOpen(&isOpen);
if (isOpen) {
ClosePopup();
bool stillOpen = false;
input->GetPopupOpen(&stillOpen);
mPopupClosedByCompositionStart = !stillOpen;
}
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::HandleEndComposition()
{
NS_ENSURE_TRUE(mCompositionState == eCompositionState_Composing, NS_OK);
// We can't yet retrieve the committed value from the editor, since it isn't
// completely committed yet. Set mCompositionState to
// eCompositionState_Committing, so that when HandleText() is called (in
// response to the "input" event), we know that we should handle the
// committed text.
mCompositionState = eCompositionState_Committing;
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::HandleTab()
{
bool cancel;
return HandleEnter(false, &cancel);
}
NS_IMETHODIMP
nsAutoCompleteController::HandleKeyNavigation(PRUint32 aKey, bool *_retval)
{
// By default, don't cancel the event
*_retval = false;
if (!mInput) {
// Stop all searches in case they are async.
StopSearch();
// Note: if now is after blur and IME end composition,
// check mInput before calling.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=193544#c31
NS_ERROR("Called before attaching to the control or after detaching from the control");
return NS_OK;
}
nsCOMPtr<nsIAutoCompleteInput> input(mInput);
nsCOMPtr<nsIAutoCompletePopup> popup;
input->GetPopup(getter_AddRefs(popup));
NS_ENSURE_TRUE(popup != nsnull, NS_ERROR_FAILURE);
bool disabled;
input->GetDisableAutoComplete(&disabled);
NS_ENSURE_TRUE(!disabled, NS_OK);
if (aKey == nsIDOMKeyEvent::DOM_VK_UP ||
aKey == nsIDOMKeyEvent::DOM_VK_DOWN ||
aKey == nsIDOMKeyEvent::DOM_VK_PAGE_UP ||
aKey == nsIDOMKeyEvent::DOM_VK_PAGE_DOWN)
{
// Prevent the input from handling up/down events, as it may move
// the cursor to home/end on some systems
*_retval = true;
bool isOpen = false;
input->GetPopupOpen(&isOpen);
if (isOpen) {
bool reverse = aKey == nsIDOMKeyEvent::DOM_VK_UP ||
aKey == nsIDOMKeyEvent::DOM_VK_PAGE_UP ? true : false;
bool page = aKey == nsIDOMKeyEvent::DOM_VK_PAGE_UP ||
aKey == nsIDOMKeyEvent::DOM_VK_PAGE_DOWN ? true : false;
// Fill in the value of the textbox with whatever is selected in the popup
// if the completeSelectedIndex attribute is set. We check this before
// calling SelectBy of an earlier attempt to avoid crashing.
bool completeSelection;
input->GetCompleteSelectedIndex(&completeSelection);
// Instruct the result view to scroll by the given amount and direction
popup->SelectBy(reverse, page);
if (completeSelection)
{
PRInt32 selectedIndex;
popup->GetSelectedIndex(&selectedIndex);
if (selectedIndex >= 0) {
// A result is selected, so fill in its value
nsAutoString value;
if (NS_SUCCEEDED(GetResultValueAt(selectedIndex, true, value))) {
input->SetTextValue(value);
input->SelectTextRange(value.Length(), value.Length());
}
} else {
// Nothing is selected, so fill in the last typed value
input->SetTextValue(mSearchString);
input->SelectTextRange(mSearchString.Length(), mSearchString.Length());
}
}
} else {
#ifdef XP_MACOSX
// on Mac, only show the popup if the caret is at the start or end of
// the input and there is no selection, so that the default defined key
// shortcuts for up and down move to the beginning and end of the field
// otherwise.
PRInt32 start, end;
if (aKey == nsIDOMKeyEvent::DOM_VK_UP) {
input->GetSelectionStart(&start);
input->GetSelectionEnd(&end);
if (start > 0 || start != end)
*_retval = false;
}
else if (aKey == nsIDOMKeyEvent::DOM_VK_DOWN) {
nsAutoString text;
input->GetTextValue(text);
input->GetSelectionStart(&start);
input->GetSelectionEnd(&end);
if (start != end || end < (PRInt32)text.Length())
*_retval = false;
}
#endif
if (*_retval) {
// Open the popup if there has been a previous search, or else kick off a new search
if (mResults.Count() > 0) {
if (mRowCount) {
OpenPopup();
}
} else {
// Stop all searches in case they are async.
StopSearch();
if (!mInput) {
// StopSearch() can call PostSearchCleanup() which might result
// in a blur event, which could null out mInput, so we need to check it
// again. See bug #395344 for more details
return NS_OK;
}
StartSearches();
}
}
}
} else if ( aKey == nsIDOMKeyEvent::DOM_VK_LEFT
|| aKey == nsIDOMKeyEvent::DOM_VK_RIGHT
#ifndef XP_MACOSX
|| aKey == nsIDOMKeyEvent::DOM_VK_HOME
#endif
)
{
// The user hit a text-navigation key.
bool isOpen = false;
input->GetPopupOpen(&isOpen);
if (isOpen) {
PRInt32 selectedIndex;
popup->GetSelectedIndex(&selectedIndex);
bool shouldComplete;
input->GetCompleteDefaultIndex(&shouldComplete);
if (selectedIndex >= 0) {
// The pop-up is open and has a selection, take its value
nsAutoString value;
if (NS_SUCCEEDED(GetResultValueAt(selectedIndex, true, value))) {
input->SetTextValue(value);
input->SelectTextRange(value.Length(), value.Length());
}
}
else if (shouldComplete) {
// We usually try to preserve the casing of what user has typed, but
// if he wants to autocomplete, we will replace the value with the
// actual autocomplete result.
// The user wants explicitely to use that result, so this ensures
// association of the result with the autocompleted text.
nsAutoString value;
nsAutoString inputValue;
input->GetTextValue(inputValue);
if (NS_SUCCEEDED(GetDefaultCompleteValue(-1, false, value)) &&
value.Equals(inputValue, nsCaseInsensitiveStringComparator())) {
input->SetTextValue(value);
input->SelectTextRange(value.Length(), value.Length());
}
}
// Close the pop-up even if nothing was selected
ClearSearchTimer();
ClosePopup();
}
// Update last-searched string to the current input, since the input may
// have changed. Without this, subsequent backspaces look like text
// additions, not text deletions.
nsAutoString value;
input->GetTextValue(value);
mSearchString = value;
}
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::HandleDelete(bool *_retval)
{
*_retval = false;
if (!mInput)
return NS_OK;
nsCOMPtr<nsIAutoCompleteInput> input(mInput);
bool isOpen = false;
input->GetPopupOpen(&isOpen);
if (!isOpen || mRowCount <= 0) {
// Nothing left to delete, proceed as normal
HandleText();
return NS_OK;
}
nsCOMPtr<nsIAutoCompletePopup> popup;
input->GetPopup(getter_AddRefs(popup));
PRInt32 index, searchIndex, rowIndex;
popup->GetSelectedIndex(&index);
RowIndexToSearch(index, &searchIndex, &rowIndex);
NS_ENSURE_TRUE(searchIndex >= 0 && rowIndex >= 0, NS_ERROR_FAILURE);
nsIAutoCompleteResult *result = mResults[searchIndex];
NS_ENSURE_TRUE(result, NS_ERROR_FAILURE);
nsAutoString search;
input->GetSearchParam(search);
// Clear the row in our result and in the DB.
result->RemoveValueAt(rowIndex, true);
--mRowCount;
// We removed it, so make sure we cancel the event that triggered this call.
*_retval = true;
// Unselect the current item.
popup->SetSelectedIndex(-1);
// Tell the tree that the row count changed.
if (mTree)
mTree->RowCountChanged(mRowCount, -1);
// Adjust index, if needed.
if (index >= (PRInt32)mRowCount)
index = mRowCount - 1;
if (mRowCount > 0) {
// There are still rows in the popup, select the current index again.
popup->SetSelectedIndex(index);
// Complete to the new current value.
bool shouldComplete = false;
mInput->GetCompleteDefaultIndex(&shouldComplete);
if (shouldComplete) {
nsAutoString value;
if (NS_SUCCEEDED(GetResultValueAt(index, true, value))) {
CompleteValue(value);
}
}
// Invalidate the popup.
popup->Invalidate();
} else {
// Nothing left in the popup, clear any pending search timers and
// close the popup.
ClearSearchTimer();
ClosePopup();
}
return NS_OK;
}
nsresult
nsAutoCompleteController::GetResultAt(PRInt32 aIndex, nsIAutoCompleteResult** aResult,
PRInt32* aRowIndex)
{
PRInt32 searchIndex;
RowIndexToSearch(aIndex, &searchIndex, aRowIndex);
NS_ENSURE_TRUE(searchIndex >= 0 && *aRowIndex >= 0, NS_ERROR_FAILURE);
*aResult = mResults[searchIndex];
NS_ENSURE_TRUE(*aResult, NS_ERROR_FAILURE);
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetValueAt(PRInt32 aIndex, nsAString & _retval)
{
GetResultLabelAt(aIndex, false, _retval);
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetLabelAt(PRInt32 aIndex, nsAString & _retval)
{
GetResultLabelAt(aIndex, false, _retval);
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetCommentAt(PRInt32 aIndex, nsAString & _retval)
{
PRInt32 rowIndex;
nsIAutoCompleteResult* result;
nsresult rv = GetResultAt(aIndex, &result, &rowIndex);
NS_ENSURE_SUCCESS(rv, rv);
return result->GetCommentAt(rowIndex, _retval);
}
NS_IMETHODIMP
nsAutoCompleteController::GetStyleAt(PRInt32 aIndex, nsAString & _retval)
{
PRInt32 rowIndex;
nsIAutoCompleteResult* result;
nsresult rv = GetResultAt(aIndex, &result, &rowIndex);
NS_ENSURE_SUCCESS(rv, rv);
return result->GetStyleAt(rowIndex, _retval);
}
NS_IMETHODIMP
nsAutoCompleteController::GetImageAt(PRInt32 aIndex, nsAString & _retval)
{
PRInt32 rowIndex;
nsIAutoCompleteResult* result;
nsresult rv = GetResultAt(aIndex, &result, &rowIndex);
NS_ENSURE_SUCCESS(rv, rv);
return result->GetImageAt(rowIndex, _retval);
}
NS_IMETHODIMP
nsAutoCompleteController::SetSearchString(const nsAString &aSearchString)
{
mSearchString = aSearchString;
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetSearchString(nsAString &aSearchString)
{
aSearchString = mSearchString;
return NS_OK;
}
////////////////////////////////////////////////////////////////////////
//// nsIAutoCompleteObserver
NS_IMETHODIMP
nsAutoCompleteController::OnUpdateSearchResult(nsIAutoCompleteSearch *aSearch, nsIAutoCompleteResult* aResult)
{
ClearResults();
return OnSearchResult(aSearch, aResult);
}
NS_IMETHODIMP
nsAutoCompleteController::OnSearchResult(nsIAutoCompleteSearch *aSearch, nsIAutoCompleteResult* aResult)
{
// look up the index of the search which is returning
PRUint32 count = mSearches.Count();
for (PRUint32 i = 0; i < count; ++i) {
if (mSearches[i] == aSearch) {
ProcessResult(i, aResult);
}
}
return NS_OK;
}
////////////////////////////////////////////////////////////////////////
//// nsITimerCallback
NS_IMETHODIMP
nsAutoCompleteController::Notify(nsITimer *timer)
{
mTimer = nsnull;
if (mImmediateSearchesCount == 0) {
// If there were no immediate searches, BeforeSearches has not yet been
// called, so do it now.
nsresult rv = BeforeSearches();
if (NS_FAILED(rv))
return rv;
}
StartSearch(nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_DELAYED);
AfterSearches();
return NS_OK;
}
////////////////////////////////////////////////////////////////////////
// nsITreeView
NS_IMETHODIMP
nsAutoCompleteController::GetRowCount(PRInt32 *aRowCount)
{
*aRowCount = mRowCount;
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetRowProperties(PRInt32 index, nsISupportsArray *properties)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetCellProperties(PRInt32 row, nsITreeColumn* col, nsISupportsArray* properties)
{
if (row >= 0) {
nsAutoString className;
GetStyleAt(row, className);
if (!className.IsEmpty()) {
nsCOMPtr<nsIAtom> atom(do_GetAtom(className));
properties->AppendElement(atom);
}
}
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetColumnProperties(nsITreeColumn* col, nsISupportsArray* properties)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetImageSrc(PRInt32 row, nsITreeColumn* col, nsAString& _retval)
{
const PRUnichar* colID;
col->GetIdConst(&colID);
if (NS_LITERAL_STRING("treecolAutoCompleteValue").Equals(colID))
return GetImageAt(row, _retval);
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetProgressMode(PRInt32 row, nsITreeColumn* col, PRInt32* _retval)
{
NS_NOTREACHED("tree has no progress cells");
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetCellValue(PRInt32 row, nsITreeColumn* col, nsAString& _retval)
{
NS_NOTREACHED("all of our cells are text");
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::GetCellText(PRInt32 row, nsITreeColumn* col, nsAString& _retval)
{
const PRUnichar* colID;
col->GetIdConst(&colID);
if (NS_LITERAL_STRING("treecolAutoCompleteValue").Equals(colID))
GetValueAt(row, _retval);
else if (NS_LITERAL_STRING("treecolAutoCompleteComment").Equals(colID))
GetCommentAt(row, _retval);
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::IsContainer(PRInt32 index, bool *_retval)
{
*_retval = false;
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::IsContainerOpen(PRInt32 index, bool *_retval)
{
NS_NOTREACHED("no container cells");
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::IsContainerEmpty(PRInt32 index, bool *_retval)
{
NS_NOTREACHED("no container cells");
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 = -1;
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::HasNextSibling(PRInt32 rowIndex, PRInt32 afterIndex, bool *_retval)
{
*_retval = false;
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::ToggleOpenState(PRInt32 index)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::SetTree(nsITreeBoxObject *tree)
{
mTree = 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::SetCellValue(PRInt32 row, nsITreeColumn* col, const nsAString& value)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::SetCellText(PRInt32 row, nsITreeColumn* col, const nsAString& value)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::CycleHeader(nsITreeColumn* col)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::CycleCell(PRInt32 row, nsITreeColumn* col)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::IsEditable(PRInt32 row, nsITreeColumn* col, bool *_retval)
{
*_retval = false;
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::IsSelectable(PRInt32 row, nsITreeColumn* col, bool *_retval)
{
*_retval = false;
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::IsSeparator(PRInt32 index, bool *_retval)
{
*_retval = false;
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::IsSorted(bool *_retval)
{
*_retval = false;
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::CanDrop(PRInt32 index, PRInt32 orientation,
nsIDOMDataTransfer* dataTransfer, bool *_retval)
{
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteController::Drop(PRInt32 row, PRInt32 orientation, nsIDOMDataTransfer* dataTransfer)
{
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, nsITreeColumn* col)
{
return NS_OK;
}
////////////////////////////////////////////////////////////////////////
//// nsAutoCompleteController
nsresult
nsAutoCompleteController::OpenPopup()
{
PRUint32 minResults;
mInput->GetMinResultsForPopup(&minResults);
if (mRowCount >= minResults) {
return mInput->SetPopupOpen(true);
}
return NS_OK;
}
nsresult
nsAutoCompleteController::ClosePopup()
{
if (!mInput) {
return NS_OK;
}
bool isOpen = false;
mInput->GetPopupOpen(&isOpen);
if (!isOpen)
return NS_OK;
nsCOMPtr<nsIAutoCompletePopup> popup;
mInput->GetPopup(getter_AddRefs(popup));
NS_ENSURE_TRUE(popup != nsnull, NS_ERROR_FAILURE);
popup->SetSelectedIndex(-1);
return mInput->SetPopupOpen(false);
}
nsresult
nsAutoCompleteController::BeforeSearches()
{
NS_ENSURE_STATE(mInput);
mSearchStatus = nsIAutoCompleteController::STATUS_SEARCHING;
mDefaultIndexCompleted = false;
// The first search result will clear mResults array, though we should pass
// the previous result to each search to allow them to reuse it. So we
// temporarily cache current results till AfterSearches().
if (!mResultCache.AppendObjects(mResults)) {
return NS_ERROR_OUT_OF_MEMORY;
}
mSearchesOngoing = mSearches.Count();
mSearchesFailed = 0;
mFirstSearchResult = true;
// notify the input that the search is beginning
mInput->OnSearchBegin();
return NS_OK;
}
nsresult
nsAutoCompleteController::StartSearch(PRUint16 aSearchType)
{
NS_ENSURE_STATE(mInput);
nsCOMPtr<nsIAutoCompleteInput> input = mInput;
for (PRInt32 i = 0; i < mSearches.Count(); ++i) {
nsCOMPtr<nsIAutoCompleteSearch> search = mSearches[i];
// Filter on search type. Not all the searches implement this interface,
// in such a case just consider them delayed.
PRUint16 searchType = nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_DELAYED;
nsCOMPtr<nsIAutoCompleteSearchDescriptor> searchDesc =
do_QueryInterface(search);
if (searchDesc)
searchDesc->GetSearchType(&searchType);
if (searchType != aSearchType)
continue;
nsIAutoCompleteResult *result = mResultCache.SafeObjectAt(i);
if (result) {
PRUint16 searchResult;
result->GetSearchResult(&searchResult);
if (searchResult != nsIAutoCompleteResult::RESULT_SUCCESS &&
searchResult != nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING &&
searchResult != nsIAutoCompleteResult::RESULT_NOMATCH)
result = nsnull;
}
nsAutoString searchParam;
nsresult rv = input->GetSearchParam(searchParam);
if (NS_FAILED(rv))
return rv;
rv = search->StartSearch(mSearchString, searchParam, result, static_cast<nsIAutoCompleteObserver *>(this));
if (NS_FAILED(rv)) {
++mSearchesFailed;
--mSearchesOngoing;
}
// Because of the joy of nested event loops (which can easily happen when some
// code uses a generator for an asynchronous AutoComplete search),
// nsIAutoCompleteSearch::StartSearch might cause us to be detached from our input
// field. The next time we iterate, we'd be touching something that we shouldn't
// be, and result in a crash.
if (!mInput) {
// The search operation has been finished.
return NS_OK;
}
}
return NS_OK;
}
void
nsAutoCompleteController::AfterSearches()
{
mResultCache.Clear();
if (mSearchesFailed == mSearches.Count())
PostSearchCleanup();
}
NS_IMETHODIMP
nsAutoCompleteController::StopSearch()
{
// Stop the timer if there is one
ClearSearchTimer();
// Stop any ongoing asynchronous searches
if (mSearchStatus == nsIAutoCompleteController::STATUS_SEARCHING) {
PRUint32 count = mSearches.Count();
for (PRUint32 i = 0; i < count; ++i) {
nsCOMPtr<nsIAutoCompleteSearch> search = mSearches[i];
search->StopSearch();
}
mSearchesOngoing = 0;
// since we were searching, but now we've stopped,
// we need to call PostSearchCleanup()
PostSearchCleanup();
}
return NS_OK;
}
nsresult
nsAutoCompleteController::StartSearches()
{
// Don't create a new search timer if we're already waiting for one to fire.
// If we don't check for this, we won't be able to cancel the original timer
// and may crash when it fires (bug 236659).
if (mTimer || !mInput)
return NS_OK;
// Get the timeout for delayed searches.
PRUint32 timeout;
mInput->GetTimeout(&timeout);
PRUint32 immediateSearchesCount = mImmediateSearchesCount;
if (timeout == 0) {
// All the searches should be executed immediately.
immediateSearchesCount = mSearches.Count();
}
if (immediateSearchesCount > 0) {
nsresult rv = BeforeSearches();
if (NS_FAILED(rv))
return rv;
StartSearch(nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_IMMEDIATE);
if (mSearches.Count() == immediateSearchesCount) {
// Either all searches are immediate, or the timeout is 0. In the
// latter case we still have to execute the delayed searches, otherwise
// this will be a no-op.
StartSearch(nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_DELAYED);
// All the searches have been started, just finish.
AfterSearches();
return NS_OK;
}
}
MOZ_ASSERT(timeout > 0, "Trying to delay searches with a 0 timeout!");
// Now start the delayed searches.
nsresult rv;
mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
if (NS_FAILED(rv))
return rv;
rv = mTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT);
if (NS_FAILED(rv))
mTimer = nsnull;
return rv;
}
nsresult
nsAutoCompleteController::ClearSearchTimer()
{
if (mTimer) {
mTimer->Cancel();
mTimer = nsnull;
}
return NS_OK;
}
nsresult
nsAutoCompleteController::EnterMatch(bool aIsPopupSelection)
{
nsCOMPtr<nsIAutoCompleteInput> input(mInput);
nsCOMPtr<nsIAutoCompletePopup> popup;
input->GetPopup(getter_AddRefs(popup));
NS_ENSURE_TRUE(popup != nsnull, NS_ERROR_FAILURE);
bool forceComplete;
input->GetForceComplete(&forceComplete);
// Ask the popup if it wants to enter a special value into the textbox
nsAutoString value;
popup->GetOverrideValue(value);
if (value.IsEmpty()) {
bool shouldComplete;
mInput->GetCompleteDefaultIndex(&shouldComplete);
bool completeSelection;
input->GetCompleteSelectedIndex(&completeSelection);
// If completeselectedindex is false or a row was selected from the popup,
// enter it into the textbox. If completeselectedindex is true, or
// EnterMatch was called via other means, for instance pressing Enter,
// don't fill in the value as it will have already been filled in as needed.
PRInt32 selectedIndex;
popup->GetSelectedIndex(&selectedIndex);
if (selectedIndex >= 0 && (!completeSelection || aIsPopupSelection))
GetResultValueAt(selectedIndex, true, value);
else if (shouldComplete) {
// We usually try to preserve the casing of what user has typed, but
// if he wants to autocomplete, we will replace the value with the
// actual autocomplete result.
// The user wants explicitely to use that result, so this ensures
// association of the result with the autocompleted text.
nsAutoString defaultIndexValue;
nsAutoString inputValue;
input->GetTextValue(inputValue);
if (NS_SUCCEEDED(GetDefaultCompleteValue(-1, false, defaultIndexValue)) &&
defaultIndexValue.Equals(inputValue, nsCaseInsensitiveStringComparator()))
value = defaultIndexValue;
}
if (forceComplete && value.IsEmpty()) {
// Since nothing was selected, and forceComplete is specified, that means
// we have to find the first default match and enter it instead
PRUint32 count = mResults.Count();
for (PRUint32 i = 0; i < count; ++i) {
nsIAutoCompleteResult *result = mResults[i];
if (result) {
PRInt32 defaultIndex;
result->GetDefaultIndex(&defaultIndex);
if (defaultIndex >= 0) {
result->GetValueAt(defaultIndex, value);
break;
}
}
}
}
}
nsCOMPtr<nsIObserverService> obsSvc =
mozilla::services::GetObserverService();
NS_ENSURE_STATE(obsSvc);
obsSvc->NotifyObservers(input, "autocomplete-will-enter-text", nsnull);
if (!value.IsEmpty()) {
input->SetTextValue(value);
input->SelectTextRange(value.Length(), value.Length());
mSearchString = value;
}
obsSvc->NotifyObservers(input, "autocomplete-did-enter-text", nsnull);
ClosePopup();
bool cancel;
input->OnTextEntered(&cancel);
return NS_OK;
}
nsresult
nsAutoCompleteController::RevertTextValue()
{
// StopSearch() can call PostSearchCleanup() which might result
// in a blur event, which could null out mInput, so we need to check it
// again. See bug #408463 for more details
if (!mInput)
return NS_OK;
nsAutoString oldValue(mSearchString);
nsCOMPtr<nsIAutoCompleteInput> input(mInput);
bool cancel = false;
input->OnTextReverted(&cancel);
if (!cancel) {
nsCOMPtr<nsIObserverService> obsSvc =
mozilla::services::GetObserverService();
NS_ENSURE_STATE(obsSvc);
obsSvc->NotifyObservers(input, "autocomplete-will-revert-text", nsnull);
nsAutoString inputValue;
input->GetTextValue(inputValue);
// Don't change the value if it is the same to prevent sending useless events.
// NOTE: how can |RevertTextValue| be called with inputValue != oldValue?
if (!oldValue.Equals(inputValue)) {
input->SetTextValue(oldValue);
}
obsSvc->NotifyObservers(input, "autocomplete-did-revert-text", nsnull);
}
return NS_OK;
}
nsresult
nsAutoCompleteController::ProcessResult(PRInt32 aSearchIndex, nsIAutoCompleteResult *aResult)
{
NS_ENSURE_STATE(mInput);
nsCOMPtr<nsIAutoCompleteInput> input(mInput);
// If this is the first search result we are processing
// we should clear out the previously cached results
if (mFirstSearchResult) {
ClearResults();
mFirstSearchResult = false;
}
PRUint16 result = 0;
if (aResult)
aResult->GetSearchResult(&result);
// if our results are incremental, the search is still ongoing
if (result != nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING &&
result != nsIAutoCompleteResult::RESULT_NOMATCH_ONGOING) {
--mSearchesOngoing;
}
PRUint32 oldMatchCount = 0;
PRUint32 matchCount = 0;
if (aResult)
aResult->GetMatchCount(&matchCount);
PRInt32 resultIndex = mResults.IndexOf(aResult);
if (resultIndex == -1) {
// cache the result
mResults.AppendObject(aResult);
mMatchCounts.AppendElement(matchCount);
resultIndex = mResults.Count() - 1;
}
else {
oldMatchCount = mMatchCounts[aSearchIndex];
mMatchCounts[resultIndex] = matchCount;
}
bool isTypeAheadResult = false;
if (aResult) {
aResult->GetTypeAheadResult(&isTypeAheadResult);
}
if (!isTypeAheadResult) {
PRUint32 oldRowCount = mRowCount;
// If the search failed, increase the match count to include the error
// description.
if (result == nsIAutoCompleteResult::RESULT_FAILURE) {
nsAutoString error;
aResult->GetErrorDescription(error);
if (!error.IsEmpty()) {
++mRowCount;
if (mTree) {
mTree->RowCountChanged(oldRowCount, 1);
}
}
} else if (result == nsIAutoCompleteResult::RESULT_SUCCESS ||
result == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
// Increase the match count for all matches in this result.
mRowCount += matchCount - oldMatchCount;
if (mTree) {
mTree->RowCountChanged(oldRowCount, matchCount - oldMatchCount);
}
}
// Refresh the popup view to display the new search results
nsCOMPtr<nsIAutoCompletePopup> popup;
input->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. Don't force the popup closed if we might
// get results in the future to avoid unnecessarily canceling searches.
if (mRowCount) {
OpenPopup();
} else if (result != nsIAutoCompleteResult::RESULT_NOMATCH_ONGOING) {
ClosePopup();
}
}
if (result == nsIAutoCompleteResult::RESULT_SUCCESS ||
result == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
// Try to autocomplete the default index for this search.
CompleteDefaultIndex(resultIndex);
}
if (mSearchesOngoing == 0) {
// If this is the last search to return, cleanup.
PostSearchCleanup();
}
return NS_OK;
}
nsresult
nsAutoCompleteController::PostSearchCleanup()
{
NS_ENSURE_STATE(mInput);
nsCOMPtr<nsIAutoCompleteInput> input(mInput);
PRUint32 minResults;
mInput->GetMinResultsForPopup(&minResults);
if (mRowCount || minResults == 0) {
OpenPopup();
if (mRowCount)
mSearchStatus = nsIAutoCompleteController::STATUS_COMPLETE_MATCH;
else
mSearchStatus = nsIAutoCompleteController::STATUS_COMPLETE_NO_MATCH;
} else {
mSearchStatus = nsIAutoCompleteController::STATUS_COMPLETE_NO_MATCH;
ClosePopup();
}
// notify the input that the search is complete
input->OnSearchComplete();
return NS_OK;
}
nsresult
nsAutoCompleteController::ClearResults()
{
PRInt32 oldRowCount = mRowCount;
mRowCount = 0;
mResults.Clear();
mMatchCounts.Clear();
if (oldRowCount != 0) {
if (mTree)
mTree->RowCountChanged(0, -oldRowCount);
else if (mInput) {
nsCOMPtr<nsIAutoCompletePopup> popup;
mInput->GetPopup(getter_AddRefs(popup));
NS_ENSURE_TRUE(popup != nsnull, NS_ERROR_FAILURE);
// if we had a tree, RowCountChanged() would have cleared the selection
// when the selected row was removed. But since we don't have a tree,
// we need to clear the selection manually.
popup->SetSelectedIndex(-1);
}
}
return NS_OK;
}
nsresult
nsAutoCompleteController::CompleteDefaultIndex(PRInt32 aResultIndex)
{
if (mDefaultIndexCompleted || mBackspaced || mSearchString.Length() == 0 || !mInput)
return NS_OK;
PRInt32 selectionStart;
mInput->GetSelectionStart(&selectionStart);
PRInt32 selectionEnd;
mInput->GetSelectionEnd(&selectionEnd);
// Don't try to automatically complete to the first result if there's already
// a selection or the cursor isn't at the end of the input
if (selectionEnd != selectionStart ||
selectionEnd != (PRInt32)mSearchString.Length())
return NS_OK;
bool shouldComplete;
mInput->GetCompleteDefaultIndex(&shouldComplete);
if (!shouldComplete)
return NS_OK;
nsAutoString resultValue;
if (NS_SUCCEEDED(GetDefaultCompleteValue(aResultIndex, true, resultValue)))
CompleteValue(resultValue);
mDefaultIndexCompleted = true;
return NS_OK;
}
nsresult
nsAutoCompleteController::GetDefaultCompleteValue(PRInt32 aResultIndex,
bool aPreserveCasing,
nsAString &_retval)
{
PRInt32 defaultIndex = -1;
PRInt32 index = aResultIndex;
if (index < 0) {
PRUint32 count = mResults.Count();
for (PRUint32 i = 0; i < count; ++i) {
nsIAutoCompleteResult *result = mResults[i];
if (result && NS_SUCCEEDED(result->GetDefaultIndex(&defaultIndex)) &&
defaultIndex >= 0) {
index = i;
break;
}
}
}
NS_ENSURE_TRUE(index >= 0, NS_ERROR_FAILURE);
nsIAutoCompleteResult *result = mResults.SafeObjectAt(index);
NS_ENSURE_TRUE(result != nsnull, NS_ERROR_FAILURE);
if (defaultIndex < 0) {
// The search must explicitly provide a default index in order
// for us to be able to complete.
result->GetDefaultIndex(&defaultIndex);
}
if (defaultIndex < 0) {
// We were given a result index, but that result doesn't want to
// be autocompleted.
return NS_ERROR_FAILURE;
}
// If the result wrongly notifies a RESULT_SUCCESS with no matches, or
// provides a defaultIndex greater than its matchCount, avoid trying to
// complete to an empty value.
PRUint32 matchCount = 0;
result->GetMatchCount(&matchCount);
// Here defaultIndex is surely non-negative, so can be cast to unsigned.
if ((PRUint32)defaultIndex >= matchCount)
return NS_ERROR_FAILURE;
nsAutoString resultValue;
result->GetValueAt(defaultIndex, resultValue);
if (aPreserveCasing &&
StringBeginsWith(resultValue, mSearchString,
nsCaseInsensitiveStringComparator())) {
// We try to preserve user casing, otherwise we would end up changing
// the case of what he typed, if we have a result with a different casing.
// For example if we have result "Test", and user starts writing "tuna",
// after digiting t, we would convert it to T trying to autocomplete "Test".
// We will still complete to cased "Test" if the user explicitely choose
// that result, by either selecting it in the results popup, or with
// keyboard navigation or if autocompleting in the middle.
nsAutoString casedResultValue;
casedResultValue.Assign(mSearchString);
// Use what the user has typed so far.
casedResultValue.Append(Substring(resultValue,
mSearchString.Length(),
resultValue.Length()));
_retval = casedResultValue;
}
else
_retval = resultValue;
return NS_OK;
}
nsresult
nsAutoCompleteController::CompleteValue(nsString &aValue)
/* mInput contains mSearchString, which we want to autocomplete to aValue. If
* selectDifference is true, select the remaining portion of aValue not
* contained in mSearchString. */
{
const PRInt32 mSearchStringLength = mSearchString.Length();
PRInt32 endSelect = aValue.Length(); // By default, select all of aValue.
if (aValue.IsEmpty() ||
StringBeginsWith(aValue, mSearchString,
nsCaseInsensitiveStringComparator())) {
// aValue is empty (we were asked to clear mInput), or mSearchString
// matches the beginning of aValue. In either case we can simply
// autocomplete to aValue.
mInput->SetTextValue(aValue);
} else {
nsresult rv;
nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCAutoString scheme;
if (NS_SUCCEEDED(ios->ExtractScheme(NS_ConvertUTF16toUTF8(aValue), scheme))) {
// Trying to autocomplete a URI from somewhere other than the beginning.
// Only succeed if the missing portion is "http://"; otherwise do not
// autocomplete. This prevents us from "helpfully" autocompleting to a
// URI that isn't equivalent to what the user expected.
const PRInt32 findIndex = 7; // length of "http://"
if ((endSelect < findIndex + mSearchStringLength) ||
!scheme.LowerCaseEqualsLiteral("http") ||
!Substring(aValue, findIndex, mSearchStringLength).Equals(
mSearchString, nsCaseInsensitiveStringComparator())) {
return NS_OK;
}
mInput->SetTextValue(mSearchString +
Substring(aValue, mSearchStringLength + findIndex,
endSelect));
endSelect -= findIndex; // We're skipping this many characters of aValue.
} else {
// Autocompleting something other than a URI from the middle.
// Use the format "searchstring >> full string" to indicate to the user
// what we are going to replace their search string with.
mInput->SetTextValue(mSearchString + NS_LITERAL_STRING(" >> ") + aValue);
endSelect = mSearchString.Length() + 4 + aValue.Length();
}
}
mInput->SelectTextRange(mSearchStringLength, endSelect);
return NS_OK;
}
nsresult
nsAutoCompleteController::GetResultLabelAt(PRInt32 aIndex, bool aValueOnly, nsAString & _retval)
{
return GetResultValueLabelAt(aIndex, aValueOnly, false, _retval);
}
nsresult
nsAutoCompleteController::GetResultValueAt(PRInt32 aIndex, bool aValueOnly, nsAString & _retval)
{
return GetResultValueLabelAt(aIndex, aValueOnly, true, _retval);
}
nsresult
nsAutoCompleteController::GetResultValueLabelAt(PRInt32 aIndex, bool aValueOnly,
bool aGetValue, nsAString & _retval)
{
NS_ENSURE_TRUE(aIndex >= 0 && (PRUint32) aIndex < mRowCount, NS_ERROR_ILLEGAL_VALUE);
PRInt32 rowIndex;
nsIAutoCompleteResult *result;
nsresult rv = GetResultAt(aIndex, &result, &rowIndex);
NS_ENSURE_SUCCESS(rv, rv);
PRUint16 searchResult;
result->GetSearchResult(&searchResult);
if (searchResult == nsIAutoCompleteResult::RESULT_FAILURE) {
if (aValueOnly)
return NS_ERROR_FAILURE;
result->GetErrorDescription(_retval);
} else if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS ||
searchResult == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
if (aGetValue)
result->GetValueAt(rowIndex, _retval);
else
result->GetLabelAt(rowIndex, _retval);
}
return NS_OK;
}
/**
* Given the index of a row in the autocomplete popup, find the
* corresponding nsIAutoCompleteSearch index, and sub-index into
* the search's results list.
*/
nsresult
nsAutoCompleteController::RowIndexToSearch(PRInt32 aRowIndex, PRInt32 *aSearchIndex, PRInt32 *aItemIndex)
{
*aSearchIndex = -1;
*aItemIndex = -1;
PRUint32 count = mSearches.Count();
PRUint32 index = 0;
// Move index through the results of each registered nsIAutoCompleteSearch
// until we find the given row
for (PRUint32 i = 0; i < count; ++i) {
nsIAutoCompleteResult *result = mResults.SafeObjectAt(i);
if (!result)
continue;
PRUint32 rowCount = 0;
// Skip past the result completely if it is marked as hidden
bool isTypeAheadResult = false;
result->GetTypeAheadResult(&isTypeAheadResult);
if (!isTypeAheadResult) {
PRUint16 searchResult;
result->GetSearchResult(&searchResult);
// Find out how many results were provided by the
// current nsIAutoCompleteSearch.
if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS ||
searchResult == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
result->GetMatchCount(&rowCount);
}
}
// If the given row index is within the results range
// of the current nsIAutoCompleteSearch then return the
// search index and sub-index into the results array
if ((rowCount != 0) && (index + rowCount-1 >= (PRUint32) aRowIndex)) {
*aSearchIndex = i;
*aItemIndex = aRowIndex - index;
return NS_OK;
}
// Advance the popup table index cursor past the
// results of the current search.
index += rowCount;
}
return NS_OK;
}
NS_GENERIC_FACTORY_CONSTRUCTOR(nsAutoCompleteController)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsAutoCompleteSimpleResult)
NS_DEFINE_NAMED_CID(NS_AUTOCOMPLETECONTROLLER_CID);
NS_DEFINE_NAMED_CID(NS_AUTOCOMPLETESIMPLERESULT_CID);
static const mozilla::Module::CIDEntry kAutoCompleteCIDs[] = {
{ &kNS_AUTOCOMPLETECONTROLLER_CID, false, NULL, nsAutoCompleteControllerConstructor },
{ &kNS_AUTOCOMPLETESIMPLERESULT_CID, false, NULL, nsAutoCompleteSimpleResultConstructor },
{ NULL }
};
static const mozilla::Module::ContractIDEntry kAutoCompleteContracts[] = {
{ NS_AUTOCOMPLETECONTROLLER_CONTRACTID, &kNS_AUTOCOMPLETECONTROLLER_CID },
{ NS_AUTOCOMPLETESIMPLERESULT_CONTRACTID, &kNS_AUTOCOMPLETESIMPLERESULT_CID },
{ NULL }
};
static const mozilla::Module kAutoCompleteModule = {
mozilla::Module::kVersion,
kAutoCompleteCIDs,
kAutoCompleteContracts
};
NSMODULE_DEFN(tkAutoCompleteModule) = &kAutoCompleteModule;