diff options
Diffstat (limited to 'mailnews/addrbook/src/nsAbView.cpp')
-rw-r--r-- | mailnews/addrbook/src/nsAbView.cpp | 1451 |
1 files changed, 1451 insertions, 0 deletions
diff --git a/mailnews/addrbook/src/nsAbView.cpp b/mailnews/addrbook/src/nsAbView.cpp new file mode 100644 index 000000000..77f3122df --- /dev/null +++ b/mailnews/addrbook/src/nsAbView.cpp @@ -0,0 +1,1451 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/DebugOnly.h" + +#include "nsAbView.h" +#include "nsISupports.h" +#include "nsCOMPtr.h" +#include "nsIServiceManager.h" +#include "nsIAbCard.h" +#include "nsILocale.h" +#include "nsILocaleService.h" +#include "prmem.h" +#include "nsCollationCID.h" +#include "nsIAbManager.h" +#include "nsAbBaseCID.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsITreeColumns.h" +#include "nsCRTGlue.h" +#include "nsIMutableArray.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIStringBundle.h" +#include "nsIPrefLocalizedString.h" +#include "nsArrayUtils.h" +#include "nsIAddrDatabase.h" // for kPriEmailColumn +#include "nsMsgUtils.h" +#include "mozilla/Services.h" + +using namespace mozilla; + +#define CARD_NOT_FOUND -1 +#define ALL_ROWS -1 + +#define PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST "mail.addr_book.lastnamefirst" +#define PREF_MAIL_ADDR_BOOK_DISPLAYNAME_AUTOGENERATION "mail.addr_book.displayName.autoGeneration" +#define PREF_MAIL_ADDR_BOOK_DISPLAYNAME_LASTNAMEFIRST "mail.addr_book.displayName.lastnamefirst" + +// Also, our default primary sort +#define GENERATED_NAME_COLUMN_ID "GeneratedName" + +NS_IMPL_ISUPPORTS(nsAbView, nsIAbView, nsITreeView, nsIAbListener, nsIObserver) + +nsAbView::nsAbView() : mInitialized(false), + mIsAllDirectoryRootView(false), + mSuppressSelectionChange(false), + mSuppressCountChange(false), + mGeneratedNameFormat(0) +{ +} + +nsAbView::~nsAbView() +{ + if (mInitialized) { + NS_ASSERTION(NS_SUCCEEDED(ClearView()), "failed to close view"); + } +} + +NS_IMETHODIMP nsAbView::ClearView() +{ + mDirectory = nullptr; + mAbViewListener = nullptr; + if (mTree) + mTree->SetView(nullptr); + mTree = nullptr; + mTreeSelection = nullptr; + + if (mInitialized) + { + nsresult rv; + mInitialized = false; + nsCOMPtr<nsIPrefBranch> pbi(do_GetService(NS_PREFSERVICE_CONTRACTID, + &rv)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = pbi->RemoveObserver(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST, this); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, + &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = abManager->RemoveAddressBookListener(this); + NS_ENSURE_SUCCESS(rv, rv); + } + + int32_t i = mCards.Length(); + while(i-- > 0) + NS_ASSERTION(NS_SUCCEEDED(RemoveCardAt(i)), "remove card failed\n"); + + return NS_OK; +} + +nsresult nsAbView::RemoveCardAt(int32_t row) +{ + nsresult rv; + + AbCard *abcard = mCards.ElementAt(row); + NS_IF_RELEASE(abcard->card); + mCards.RemoveElementAt(row); + PR_FREEIF(abcard->primaryCollationKey); + PR_FREEIF(abcard->secondaryCollationKey); + PR_FREEIF(abcard); + + + // This needs to happen after we remove the card, as RowCountChanged() will call GetRowCount() + if (mTree) { + rv = mTree->RowCountChanged(row, -1); + NS_ENSURE_SUCCESS(rv,rv); + } + + if (mAbViewListener && !mSuppressCountChange) { + rv = mAbViewListener->OnCountChanged(mCards.Length()); + NS_ENSURE_SUCCESS(rv,rv); + } + return NS_OK; +} + +nsresult nsAbView::SetGeneratedNameFormatFromPrefs() +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranchInt(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv,rv); + + return prefBranchInt->GetIntPref(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST, &mGeneratedNameFormat); +} + +nsresult nsAbView::Initialize() +{ + if (mInitialized) + return NS_OK; + + mInitialized = true; + + nsresult rv; + nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = abManager->AddAddressBookListener(this, nsIAbListener::all); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrefBranch> pbi(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = pbi->AddObserver(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST, this, false); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mABBundle) + { + nsCOMPtr<nsIStringBundleService> stringBundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(stringBundleService, NS_ERROR_UNEXPECTED); + + rv = stringBundleService->CreateBundle("chrome://messenger/locale/addressbook/addressBook.properties", getter_AddRefs(mABBundle)); + NS_ENSURE_SUCCESS(rv, rv); + } + + return SetGeneratedNameFormatFromPrefs(); +} + +NS_IMETHODIMP nsAbView::SetView(nsIAbDirectory *aAddressBook, + nsIAbViewListener *aAbViewListener, + const nsAString &aSortColumn, + const nsAString &aSortDirection, + nsAString &aResult) +{ + // Ensure we are initialized + nsresult rv = Initialize(); + + mAbViewListener = nullptr; + if (mTree) + { + // Try and speed deletion of old cards by disconnecting the tree from us. + mTreeSelection->ClearSelection(); + mTree->SetView(nullptr); + } + + // Clear out old cards + int32_t i = mCards.Length(); + while(i-- > 0) + { + rv = RemoveCardAt(i); + NS_ASSERTION(NS_SUCCEEDED(rv), "remove card failed\n"); + } + + // We replace all cards so any sorting is no longer valid. + mSortColumn.AssignLiteral(""); + mSortDirection.AssignLiteral(""); + + nsCString uri; + aAddressBook->GetURI(uri); + int32_t searchBegin = uri.FindChar('?'); + nsCString searchQuery(Substring(uri, searchBegin)); + // This is a special case, a workaround basically, to just have all ABs. + if (searchQuery.EqualsLiteral("?")) { + searchQuery.AssignLiteral(""); + } + + if (Substring(uri, 0, searchBegin).EqualsLiteral(kAllDirectoryRoot)) { + mIsAllDirectoryRootView = true; + // We have special request case to search all addressbooks, so we need + // to iterate over all addressbooks. + // Since the request is for all addressbooks, the URI must have been + // passed with an extra '?'. We still check it for sanity and trim it here. + if (searchQuery.Find("??") == 0) + searchQuery = Substring(searchQuery, 1); + + nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, + &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsISimpleEnumerator> enumerator; + rv = abManager->GetDirectories(getter_AddRefs(enumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore = false; + nsCOMPtr<nsISupports> support; + nsCOMPtr<nsIAbDirectory> directory; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) { + rv = enumerator->GetNext(getter_AddRefs(support)); + NS_ENSURE_SUCCESS(rv, rv); + directory = do_QueryInterface(support, &rv); + + // If, for some reason, we are unable to get a directory, we continue. + if (NS_FAILED(rv)) + continue; + + // Get appropriate directory with search query. + nsCString uri; + directory->GetURI(uri); + rv = abManager->GetDirectory(uri + searchQuery, getter_AddRefs(directory)); + mDirectory = directory; + rv = EnumerateCards(); + NS_ENSURE_SUCCESS(rv, rv); + } + } else { + mIsAllDirectoryRootView = false; + mDirectory = aAddressBook; + rv = EnumerateCards(); + NS_ENSURE_SUCCESS(rv, rv); + } + + NS_NAMED_LITERAL_STRING(generatedNameColumnId, GENERATED_NAME_COLUMN_ID); + + // See if the persisted sortColumn is valid. + // It may not be, if you migrated from older versions, or switched between + // a mozilla build and a commercial build, which have different columns. + nsAutoString actualSortColumn; + if (!generatedNameColumnId.Equals(aSortColumn) && mCards.Length()) { + nsIAbCard *card = mCards.ElementAt(0)->card; + nsString value; + // XXX todo + // Need to check if _Generic is valid. GetCardValue() will always return NS_OK for _Generic + // We're going to have to ask mDirectory if it is. + // It might not be. example: _ScreenName is valid in Netscape, but not Mozilla. + rv = GetCardValue(card, PromiseFlatString(aSortColumn).get(), value); + if (NS_FAILED(rv)) + actualSortColumn = generatedNameColumnId; + else + actualSortColumn = aSortColumn; + } + else + actualSortColumn = aSortColumn; + + rv = SortBy(actualSortColumn.get(), PromiseFlatString(aSortDirection).get(), false); + NS_ENSURE_SUCCESS(rv, rv); + + mAbViewListener = aAbViewListener; + if (mAbViewListener && !mSuppressCountChange) { + rv = mAbViewListener->OnCountChanged(mCards.Length()); + NS_ENSURE_SUCCESS(rv, rv); + } + + aResult = actualSortColumn; + return NS_OK; +} + +NS_IMETHODIMP nsAbView::GetDirectory(nsIAbDirectory **aDirectory) +{ + NS_ENSURE_ARG_POINTER(aDirectory); + NS_IF_ADDREF(*aDirectory = mDirectory); + return NS_OK; +} + +nsresult nsAbView::EnumerateCards() +{ + nsresult rv; + nsCOMPtr<nsISimpleEnumerator> cardsEnumerator; + nsCOMPtr<nsIAbCard> card; + + if (!mDirectory) + return NS_ERROR_UNEXPECTED; + + rv = mDirectory->GetChildCards(getter_AddRefs(cardsEnumerator)); + if (NS_SUCCEEDED(rv) && cardsEnumerator) + { + nsCOMPtr<nsISupports> item; + bool more; + while (NS_SUCCEEDED(cardsEnumerator->HasMoreElements(&more)) && more) + { + rv = cardsEnumerator->GetNext(getter_AddRefs(item)); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr <nsIAbCard> card = do_QueryInterface(item); + // Malloc these from an arena + AbCard *abcard = (AbCard *) PR_Calloc(1, sizeof(struct AbCard)); + if (!abcard) + return NS_ERROR_OUT_OF_MEMORY; + + abcard->card = card; + NS_IF_ADDREF(abcard->card); + + // XXX todo + // Would it be better to do an insertion sort, than append and sort? + // XXX todo + // If we knew how many cards there was going to be + // we could allocate an array of the size, + // instead of growing and copying as we append. + DebugOnly<bool> didAppend = mCards.AppendElement(abcard); + NS_ASSERTION(didAppend, "failed to append card"); + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsAbView::GetRowCount(int32_t *aRowCount) +{ + *aRowCount = mCards.Length(); + return NS_OK; +} + +NS_IMETHODIMP nsAbView::GetSelection(nsITreeSelection * *aSelection) +{ + NS_IF_ADDREF(*aSelection = mTreeSelection); + return NS_OK; +} + +NS_IMETHODIMP nsAbView::SetSelection(nsITreeSelection * aSelection) +{ + mTreeSelection = aSelection; + return NS_OK; +} + +NS_IMETHODIMP nsAbView::GetRowProperties(int32_t index, nsAString& properties) +{ + return NS_OK; +} + +NS_IMETHODIMP nsAbView::GetCellProperties(int32_t row, nsITreeColumn* col, nsAString& properties) +{ + NS_ENSURE_TRUE(row >= 0, NS_ERROR_UNEXPECTED); + + if (mCards.Length() <= (size_t)row) + return NS_OK; + + const char16_t* colID; + col->GetIdConst(&colID); + // "G" == "GeneratedName" + if (colID[0] != char16_t('G')) + return NS_OK; + + nsIAbCard *card = mCards.ElementAt(row)->card; + + bool isMailList; + nsresult rv = card->GetIsMailList(&isMailList); + NS_ENSURE_SUCCESS(rv,rv); + + if (isMailList) + properties.AssignLiteral("MailList"); + + return NS_OK; +} + +NS_IMETHODIMP nsAbView::GetColumnProperties(nsITreeColumn* col, nsAString& properties) +{ + return NS_OK; +} + +NS_IMETHODIMP nsAbView::IsContainer(int32_t index, bool *_retval) +{ + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP nsAbView::IsContainerOpen(int32_t index, bool *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::IsContainerEmpty(int32_t index, bool *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::IsSeparator(int32_t index, bool *_retval) +{ + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP nsAbView::IsSorted(bool *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::CanDrop(int32_t index, + int32_t orientation, + nsIDOMDataTransfer *dataTransfer, + bool *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::Drop(int32_t row, + int32_t orientation, + nsIDOMDataTransfer *dataTransfer) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::GetParentIndex(int32_t rowIndex, int32_t *_retval) +{ + *_retval = -1; + return NS_OK; +} + +NS_IMETHODIMP nsAbView::HasNextSibling(int32_t rowIndex, int32_t afterIndex, bool *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::GetLevel(int32_t index, int32_t *_retval) +{ + *_retval = 0; + return NS_OK; +} + +NS_IMETHODIMP nsAbView::GetImageSrc(int32_t row, nsITreeColumn* col, nsAString& _retval) +{ + return NS_OK; +} + +NS_IMETHODIMP nsAbView::GetProgressMode(int32_t row, nsITreeColumn* col, int32_t* _retval) +{ + return NS_OK; +} + +NS_IMETHODIMP nsAbView::GetCellValue(int32_t row, nsITreeColumn* col, nsAString& _retval) +{ + return NS_OK; +} + +nsresult nsAbView::GetCardValue(nsIAbCard *card, const char16_t *colID, + nsAString &_retval) +{ + if (nsString(colID).EqualsLiteral("addrbook")) { + nsCString dirID; + nsresult rv = card->GetDirectoryId(dirID); + if (NS_SUCCEEDED(rv)) + CopyUTF8toUTF16(Substring(dirID, dirID.FindChar('&') + 1), _retval); + + return rv; + } + + // "G" == "GeneratedName", "_P" == "_PhoneticName" + // else, standard column (like PrimaryEmail and _AimScreenName) + if (colID[0] == char16_t('G')) + return card->GenerateName(mGeneratedNameFormat, mABBundle, _retval); + + if (colID[0] == char16_t('_') && colID[1] == char16_t('P')) + // Use LN/FN order for the phonetic name + return card->GeneratePhoneticName(true, _retval); + + if (!NS_strcmp(colID, u"ChatName")) + return card->GenerateChatName(_retval); + + nsresult rv = card->GetPropertyAsAString(NS_ConvertUTF16toUTF8(colID).get(), _retval); + if (rv == NS_ERROR_NOT_AVAILABLE) { + rv = NS_OK; + _retval.Truncate(); + } + return rv; +} + +nsresult nsAbView::RefreshTree() +{ + nsresult rv; + + // The PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST pref affects how the GeneratedName column looks. + // so if the GeneratedName is our primary or secondary sort, + // we need to resort. + // the same applies for kPhoneticNameColumn + // + // XXX optimize me + // PrimaryEmail is always the secondary sort, unless it is currently the + // primary sort. So, if PrimaryEmail is the primary sort, + // GeneratedName might be the secondary sort. + // + // One day, we can get fancy and remember what the secondary sort is. + // We do that, we can fix this code. At best, it will turn a sort into a invalidate. + // + // If neither the primary nor the secondary sorts are GeneratedName (or kPhoneticNameColumn), + // all we have to do is invalidate (to show the new GeneratedNames), + // but the sort will not change. + if (mSortColumn.EqualsLiteral(GENERATED_NAME_COLUMN_ID) || + mSortColumn.EqualsLiteral(kPriEmailProperty) || + mSortColumn.EqualsLiteral(kPhoneticNameColumn)) { + rv = SortBy(mSortColumn.get(), mSortDirection.get(), true); + } + else { + rv = InvalidateTree(ALL_ROWS); + + // Although the selection hasn't changed, the card that is selected may need + // to be displayed differently, therefore pretend that the selection has + // changed to force that update. + SelectionChanged(); + } + + return rv; +} + +NS_IMETHODIMP nsAbView::GetCellText(int32_t row, nsITreeColumn* col, nsAString& _retval) +{ + NS_ENSURE_TRUE(row >= 0 && (size_t)row < mCards.Length(), NS_ERROR_UNEXPECTED); + + nsIAbCard *card = mCards.ElementAt(row)->card; + const char16_t* colID; + col->GetIdConst(&colID); + return GetCardValue(card, colID, _retval); +} + +NS_IMETHODIMP nsAbView::SetTree(nsITreeBoxObject *tree) +{ + mTree = tree; + return NS_OK; +} + +NS_IMETHODIMP nsAbView::ToggleOpenState(int32_t index) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::CycleHeader(nsITreeColumn* col) +{ + return NS_OK; +} + +nsresult nsAbView::InvalidateTree(int32_t row) +{ + if (!mTree) + return NS_OK; + + if (row == ALL_ROWS) + return mTree->Invalidate(); + else + return mTree->InvalidateRow(row); +} + +NS_IMETHODIMP nsAbView::SelectionChanged() +{ + if (mAbViewListener && !mSuppressSelectionChange) { + nsresult rv = mAbViewListener->OnSelectionChanged(); + NS_ENSURE_SUCCESS(rv,rv); + } + return NS_OK; +} + +NS_IMETHODIMP nsAbView::CycleCell(int32_t row, nsITreeColumn* col) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::IsEditable(int32_t row, nsITreeColumn* col, bool* _retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::IsSelectable(int32_t row, nsITreeColumn* col, bool* _retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::SetCellValue(int32_t row, nsITreeColumn* col, const nsAString& value) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::SetCellText(int32_t row, nsITreeColumn* col, const nsAString& value) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::PerformAction(const char16_t *action) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::PerformActionOnRow(const char16_t *action, int32_t row) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::PerformActionOnCell(const char16_t *action, int32_t row, nsITreeColumn* col) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::GetCardFromRow(int32_t row, nsIAbCard **aCard) +{ + *aCard = nullptr; + NS_ENSURE_TRUE(row >= 0, NS_ERROR_UNEXPECTED); + if (mCards.Length() <= (size_t)row) { + return NS_OK; + } + + AbCard *a = mCards.ElementAt(row); + if (!a) + return NS_OK; + + NS_IF_ADDREF(*aCard = a->card); + return NS_OK; +} + +#define DESCENDING_SORT_FACTOR -1 +#define ASCENDING_SORT_FACTOR 1 + +typedef struct SortClosure +{ + const char16_t *colID; + int32_t factor; + nsAbView *abView; +} SortClosure; + +static int +inplaceSortCallback(const AbCard *card1, const AbCard *card2, SortClosure *closure) +{ + int32_t sortValue; + + // If we are sorting the "PrimaryEmail", swap the collation keys, as the secondary is always the + // PrimaryEmail. Use the last primary key as the secondary key. + // + // "Pr" to distinguish "PrimaryEmail" from "PagerNumber" + if (closure->colID[0] == char16_t('P') && closure->colID[1] == char16_t('r')) { + sortValue = closure->abView->CompareCollationKeys(card1->secondaryCollationKey,card1->secondaryCollationKeyLen,card2->secondaryCollationKey,card2->secondaryCollationKeyLen); + if (sortValue) + return sortValue * closure->factor; + else + return closure->abView->CompareCollationKeys(card1->primaryCollationKey,card1->primaryCollationKeyLen,card2->primaryCollationKey,card2->primaryCollationKeyLen) * (closure->factor); + } + else { + sortValue = closure->abView->CompareCollationKeys(card1->primaryCollationKey,card1->primaryCollationKeyLen,card2->primaryCollationKey,card2->primaryCollationKeyLen); + if (sortValue) + return sortValue * (closure->factor); + else + return closure->abView->CompareCollationKeys(card1->secondaryCollationKey,card1->secondaryCollationKeyLen,card2->secondaryCollationKey,card2->secondaryCollationKeyLen) * (closure->factor); + } +} + +static void SetSortClosure(const char16_t *sortColumn, const char16_t *sortDirection, nsAbView *abView, SortClosure *closure) +{ + closure->colID = sortColumn; + + if (sortDirection && !NS_strcmp(sortDirection, u"descending")) + closure->factor = DESCENDING_SORT_FACTOR; + else + closure->factor = ASCENDING_SORT_FACTOR; + + closure->abView = abView; + return; +} + +class CardComparator +{ +public: + void SetClosure(SortClosure *closure) { m_closure = closure; }; + + bool Equals(const AbCard *a, const AbCard *b) const { + return inplaceSortCallback(a, b, m_closure) == 0; + } + bool LessThan(const AbCard *a, const AbCard *b) const{ + return inplaceSortCallback(a, b, m_closure) < 0; + } + +private: + SortClosure *m_closure; +}; + +NS_IMETHODIMP nsAbView::SortBy(const char16_t *colID, const char16_t *sortDir, bool aResort = false) +{ + nsresult rv; + + int32_t count = mCards.Length(); + + nsAutoString sortColumn; + if (!colID) + sortColumn = NS_LITERAL_STRING(GENERATED_NAME_COLUMN_ID); // default sort column + else + sortColumn = colID; + + nsAutoString sortDirection; + if (!sortDir) + sortDirection = NS_LITERAL_STRING("ascending"); // default direction + else + sortDirection = sortDir; + + if (mSortColumn.Equals(sortColumn) && !aResort) { + if (mSortDirection.Equals(sortDir)) { + // If sortColumn and sortDirection are identical since the last call, do nothing. + return NS_OK; + } else { + // If we are sorting by how we are already sorted, + // and just the sort direction changes, just reverse. + int32_t halfPoint = count / 2; + for (int32_t i = 0; i < halfPoint; i++) { + // Swap the elements. + AbCard *ptr1 = mCards.ElementAt(i); + AbCard *ptr2 = mCards.ElementAt(count - i - 1); + mCards.ReplaceElementAt(i, ptr2); + mCards.ReplaceElementAt(count - i - 1, ptr1); + } + mSortDirection = sortDir; + } + } + else { + // Generate collation keys + for (int32_t i = 0; i < count; i++) { + AbCard *abcard = mCards.ElementAt(i); + + rv = GenerateCollationKeysForCard(sortColumn.get(), abcard); + NS_ENSURE_SUCCESS(rv,rv); + } + + // We need to do full sort. + SortClosure closure; + SetSortClosure(sortColumn.get(), sortDirection.get(), this, &closure); + + nsCOMPtr<nsIMutableArray> selectedCards = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GetSelectedCards(selectedCards); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbCard> indexCard; + + if (mTreeSelection) { + int32_t currentIndex = -1; + + rv = mTreeSelection->GetCurrentIndex(¤tIndex); + NS_ENSURE_SUCCESS(rv,rv); + + if (currentIndex != -1) { + rv = GetCardFromRow(currentIndex, getter_AddRefs(indexCard)); + NS_ENSURE_SUCCESS(rv,rv); + } + } + + CardComparator cardComparator; + cardComparator.SetClosure(&closure); + mCards.Sort(cardComparator); + + rv = ReselectCards(selectedCards, indexCard); + NS_ENSURE_SUCCESS(rv, rv); + + mSortColumn = sortColumn; + mSortDirection = sortDirection; + } + + rv = InvalidateTree(ALL_ROWS); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +int32_t nsAbView::CompareCollationKeys(uint8_t *key1, uint32_t len1, uint8_t *key2, uint32_t len2) +{ + NS_ASSERTION(mCollationKeyGenerator, "no key generator"); + if (!mCollationKeyGenerator) + return 0; + + int32_t result; + + nsresult rv = mCollationKeyGenerator->CompareRawSortKey(key1,len1,key2,len2,&result); + NS_ASSERTION(NS_SUCCEEDED(rv), "key compare failed"); + if (NS_FAILED(rv)) + result = 0; + return result; +} + +nsresult nsAbView::GenerateCollationKeysForCard(const char16_t *colID, AbCard *abcard) +{ + nsresult rv; + nsString value; + + if (!mCollationKeyGenerator) + { + nsCOMPtr<nsILocaleService> localeSvc = do_GetService(NS_LOCALESERVICE_CONTRACTID,&rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILocale> locale; + rv = localeSvc->GetApplicationLocale(getter_AddRefs(locale)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsICollationFactory> factory = do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = factory->CreateCollation(locale, getter_AddRefs(mCollationKeyGenerator)); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = GetCardValue(abcard->card, colID, value); + NS_ENSURE_SUCCESS(rv,rv); + + PR_FREEIF(abcard->primaryCollationKey); + rv = mCollationKeyGenerator->AllocateRawSortKey(nsICollation::kCollationCaseInSensitive, + value, &(abcard->primaryCollationKey), &(abcard->primaryCollationKeyLen)); + NS_ENSURE_SUCCESS(rv,rv); + + // Hardcode email to be our secondary key. As we are doing this, just call + // the card's GetCardValue direct, rather than our own function which will + // end up doing the same as then we can save a bit of time. + rv = abcard->card->GetPrimaryEmail(value); + NS_ENSURE_SUCCESS(rv,rv); + + PR_FREEIF(abcard->secondaryCollationKey); + rv = mCollationKeyGenerator->AllocateRawSortKey(nsICollation::kCollationCaseInSensitive, + value, &(abcard->secondaryCollationKey), &(abcard->secondaryCollationKeyLen)); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +// A helper method currently returns true if the directory is an LDAP. +// We can tweak this to return true for all Remote Address Books where the +// search is asynchronous. +bool isDirectoryRemote(nsCOMPtr<nsIAbDirectory> aDir) +{ + nsCString uri; + aDir->GetURI(uri); + return (uri.Find("moz-abldapdirectory") != kNotFound); +} + +// A helper method to get the query string for nsIAbDirectory. +nsCString getQuery(nsCOMPtr<nsIAbDirectory> aDir) +{ + nsCString uri; + aDir->GetURI(uri); + int32_t searchBegin = uri.FindChar('?'); + if (searchBegin == kNotFound) + return EmptyCString(); + + return nsCString(Substring(uri, searchBegin)); +} + +NS_IMETHODIMP nsAbView::OnItemAdded(nsISupports *parentDir, nsISupports *item) +{ + nsresult rv; + nsCOMPtr <nsIAbDirectory> directory = do_QueryInterface(parentDir, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + bool isRemote = isDirectoryRemote(directory); + // If the search is performed on All Address books, its possible that the LDAP + // results start coming when mDirectory has changed (LDAP search works in an + // asynchronous manner). + // Since the listeners are being added to all nsAbView instances, we need to + // make sure that all the views aren't updated by the listeners. + bool isDirectoryQuery = false; + bool isMDirectoryQuery = false; + // See if current parent directory to which the item is added is a query + // directory. + directory->GetIsQuery(&isDirectoryQuery); + // Get the query string for the directory in Advanced AB Search window. + nsCString directoryQuery(getQuery(directory)); + // See if the selected directory in Address book main window is a query + // directory. + mDirectory->GetIsQuery(&isMDirectoryQuery); + // Get the query string for the selected directory in the main AB window. + nsCString mDirectoryQuery(getQuery(mDirectory)); + if ((mIsAllDirectoryRootView && isRemote && + isDirectoryQuery && isMDirectoryQuery && + directoryQuery.Equals(mDirectoryQuery)) || + directory.get() == mDirectory.get()) { + nsCOMPtr <nsIAbCard> addedCard = do_QueryInterface(item); + if (addedCard) { + // Malloc these from an arena + AbCard *abcard = (AbCard *) PR_Calloc(1, sizeof(struct AbCard)); + if (!abcard) + return NS_ERROR_OUT_OF_MEMORY; + + abcard->card = addedCard; + NS_IF_ADDREF(abcard->card); + + rv = GenerateCollationKeysForCard(mSortColumn.get(), abcard); + NS_ENSURE_SUCCESS(rv,rv); + + int32_t index; + rv = AddCard(abcard, false /* select card */, &index); + NS_ENSURE_SUCCESS(rv,rv); + } + } + return rv; +} + +NS_IMETHODIMP nsAbView::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData) +{ + if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { + if (nsDependentString(someData).EqualsLiteral(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST)) { + nsresult rv = SetGeneratedNameFormatFromPrefs(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = RefreshTree(); + NS_ENSURE_SUCCESS(rv, rv); + } + } + return NS_OK; +} + +nsresult nsAbView::AddCard(AbCard *abcard, bool selectCardAfterAdding, int32_t *index) +{ + nsresult rv = NS_OK; + NS_ENSURE_ARG_POINTER(abcard); + + *index = FindIndexForInsert(abcard); + mCards.InsertElementAt(*index, abcard); + + // This needs to happen after we insert the card, as RowCountChanged() will call GetRowCount() + if (mTree) + rv = mTree->RowCountChanged(*index, 1); + + // Checking for mTree here works around core bug 399227 + if (selectCardAfterAdding && mTreeSelection && mTree) { + mTreeSelection->SetCurrentIndex(*index); + mTreeSelection->RangedSelect(*index, *index, false /* augment */); + } + + if (mAbViewListener && !mSuppressCountChange) { + rv = mAbViewListener->OnCountChanged(mCards.Length()); + NS_ENSURE_SUCCESS(rv,rv); + } + + return rv; +} + +int32_t nsAbView::FindIndexForInsert(AbCard *abcard) +{ + int32_t count = mCards.Length(); + int32_t i; + + SortClosure closure; + SetSortClosure(mSortColumn.get(), mSortDirection.get(), this, &closure); + + // XXX todo + // Make this a binary search + for (i=0; i < count; i++) { + int32_t value = inplaceSortCallback(abcard, mCards.ElementAt(i), &closure); + // XXX Fix me, this is not right for both ascending and descending + if (value <= 0) + break; + } + return i; +} + +NS_IMETHODIMP nsAbView::OnItemRemoved(nsISupports *parentDir, nsISupports *item) +{ + nsresult rv; + + nsCOMPtr <nsIAbDirectory> directory = do_QueryInterface(parentDir,&rv); + NS_ENSURE_SUCCESS(rv,rv); + + if (directory.get() == mDirectory.get()) + return RemoveCardAndSelectNextCard(item); + + // The pointers aren't the same, are the URI strings similar? This is most + // likely the case if the current directory is a search on a directory. + nsCString currentURI; + rv = mDirectory->GetURI(currentURI); + NS_ENSURE_SUCCESS(rv, rv); + + // If it is a search, it will have something like ?(or(PrimaryEmail... + // on the end of the string, so remove that before comparing + int32_t pos = currentURI.FindChar('?'); + if (pos != -1) + currentURI.SetLength(pos); + + nsCString notifiedURI; + rv = directory->GetURI(notifiedURI); + NS_ENSURE_SUCCESS(rv, rv); + + if (currentURI.Equals(notifiedURI)) + return RemoveCardAndSelectNextCard(item); + + return NS_OK; +} + +nsresult nsAbView::RemoveCardAndSelectNextCard(nsISupports *item) +{ + nsresult rv = NS_OK; + nsCOMPtr <nsIAbCard> card = do_QueryInterface(item); + if (card) { + int32_t index = FindIndexForCard(card); + if (index != CARD_NOT_FOUND) { + bool selectNextCard = false; + if (mTreeSelection) { + int32_t selectedIndex; + // XXX todo + // Make sure it works if nothing selected + mTreeSelection->GetCurrentIndex(&selectedIndex); + if (index == selectedIndex) + selectNextCard = true; + } + + rv = RemoveCardAt(index); + NS_ENSURE_SUCCESS(rv,rv); + + if (selectNextCard) { + int32_t count = mCards.Length(); + if (count && mTreeSelection) { + // If we deleted the last card, adjust so we select the new "last" card + if (index >= (count - 1)) { + index = count -1; + } + mTreeSelection->SetCurrentIndex(index); + mTreeSelection->RangedSelect(index, index, false /* augment */); + } + } + } + } + return rv; +} + +int32_t nsAbView::FindIndexForCard(nsIAbCard *card) +{ + int32_t count = mCards.Length(); + int32_t i; + + // You can't implement the binary search here, as all you have is the nsIAbCard + // you might be here because one of the card properties has changed, and that property + // could be the collation key. + for (i=0; i < count; i++) { + AbCard *abcard = mCards.ElementAt(i); + bool equals; + nsresult rv = card->Equals(abcard->card, &equals); + if (NS_SUCCEEDED(rv) && equals) { + return i; + } + } + return CARD_NOT_FOUND; +} + +NS_IMETHODIMP nsAbView::OnItemPropertyChanged(nsISupports *item, const char *property, const char16_t *oldValue, const char16_t *newValue) +{ + nsresult rv; + + nsCOMPtr <nsIAbCard> card = do_QueryInterface(item); + if (!card) + return NS_OK; + + int32_t index = FindIndexForCard(card); + if (index == -1) + return NS_OK; + + AbCard *oldCard = mCards.ElementAt(index); + + // Malloc these from an arena + AbCard *newCard = (AbCard *) PR_Calloc(1, sizeof(struct AbCard)); + if (!newCard) + return NS_ERROR_OUT_OF_MEMORY; + + newCard->card = card; + NS_IF_ADDREF(newCard->card); + + rv = GenerateCollationKeysForCard(mSortColumn.get(), newCard); + NS_ENSURE_SUCCESS(rv,rv); + + bool cardWasSelected = false; + + if (mTreeSelection) { + rv = mTreeSelection->IsSelected(index, &cardWasSelected); + NS_ENSURE_SUCCESS(rv,rv); + } + + if (!CompareCollationKeys(newCard->primaryCollationKey,newCard->primaryCollationKeyLen,oldCard->primaryCollationKey,oldCard->primaryCollationKeyLen) + && CompareCollationKeys(newCard->secondaryCollationKey,newCard->secondaryCollationKeyLen,oldCard->secondaryCollationKey,oldCard->secondaryCollationKeyLen)) { + // No need to remove and add, since the collation keys haven't changed. + // Since they haven't changed, the card will sort to the same place. + // We just need to clean up what we allocated. + NS_IF_RELEASE(newCard->card); + if (newCard->primaryCollationKey) + free(newCard->primaryCollationKey); + if (newCard->secondaryCollationKey) + free(newCard->secondaryCollationKey); + PR_FREEIF(newCard); + + // Still need to invalidate, as the other columns may have changed. + rv = InvalidateTree(index); + NS_ENSURE_SUCCESS(rv,rv); + } + else { + mSuppressSelectionChange = true; + mSuppressCountChange = true; + + // Remove the old card. + rv = RemoveCardAt(index); + NS_ASSERTION(NS_SUCCEEDED(rv), "remove card failed\n"); + + // Add the card we created, and select it (to restore selection) if it was selected. + rv = AddCard(newCard, cardWasSelected /* select card */, &index); + NS_ASSERTION(NS_SUCCEEDED(rv), "add card failed\n"); + + mSuppressSelectionChange = false; + mSuppressCountChange = false; + + // Ensure restored selection is visible + if (cardWasSelected && mTree) + mTree->EnsureRowIsVisible(index); + } + + // Although the selection hasn't changed, the card that is selected may need + // to be displayed differently, therefore pretend that the selection has + // changed to force that update. + if (cardWasSelected) + SelectionChanged(); + + return NS_OK; +} + +NS_IMETHODIMP nsAbView::SelectAll() +{ + if (mTreeSelection && mTree) { + mTreeSelection->SelectAll(); + mTree->Invalidate(); + } + return NS_OK; +} + +NS_IMETHODIMP nsAbView::GetSortDirection(nsAString & aDirection) +{ + aDirection = mSortDirection; + return NS_OK; +} + +NS_IMETHODIMP nsAbView::GetSortColumn(nsAString & aColumn) +{ + aColumn = mSortColumn; + return NS_OK; +} + +nsresult nsAbView::ReselectCards(nsIArray *aCards, nsIAbCard *aIndexCard) +{ + uint32_t count; + uint32_t i; + + if (!mTreeSelection || !aCards) + return NS_OK; + + nsresult rv = mTreeSelection->ClearSelection(); + NS_ENSURE_SUCCESS(rv,rv); + + rv = aCards->GetLength(&count); + NS_ENSURE_SUCCESS(rv, rv); + + // If we don't have any cards selected, nothing else to do. + if (!count) + return NS_OK; + + for (i = 0; i < count; i++) { + nsCOMPtr<nsIAbCard> card = do_QueryElementAt(aCards, i); + if (card) { + int32_t index = FindIndexForCard(card); + if (index != CARD_NOT_FOUND) { + mTreeSelection->RangedSelect(index, index, true /* augment */); + } + } + } + + // Reset the index card, and ensure it is visible. + if (aIndexCard) { + int32_t currentIndex = FindIndexForCard(aIndexCard); + rv = mTreeSelection->SetCurrentIndex(currentIndex); + NS_ENSURE_SUCCESS(rv, rv); + + if (mTree) { + rv = mTree->EnsureRowIsVisible(currentIndex); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsAbView::DeleteSelectedCards() +{ + nsresult rv; + nsCOMPtr<nsIMutableArray> cardsToDelete = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GetSelectedCards(cardsToDelete); + NS_ENSURE_SUCCESS(rv, rv); + + // mDirectory should not be null + // Bullet proof (and assert) to help figure out bug #127748 + NS_ENSURE_TRUE(mDirectory, NS_ERROR_UNEXPECTED); + + rv = mDirectory->DeleteCards(cardsToDelete); + NS_ENSURE_SUCCESS(rv, rv); + return rv; +} + +nsresult nsAbView::GetSelectedCards(nsCOMPtr<nsIMutableArray> &aSelectedCards) +{ + if (!mTreeSelection) + return NS_OK; + + int32_t selectionCount; + nsresult rv = mTreeSelection->GetRangeCount(&selectionCount); + NS_ENSURE_SUCCESS(rv, rv); + + if (!selectionCount) + return NS_OK; + + for (int32_t i = 0; i < selectionCount; i++) + { + int32_t startRange; + int32_t endRange; + rv = mTreeSelection->GetRangeAt(i, &startRange, &endRange); + NS_ENSURE_SUCCESS(rv, NS_OK); + int32_t totalCards = mCards.Length(); + if (startRange >= 0 && startRange < totalCards) + { + for (int32_t rangeIndex = startRange; rangeIndex <= endRange && rangeIndex < totalCards; rangeIndex++) { + nsCOMPtr<nsIAbCard> abCard; + rv = GetCardFromRow(rangeIndex, getter_AddRefs(abCard)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = aSelectedCards->AppendElement(abCard, false); + NS_ENSURE_SUCCESS(rv, rv); + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsAbView::SwapFirstNameLastName() +{ + if (!mTreeSelection) + return NS_OK; + + int32_t selectionCount; + nsresult rv = mTreeSelection->GetRangeCount(&selectionCount); + NS_ENSURE_SUCCESS(rv, rv); + + if (!selectionCount) + return NS_OK; + + // Prepare for displayname generation + // No cache for pref and bundle since the swap operation is not executed frequently + bool displayNameAutoGeneration; + bool displayNameLastnamefirst = false; + + nsCOMPtr<nsIPrefBranch> pPrefBranchInt(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = pPrefBranchInt->GetBoolPref(PREF_MAIL_ADDR_BOOK_DISPLAYNAME_AUTOGENERATION, &displayNameAutoGeneration); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIStringBundle> bundle; + if (displayNameAutoGeneration) + { + nsCOMPtr<nsIPrefLocalizedString> pls; + rv = pPrefBranchInt->GetComplexValue(PREF_MAIL_ADDR_BOOK_DISPLAYNAME_LASTNAMEFIRST, + NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(pls)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString str; + pls->ToString(getter_Copies(str)); + displayNameLastnamefirst = str.EqualsLiteral("true"); + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + + rv = bundleService->CreateBundle("chrome://messenger/locale/addressbook/addressBook.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + } + + for (int32_t i = 0; i < selectionCount; i++) + { + int32_t startRange; + int32_t endRange; + rv = mTreeSelection->GetRangeAt(i, &startRange, &endRange); + NS_ENSURE_SUCCESS(rv, NS_OK); + int32_t totalCards = mCards.Length(); + if (startRange >= 0 && startRange < totalCards) + { + for (int32_t rangeIndex = startRange; rangeIndex <= endRange && rangeIndex < totalCards; rangeIndex++) { + nsCOMPtr<nsIAbCard> abCard; + rv = GetCardFromRow(rangeIndex, getter_AddRefs(abCard)); + NS_ENSURE_SUCCESS(rv, rv); + + // Swap FN/LN + nsAutoString fn, ln; + abCard->GetFirstName(fn); + abCard->GetLastName(ln); + if (!fn.IsEmpty() || !ln.IsEmpty()) + { + abCard->SetFirstName(ln); + abCard->SetLastName(fn); + + // Generate display name using the new order + if (displayNameAutoGeneration && + !fn.IsEmpty() && !ln.IsEmpty()) + { + nsString dnLnFn; + nsString dnFnLn; + const char16_t *nameString[2]; + const char16_t *formatString; + + // The format should stays the same before/after we swap the names + formatString = displayNameLastnamefirst ? + u"lastFirstFormat" : + u"firstLastFormat"; + + // Generate both ln/fn and fn/ln combination since we need both later + // to check to see if the current display name was edited + // note that fn/ln still hold the values before the swap + nameString[0] = ln.get(); + nameString[1] = fn.get(); + rv = bundle->FormatStringFromName(formatString, + nameString, 2, getter_Copies(dnLnFn)); + NS_ENSURE_SUCCESS(rv, rv); + nameString[0] = fn.get(); + nameString[1] = ln.get(); + rv = bundle->FormatStringFromName(formatString, + nameString, 2, getter_Copies(dnFnLn)); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the current display name + nsAutoString dn; + rv = abCard->GetDisplayName(dn); + NS_ENSURE_SUCCESS(rv, rv); + + // Swap the display name if not edited + if (displayNameLastnamefirst) + { + if (dn.Equals(dnLnFn)) + abCard->SetDisplayName(dnFnLn); + } + else + { + if (dn.Equals(dnFnLn)) + abCard->SetDisplayName(dnLnFn); + } + } + + // Swap phonetic names + rv = abCard->GetPropertyAsAString(kPhoneticFirstNameProperty, fn); + NS_ENSURE_SUCCESS(rv, rv); + rv = abCard->GetPropertyAsAString(kPhoneticLastNameProperty, ln); + NS_ENSURE_SUCCESS(rv, rv); + if (!fn.IsEmpty() || !ln.IsEmpty()) + { + abCard->SetPropertyAsAString(kPhoneticFirstNameProperty, ln); + abCard->SetPropertyAsAString(kPhoneticLastNameProperty, fn); + } + } + } + } + } + // Update the tree + // Re-sort if either generated or phonetic name is primary or secondary sort, + // otherwise invalidate to reflect the change + rv = RefreshTree(); + + return rv; +} + +NS_IMETHODIMP nsAbView::GetSelectedAddresses(nsIArray **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + + nsresult rv; + nsCOMPtr<nsIMutableArray> selectedCards = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GetSelectedCards(selectedCards); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMutableArray> addresses = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t count; + selectedCards->GetLength(&count); + + for (uint32_t i = 0; i < count; i++) { + nsCOMPtr<nsIAbCard> card(do_QueryElementAt(selectedCards, i, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isMailList; + card->GetIsMailList(&isMailList); + nsAutoString primaryEmail; + if (isMailList) { + nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, + &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString mailListURI; + rv = card->GetMailListURI(getter_Copies(mailListURI)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectory> mailList; + rv = abManager->GetDirectory(mailListURI, getter_AddRefs(mailList)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMutableArray> mailListAddresses; + rv = mailList->GetAddressLists(getter_AddRefs(mailListAddresses)); + NS_ENSURE_SUCCESS(rv,rv); + + uint32_t mailListCount = 0; + mailListAddresses->GetLength(&mailListCount); + + for (uint32_t j = 0; j < mailListCount; j++) { + nsCOMPtr<nsIAbCard> mailListCard = do_QueryElementAt(mailListAddresses, j, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + rv = mailListCard->GetPrimaryEmail(primaryEmail); + NS_ENSURE_SUCCESS(rv,rv); + + if (!primaryEmail.IsEmpty()) { + nsCOMPtr<nsISupportsString> supportsEmail(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID)); + supportsEmail->SetData(primaryEmail); + addresses->AppendElement(supportsEmail, false); + } + } + } + else { + rv = card->GetPrimaryEmail(primaryEmail); + NS_ENSURE_SUCCESS(rv,rv); + + if (!primaryEmail.IsEmpty()) { + nsCOMPtr<nsISupportsString> supportsEmail(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID)); + supportsEmail->SetData(primaryEmail); + addresses->AppendElement(supportsEmail, false); + } + } + } + + NS_IF_ADDREF(*_retval = addresses); + + return NS_OK; +} |