/* -*- Mode: C++; tab-width: 4; 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.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Pierre Phaneuf * * Alternatively, the contents of this file may be used under the terms of * either of 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 "msgCore.h" #include "nsAbAutoCompleteSession.h" #include "nsString.h" #include "nsRDFCID.h" #include "nsIRDFService.h" #include "nsIAbDirectory.h" #include "nsIAbCard.h" #include "nsXPIDLString.h" #include "nsReadableUtils.h" #include "nsUnicharUtils.h" #include "nsMsgBaseCID.h" #include "nsMsgI18N.h" #include "nsIMsgIdentity.h" #include "prmem.h" #include "nsNetCID.h" #include "nsIIOService.h" #include "nsIPrefService.h" #include "nsIPrefBranch.h" NS_IMPL_ISUPPORTS2(nsAbAutoCompleteSession, nsIAbAutoCompleteSession, nsIAutoCompleteSession) nsAbAutoCompleteSession::nsAbAutoCompleteSession() { mParser = do_GetService(NS_MAILNEWS_MIME_HEADER_PARSER_CONTRACTID); } nsAbAutoCompleteSession::~nsAbAutoCompleteSession() { } PRBool nsAbAutoCompleteSession::ItsADuplicate(PRUnichar* fullAddrStr, PRInt32 aPopularityIndex, nsIAutoCompleteResults* results) { nsresult rv; nsCOMPtr array; rv = results->GetItems(getter_AddRefs(array)); if (NS_SUCCEEDED(rv)) { nsCOMPtr enumerator; rv = array->Enumerate(getter_AddRefs(enumerator)); if (NS_SUCCEEDED(rv)) { nsCOMPtr item; nsCOMPtr resultItem; nsAutoString valueStr; for (rv = enumerator->First(); NS_SUCCEEDED(rv); rv = enumerator->Next()) { rv = enumerator->CurrentItem(getter_AddRefs(item)); if (NS_SUCCEEDED(rv) && item) { resultItem = do_QueryInterface(item, &rv); if (NS_SUCCEEDED(rv)) { rv = resultItem->GetValue(valueStr); if (NS_SUCCEEDED(rv) && !valueStr.IsEmpty() && nsDependentString(fullAddrStr).Equals(valueStr, nsCaseInsensitiveStringComparator())) { // ok, we have a duplicate, but before we ignore the dupe, check the popularity index // and use the card that is the most popular so it gets sorted correctly nsCOMPtr currentItemParams; rv = resultItem->GetParam(getter_AddRefs(currentItemParams)); if (NS_SUCCEEDED(rv)) { nsAbAutoCompleteParam *param = (nsAbAutoCompleteParam *)(void *)currentItemParams; if (aPopularityIndex > param->mPopularityIndex) { // remove the current autocomplete result, and return false so our dupe // gets added in its place. array->RemoveElement(item); break; } } // it's a dupe, ignore it. return PR_TRUE; } } } } } } return PR_FALSE; } void nsAbAutoCompleteSession::AddToResult(const PRUnichar* pNickNameStr, const PRUnichar* pDisplayNameStr, const PRUnichar* pFirstNameStr, const PRUnichar* pLastNameStr, const PRUnichar* pEmailStr, const PRUnichar* pNotesStr, const PRUnichar* pDirName, PRUint32 aPopularityIndex, PRBool bIsMailList, PRBool pDefaultMatch, nsIAutoCompleteResults* results) { nsresult rv; PRUnichar* fullAddrStr = nsnull; if (pDefaultMatch) { if (mDefaultDomain[0] == 0) return; nsAutoString aStr(pDisplayNameStr); if (aStr.FindChar('@') == kNotFound) { aStr.Append(PRUnichar('@')); aStr += mDefaultDomain; } fullAddrStr = ToNewUnicode(aStr); } else { if (mParser) { nsXPIDLCString fullAddress; nsXPIDLCString utf8Email; if (bIsMailList) { if (pNotesStr && pNotesStr[0] != 0) utf8Email.Adopt(ToNewUTF8String(nsDependentString(pNotesStr))); else utf8Email.Adopt(ToNewUTF8String(nsDependentString(pDisplayNameStr))); } else utf8Email.Adopt(ToNewUTF8String(nsDependentString(pEmailStr))); mParser->MakeFullAddress(nsnull, NS_ConvertUCS2toUTF8(pDisplayNameStr).get(), utf8Email, getter_Copies(fullAddress)); if (!fullAddress.IsEmpty()) { /* We need to convert back the result from UTF-8 to Unicode */ fullAddrStr = nsCRT::strdup(NS_ConvertUTF8toUCS2(fullAddress.get()).get()); } } if (!fullAddrStr) { //oops, parser problem! I will try to do my best... const PRUnichar * pStr = nsnull; if (bIsMailList) { if (pNotesStr && pNotesStr[0] != 0) pStr = pNotesStr; else pStr = pDisplayNameStr; } else pStr = pEmailStr; // check this so we do not get a bogus entry "someName <>" if (pStr && pStr[0] != 0) { nsAutoString aStr(pDisplayNameStr); aStr.AppendLiteral(" <"); aStr += pStr; aStr.AppendLiteral(">"); fullAddrStr = ToNewUnicode(aStr); } else fullAddrStr = nsnull; } } if (fullAddrStr && ! ItsADuplicate(fullAddrStr, aPopularityIndex, results)) { nsCOMPtr newItem = do_CreateInstance(NS_AUTOCOMPLETEITEM_CONTRACTID, &rv); if (NS_SUCCEEDED(rv)) { nsAbAutoCompleteParam *param = new nsAbAutoCompleteParam(pNickNameStr, pDisplayNameStr, pFirstNameStr, pLastNameStr, pEmailStr, pNotesStr, pDirName, aPopularityIndex, bIsMailList); NS_IF_ADDREF(param); newItem->SetParam(param); NS_IF_RELEASE(param); // how to process the comment column, if at all. this value // comes from "mail.autoComplete.commentColumn", or, if that // doesn't exist, defaults to 0 // // 0 = none // 1 = name of addressbook this card came from // 2 = other per-addressbook format (currrently unused here) // if (mAutoCompleteCommentColumn == 1) { rv = newItem->SetComment(pDirName); if (NS_FAILED(rv)) { NS_WARNING("nsAbAutoCompleteSession::AddToResult():" " newItem->SetComment() failed\n"); } } // if this isn't a default match, set the class name so we can style // this cell with the local addressbook icon (or whatever) // rv = newItem->SetClassName(pDefaultMatch ? "default-match" : "local-abook"); if (NS_FAILED(rv)) { NS_WARNING("nsAbAutoCompleteSession::AddToResult():" " newItem->SetClassName() failed\n"); } newItem->SetValue(nsDependentString(fullAddrStr)); nsCOMPtr array; rv = results->GetItems(getter_AddRefs(array)); if (NS_SUCCEEDED(rv)) { PRUint32 nbrOfItems; rv = array->Count(&nbrOfItems); PRInt32 insertPosition = 0; for (; insertPosition < nbrOfItems && !pDefaultMatch; insertPosition++) { nsCOMPtr currentItemParams; nsCOMPtr resultItem; nsresult rv = array->QueryElementAt(insertPosition, NS_GET_IID(nsIAutoCompleteItem), getter_AddRefs(resultItem)); if (NS_FAILED(rv)) continue; rv = resultItem->GetParam(getter_AddRefs(currentItemParams)); if (NS_FAILED(rv)) continue; param = (nsAbAutoCompleteParam *)(void *)currentItemParams; if (aPopularityIndex > param->mPopularityIndex) // sort the search results by popularity index break; } rv = array->InsertElementAt(newItem, insertPosition); } } } PR_Free(fullAddrStr); } static PRBool CommonPrefix(const PRUnichar *aString, const PRUnichar *aSubstr, PRInt32 aSubstrLen) { if (!aSubstrLen || (nsCRT::strlen(aString) < NS_STATIC_CAST(PRUint32, aSubstrLen))) return PR_FALSE; return (Substring(aString, aString+aSubstrLen).Equals(Substring(aSubstr, aSubstr+aSubstrLen), nsCaseInsensitiveStringComparator())); } PRBool nsAbAutoCompleteSession::CheckEntry(nsAbAutoCompleteSearchString* searchStr, const PRUnichar* nickName, const PRUnichar* displayName, const PRUnichar* firstName, const PRUnichar* lastName, const PRUnichar* emailAddress) { const PRUnichar * fullString; PRUint32 fullStringLen; PRBool isAMatch = PR_FALSE; if (searchStr->mFirstPartLen > 0 && searchStr->mSecondPartLen == 0) { fullString = searchStr->mFirstPart; fullStringLen = searchStr->mFirstPartLen; } else { fullString = searchStr->mFullString; fullStringLen = searchStr->mFullStringLen; } nsDependentString fullStringStr(fullString, fullStringLen); // Compare various properties looking for a match (exact or partial) if ( (nickName && fullStringStr.Equals(nsDependentString(nickName), nsCaseInsensitiveStringComparator())) || (displayName && fullStringStr.Equals(nsDependentString(displayName), nsCaseInsensitiveStringComparator())) || (firstName && fullStringStr.Equals(nsDependentString(firstName), nsCaseInsensitiveStringComparator())) || (lastName && fullStringStr.Equals(nsDependentString(lastName), nsCaseInsensitiveStringComparator())) || (emailAddress && fullStringStr.Equals(nsDependentString(emailAddress), nsCaseInsensitiveStringComparator())) || (nickName && CommonPrefix(nickName, fullString, fullStringLen)) || (displayName && CommonPrefix(displayName, fullString, fullStringLen)) || (firstName && CommonPrefix(firstName, fullString, fullStringLen)) || (lastName && CommonPrefix(lastName, fullString, fullStringLen)) || (emailAddress && CommonPrefix(emailAddress, fullString, fullStringLen)) ) isAMatch = PR_TRUE; //If we have a muti-part search string, look for a partial match with first name and last name or reverse else if (searchStr->mFirstPartLen && searchStr->mSecondPartLen) { if (((firstName && CommonPrefix(firstName, searchStr->mFirstPart, searchStr->mFirstPartLen)) && (lastName && CommonPrefix(lastName, searchStr->mSecondPart, searchStr->mSecondPartLen))) || ((lastName && CommonPrefix(lastName, searchStr->mFirstPart, searchStr->mFirstPartLen)) && (firstName && CommonPrefix(firstName, searchStr->mSecondPart, searchStr->mSecondPartLen)))) isAMatch = PR_TRUE; } return isAMatch; } nsresult nsAbAutoCompleteSession::SearchCards(nsIAbDirectory* directory, nsAbAutoCompleteSearchString* searchStr, nsIAutoCompleteResults* results) { nsresult rv; nsCOMPtr cardsEnumerator; nsCOMPtr card; PRInt32 i; rv = directory->GetChildCards(getter_AddRefs(cardsEnumerator)); if (NS_SUCCEEDED(rv) && cardsEnumerator) { nsCOMPtr item; for (rv = cardsEnumerator->First(); NS_SUCCEEDED(rv); rv = cardsEnumerator->Next()) { rv = cardsEnumerator->CurrentItem(getter_AddRefs(item)); if (NS_SUCCEEDED(rv)) { card = do_QueryInterface(item, &rv); if (NS_SUCCEEDED(rv)) { // Skip if it's not a normal card (ie, they can't be added as members). PRBool isNormal; rv = card->GetIsANormalCard(&isNormal); if (NS_FAILED(rv) || !isNormal) continue; nsXPIDLString pEmailStr[MAX_NUMBER_OF_EMAIL_ADDRESSES]; //[0]=primary email, [1]=secondary email (no available with mailing list) nsXPIDLString pDisplayNameStr; nsXPIDLString pFirstNameStr; nsXPIDLString pLastNameStr; nsXPIDLString pNickNameStr; nsXPIDLString pNotesStr; PRUint32 popularityIndex = 0; PRBool bIsMailList; rv = card->GetIsMailList(&bIsMailList); if (NS_FAILED(rv)) continue; if (bIsMailList) { rv = card->GetNotes(getter_Copies(pNotesStr)); if (NS_FAILED(rv)) continue; } else { for (i = 0 ; i < MAX_NUMBER_OF_EMAIL_ADDRESSES; i ++) { switch (i) { case 0: rv = card->GetPrimaryEmail(getter_Copies(pEmailStr[i])); break; case 1: rv = card->GetSecondEmail(getter_Copies(pEmailStr[i])); break; default: return NS_ERROR_FAILURE; } if (NS_FAILED(rv)) continue; // Don't bother with card without an email address if (pEmailStr[i].IsEmpty()) continue; //...and does it looks like a valid address? if (pEmailStr[i].FindChar('@') <= 0) pEmailStr[i].SetLength(0); } if (pEmailStr[0].IsEmpty() && pEmailStr[1].IsEmpty()) continue; } //Now, retrive the user name and nickname rv = card->GetDisplayName(getter_Copies(pDisplayNameStr)); if (NS_FAILED(rv)) continue; rv = card->GetFirstName(getter_Copies(pFirstNameStr)); if (NS_FAILED(rv)) continue; rv = card->GetLastName(getter_Copies(pLastNameStr)); if (NS_FAILED(rv)) continue; rv = card->GetNickName(getter_Copies(pNickNameStr)); if (NS_FAILED(rv)) continue; (void) card->GetPopularityIndex(&popularityIndex); // in the address book a mailing list does not have an email address field. However, // we do "fix up" mailing lists in the UI sometimes to look like "My List " // if we are looking up an address and we are comparing it to a mailing list to see if it is a match // instead of just looking for an exact match on "My List", hijack the unused email address field // and use that to test against "My List " if (bIsMailList) mParser->MakeFullAddressWString (pDisplayNameStr, pDisplayNameStr, getter_Copies(pEmailStr[0])); for (i = 0 ; i < MAX_NUMBER_OF_EMAIL_ADDRESSES; i ++) { if (!bIsMailList && pEmailStr[i].IsEmpty()) continue; if (CheckEntry(searchStr, pNickNameStr.get(), pDisplayNameStr.get(), pFirstNameStr.get(), pLastNameStr.get(), pEmailStr[i].get())) { nsXPIDLString pDirName; if (mAutoCompleteCommentColumn == 1) { rv = directory->GetDirName(getter_Copies(pDirName)); if (NS_FAILED(rv)) continue; } AddToResult(pNickNameStr.get(), pDisplayNameStr.get(), pFirstNameStr.get(), pLastNameStr.get(), pEmailStr[i].get(), pNotesStr.get(), pDirName.get(), popularityIndex, bIsMailList, PR_FALSE, results); } } } } } } return NS_OK; } static nsresult NeedToSearchReplicatedLDAPDirectories(nsIPrefBranch *aPrefs, PRBool *aNeedToSearch) { NS_ENSURE_ARG_POINTER(aPrefs); NS_ENSURE_ARG_POINTER(aNeedToSearch); // first check if the user is set up to do LDAP autocompletion nsresult rv = aPrefs->GetBoolPref("ldap_2.autoComplete.useDirectory", aNeedToSearch); NS_ENSURE_SUCCESS(rv, rv); // no need to search if not set up for LDAP autocompletion if (!*aNeedToSearch) return NS_OK; // now see if we are offline, if we are we need to search the // replicated directory nsCOMPtr ioService = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv,rv); rv = ioService->GetOffline(aNeedToSearch); NS_ENSURE_SUCCESS(rv,rv); return NS_OK; } nsresult nsAbAutoCompleteSession::SearchReplicatedLDAPDirectories(nsIPrefBranch *aPref, nsAbAutoCompleteSearchString* searchStr, PRBool searchSubDirectory, nsIAutoCompleteResults* results) { NS_ENSURE_ARG_POINTER(aPref); nsXPIDLCString prefName; nsresult rv = aPref->GetCharPref("ldap_2.autoComplete.directoryServer", getter_Copies(prefName)); NS_ENSURE_SUCCESS(rv,rv); if (prefName.IsEmpty()) return NS_OK; // use the prefName to get the fileName pref nsCAutoString fileNamePref; fileNamePref = prefName + NS_LITERAL_CSTRING(".filename"); nsXPIDLCString fileName; rv = aPref->GetCharPref(fileNamePref.get(), getter_Copies(fileName)); NS_ENSURE_SUCCESS(rv,rv); // if there is no fileName, bail out now. if (fileName.IsEmpty()) return NS_OK; // use the fileName to create the URI for the replicated directory nsCAutoString URI; URI = NS_LITERAL_CSTRING("moz-abmdbdirectory://") + fileName; // and then search the replicated directory return SearchDirectory(URI, searchStr, searchSubDirectory, results); } nsresult nsAbAutoCompleteSession::SearchDirectory(const nsACString& aURI, nsAbAutoCompleteSearchString* searchStr, PRBool searchSubDirectory, nsIAutoCompleteResults* results) { nsresult rv = NS_OK; nsCOMPtr rdfService(do_GetService("@mozilla.org/rdf/rdf-service;1", &rv)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr resource; rv = rdfService->GetResource(aURI, getter_AddRefs(resource)); NS_ENSURE_SUCCESS(rv, rv); // query interface nsCOMPtr directory(do_QueryInterface(resource, &rv)); NS_ENSURE_SUCCESS(rv, rv); // when autocompleteing against directories, // we only want to match against certain directories // we ask the directory if it wants to be used // for local autocompleting. PRBool searchDuringLocalAutocomplete; rv = directory->GetSearchDuringLocalAutocomplete(&searchDuringLocalAutocomplete); NS_ENSURE_SUCCESS(rv, rv); if (!searchDuringLocalAutocomplete) return NS_OK; if (!aURI.EqualsLiteral(kAllDirectoryRoot)) rv = SearchCards(directory, searchStr, results); if (!searchSubDirectory) return rv; nsCOMPtr subDirectories; if (NS_SUCCEEDED(directory->GetChildNodes(getter_AddRefs(subDirectories))) && subDirectories) { nsCOMPtr item; PRBool hasMore; while (NS_SUCCEEDED(rv = subDirectories->HasMoreElements(&hasMore)) && hasMore) { if (NS_SUCCEEDED(subDirectories->GetNext(getter_AddRefs(item)))) { directory = do_QueryInterface(item, &rv); if (NS_SUCCEEDED(rv)) { nsCOMPtr subResource(do_QueryInterface(item, &rv)); if (NS_SUCCEEDED(rv)) { nsXPIDLCString URI; subResource->GetValue(getter_Copies(URI)); rv = SearchDirectory(URI, searchStr, PR_TRUE, results); } } } } } return rv; } nsresult nsAbAutoCompleteSession::SearchPreviousResults(nsAbAutoCompleteSearchString *searchStr, nsIAutoCompleteResults *previousSearchResult, nsIAutoCompleteResults* results) { if (!previousSearchResult) return NS_ERROR_NULL_POINTER; nsXPIDLString prevSearchString; nsresult rv; rv = previousSearchResult->GetSearchString(getter_Copies(prevSearchString)); NS_ENSURE_SUCCESS(rv, rv); if (!(const PRUnichar*)prevSearchString || ((const PRUnichar*)prevSearchString)[0] == 0) return NS_ERROR_FAILURE; PRUint32 prevSearchStrLen = nsCRT::strlen(prevSearchString); if (searchStr->mFullStringLen < prevSearchStrLen || CommonPrefix(searchStr->mFullString, prevSearchString, prevSearchStrLen)) return NS_ERROR_ABORT; nsCOMPtr array; rv = previousSearchResult->GetItems(getter_AddRefs(array)); if (NS_SUCCEEDED(rv)) { PRUint32 nbrOfItems; PRUint32 i; PRUint32 pos; rv = array->Count(&nbrOfItems); if (NS_FAILED(rv) || nbrOfItems <= 0) return NS_ERROR_FAILURE; nsCOMPtr item; nsCOMPtr resultItem; nsAbAutoCompleteParam *param; for (i = 0, pos = 0; i < nbrOfItems; i ++, pos ++) { rv = array->QueryElementAt(pos, NS_GET_IID(nsIAutoCompleteItem), getter_AddRefs(resultItem)); NS_ENSURE_SUCCESS(rv, rv); rv = resultItem->GetParam(getter_AddRefs(item)); NS_ENSURE_SUCCESS(rv, rv); if (!item) return NS_ERROR_FAILURE; param = (nsAbAutoCompleteParam *)(void *)item; if (CheckEntry(searchStr, param->mNickName, param->mDisplayName, param->mFirstName, param->mLastName, param->mEmailAddress)) AddToResult(param->mNickName, param->mDisplayName, param->mFirstName, param->mLastName, param->mEmailAddress, param->mNotes, param->mDirName, param->mPopularityIndex, param->mIsMailList, PR_FALSE, results); } return NS_OK; } return NS_ERROR_ABORT; } NS_IMETHODIMP nsAbAutoCompleteSession::OnStartLookup(const PRUnichar *uSearchString, nsIAutoCompleteResults *previousSearchResult, nsIAutoCompleteListener *listener) { nsresult rv = NS_OK; if (!listener) return NS_ERROR_NULL_POINTER; PRBool enableLocalAutocomplete; PRBool enableReplicatedLDAPAutocomplete; nsCOMPtr prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); NS_ENSURE_SUCCESS(rv, rv); // check if using autocomplete for local address books rv = prefs->GetBoolPref("mail.enable_autocomplete", &enableLocalAutocomplete); NS_ENSURE_SUCCESS(rv,rv); rv = NeedToSearchReplicatedLDAPDirectories(prefs, &enableReplicatedLDAPAutocomplete); NS_ENSURE_SUCCESS(rv,rv); if (uSearchString[0] == 0 || (!enableLocalAutocomplete && !enableReplicatedLDAPAutocomplete)) { listener->OnAutoComplete(nsnull, nsIAutoCompleteStatus::ignored); return NS_OK; } // figure out what we're supposed to do about the comment column, and // remember it for when the results start coming back // rv = prefs->GetIntPref("mail.autoComplete.commentColumn", &mAutoCompleteCommentColumn); if (NS_FAILED(rv)) { mAutoCompleteCommentColumn = 0; } // strings with commas (commas denote multiple names) should be ignored for // autocomplete purposes PRInt32 i; for (i = nsCRT::strlen(uSearchString) - 1; i >= 0; i --) if (uSearchString[i] == ',') { listener->OnAutoComplete(nsnull, nsIAutoCompleteStatus::ignored); return NS_OK; } nsAbAutoCompleteSearchString searchStrings(uSearchString); nsCOMPtr results = do_CreateInstance(NS_AUTOCOMPLETERESULTS_CONTRACTID, &rv); if (NS_SUCCEEDED(rv)) if (NS_FAILED(SearchPreviousResults(&searchStrings, previousSearchResult, results))) { nsresult rv1,rv2; if (enableLocalAutocomplete) { rv1 = SearchDirectory(NS_LITERAL_CSTRING(kAllDirectoryRoot), &searchStrings, PR_TRUE, results); NS_ASSERTION(NS_SUCCEEDED(rv1), "searching all local directories failed"); } else rv1 = NS_OK; if (enableReplicatedLDAPAutocomplete) { rv2 = SearchReplicatedLDAPDirectories(prefs, &searchStrings, PR_TRUE, results); NS_ASSERTION(NS_SUCCEEDED(rv2), "searching all replicated LDAP directories failed"); } else rv2 = NS_OK; // only bail out if both failed. otherwise, we have some results we can use if (NS_FAILED(rv1) && NS_FAILED(rv2)) rv = NS_ERROR_FAILURE; else rv = NS_OK; } AutoCompleteStatus status = nsIAutoCompleteStatus::failed; if (NS_SUCCEEDED(rv) && results) { PRBool addedDefaultItem = PR_FALSE; results->SetSearchString(uSearchString); results->SetDefaultItemIndex(-1); if (mDefaultDomain[0] != 0) { PRUnichar emptyStr = 0; AddToResult(&emptyStr, uSearchString, &emptyStr, &emptyStr, &emptyStr, &emptyStr, &emptyStr, 0 /* popularity index */, PR_FALSE, PR_TRUE, results); addedDefaultItem = PR_TRUE; } nsCOMPtr array; rv = results->GetItems(getter_AddRefs(array)); if (NS_SUCCEEDED(rv)) { //If we have more than a match (without counting the default item), we don't //want to auto complete the user input therefore set the default item index to -1 PRUint32 nbrOfItems; rv = array->Count(&nbrOfItems); if (NS_SUCCEEDED(rv)) if (nbrOfItems == 0) status = nsIAutoCompleteStatus::noMatch; else { status = nsIAutoCompleteStatus::matchFound; if (addedDefaultItem) { // If we have at least one REAL match then make it the default item. If we don't have any matches, // just the default domain, then don't install a default item index on the widget. results->SetDefaultItemIndex(nbrOfItems > 1 ? 1 : -1); } else results->SetDefaultItemIndex(0); } } } listener->OnAutoComplete(results, status); return NS_OK; } NS_IMETHODIMP nsAbAutoCompleteSession::OnStopLookup() { return NS_OK; } NS_IMETHODIMP nsAbAutoCompleteSession::OnAutoComplete(const PRUnichar *searchString, nsIAutoCompleteResults *previousSearchResult, nsIAutoCompleteListener *listener) { return OnStartLookup(searchString, previousSearchResult, listener); } NS_IMETHODIMP nsAbAutoCompleteSession::GetDefaultDomain(PRUnichar * *aDefaultDomain) { if (!aDefaultDomain) return NS_ERROR_NULL_POINTER; *aDefaultDomain = ToNewUnicode(mDefaultDomain); return NS_OK; } NS_IMETHODIMP nsAbAutoCompleteSession::SetDefaultDomain(const PRUnichar * aDefaultDomain) { mDefaultDomain = aDefaultDomain; return NS_OK; } NS_IMPL_ISUPPORTS1(nsAbAutoCompleteParam, nsISupports) nsAbAutoCompleteSearchString::nsAbAutoCompleteSearchString(const PRUnichar *uSearchString) { mFullString = nsCRT::strdup(uSearchString); mFullStringLen = nsCRT::strlen(mFullString); PRUint32 i; PRUnichar * aPtr; for (i = 0, aPtr = (PRUnichar*)mFullString; i < mFullStringLen; i ++, aPtr ++) { if (*aPtr == ' ') { mFirstPart = nsCRT::strndup(mFullString, i); mFirstPartLen = i; mSecondPart = nsCRT::strdup(++aPtr); mSecondPartLen = mFullStringLen - i - 1; return; } } /* If we did not find a space in the search string, initialize the first and second part as null */ mFirstPart = nsnull; mFirstPartLen = 0; mSecondPart = nsnull; mSecondPartLen = 0; } nsAbAutoCompleteSearchString::~nsAbAutoCompleteSearchString() { if (mFullString) nsCRT::free((PRUnichar*)mFullString); if (mFirstPart) nsCRT::free((PRUnichar*)mFirstPart); if (mSecondPart) nsCRT::free((PRUnichar*)mSecondPart); }