/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ #include "xp.h" #include "abpane2.h" #include "xp_qsort.h" #include "abpicker.h" #include "msgurlq.h" extern "C" { extern int MK_MSG_NEW_MAIL_MESSAGE; extern int MK_MSG_UNDO; extern int MK_MSG_REDO; extern int MK_ADDR_DELETE_ALL; extern int XP_FILTER_DELETE; extern int MK_MSG_SORT_BACKWARD; extern int MK_MSG_BY_TYPE; extern int MK_MSG_BY_NAME; extern int MK_MSG_BY_NICKNAME; extern int MK_MSG_BY_EMAILADDRESS; extern int MK_MSG_BY_COMPANY; extern int MK_MSG_BY_LOCALITY; extern int MK_MSG_SORT_DESCENDING; extern int MK_MSG_ADD_NAME; extern int MK_MSG_ADD_LIST; extern int MK_MSG_PROPERTIES; extern int MK_MSG_SEARCH_STATUS; extern int XP_BKMKS_IMPORT_ADDRBOOK; extern int XP_BKMKS_SAVE_ADDRBOOK; extern int MK_MSG_CALL; extern int MK_ADDR_NO_EMAIL_ADDRESS; extern int MK_ADDR_DEFAULT_EXPORT_FILENAME; extern int MK_ADDR_ADD; extern int MK_MSG_ADDRESS_BOOK; extern int MK_ACCESS_NAME; } /********************************************************************** AB_Pane is a container listener. Here are the notification handlers.... ************************************************************************/ void AB_Pane::OnContainerAttribChange(AB_ContainerInfo * ctr, AB_NOTIFY_CODE code, AB_ContainerListener * /* instigator */) { // unless the container is going away, this does not effect us at all if (ctr == m_container) { switch (code) { case AB_NotifyScramble: StartingUpdate(MSG_NotifyAll, 0, 0); EndingUpdate(MSG_NotifyAll, 0, 0); break; case AB_NotifyStartSearching: m_IsSearching = TRUE; FE_PaneChanged(this, TRUE, MSG_PaneNotifyStartSearching, 0); break; case AB_NotifyStopSearching: m_IsSearching = FALSE; FE_PaneChanged(this, TRUE, MSG_PaneNotifyStopSearching, 0); break; default: // do nothing.... break; } } } void AB_Pane::OnContainerEntryChange(AB_ContainerInfo * ctr, AB_NOTIFY_CODE code, MSG_ViewIndex index, int32 numChanged, AB_ContainerListener * /*instigator*/) { if (ctr == m_container) { switch (code) { case AB_NotifyNewTopIndex: StartingUpdate(MSG_NotifyNewTopIndex, index, 0); EndingUpdate(MSG_NotifyNewTopIndex, index, 0); break; case AB_NotifyLDAPTotalContentChanged: StartingUpdate(MSG_NotifyLDAPTotalContentChanged, index, numChanged); EndingUpdate(MSG_NotifyLDAPTotalContentChanged, index, numChanged); break; case AB_NotifyDeleted: StartingUpdate(MSG_NotifyInsertOrDelete, index, -1*numChanged); EndingUpdate(MSG_NotifyInsertOrDelete, index, -1*numChanged); break; case AB_NotifyInserted: // if we are LDAP searching, we need to supress this notification...FEs know already that they need // to update themselves. Generating a notification here only confuses them as they are already getting // these from the LDAP Search code. if (!m_IsSearching) { StartingUpdate(MSG_NotifyInsertOrDelete, index, numChanged); EndingUpdate(MSG_NotifyInsertOrDelete, index, numChanged); } break; case AB_NotifyPropertyChanged: if (index != MSG_VIEWINDEXNONE) { StartingUpdate(MSG_NotifyChanged, index, numChanged); EndingUpdate(MSG_NotifyChanged, index, numChanged); } break; case AB_NotifyAll: StartingUpdate(MSG_NotifyAll, 0, 0); EndingUpdate(MSG_NotifyAll, 0, 0); break; default: // we don't know how to handle the error... break; } } } void AB_Pane::OnAnnouncerGoingAway(AB_ContainerAnnouncer * instigator) { // our container is going away!!! if (instigator == m_container) { instigator->RemoveListener(this); // now, to be safe, we should probably set our container to NULL. Since it is now invalid! m_container->Release(); // release our ref on the ctr m_container = NULL; } } /*********************************************************************** Rest of the AB_Pane definitions.... ************************************************************************/ AB_Pane::AB_Pane(MWContext * context, MSG_Master * master, uint32 /*pageSize*/) : MSG_LinedPane(context, master), AB_IndexBasedContainerListener(), AB_PaneAnnouncer() { m_context = context; m_master = master; m_IsSearching = FALSE; m_container = NULL; m_selectionList = NULL; } AB_Pane::~AB_Pane() { if (m_selectionList) delete m_selectionList; NotifyAnnouncerGoingAway(this); // let any pane listener's know we are going away! if (m_container) { m_container->RemoveListener(this); // now, we have obtained a reference for our container, we must dereference it... m_container->Release(); m_container = NULL; // our handle is now invalid! } } int AB_Pane::SetPageSize(uint32 pageSize) { // right now, all we need to do is save the size.... if (pageSize > 0) m_pageSize = pageSize; else m_pageSize = AB_k_DefaultPageSize; // notify our container that our page size has changed!! if (m_container) m_container->UpdatePageSize(pageSize); return AB_SUCCESS; } MSG_PaneType AB_Pane::GetPaneType() { return AB_ABPANE; } /* static */ int AB_Pane::Create(MSG_Pane ** pane, MWContext * context, MSG_Master * master, uint32 pageSize) { *pane = new AB_Pane(context, master, pageSize); if (*pane) return AB_SUCCESS; else return AB_OUT_OF_MEMORY; } /* static */ int AB_Pane::Close(AB_Pane * pane) { if (pane) delete pane; return AB_SUCCESS; } ABID AB_Pane::GetABIDForIndex(const MSG_ViewIndex index) { if (m_container) return m_container->GetABIDForIndex(index); else return AB_ABIDUNKNOWN; } MSG_ViewIndex AB_Pane::GetEntryIndexForID(ABID id) { if (m_container) return m_container->GetIndexForABID(id); else return MSG_VIEWINDEXNONE; } AB_ContainerInfo * AB_Pane::GetContainerForIndex(const MSG_ViewIndex /* index */) { // will eventually detect if index is a mailing list, if so, create a mailing list container info // for the item. Will need to be careful with the reference counting issue. We might need to store away // mailing list containerInfo's in a pane cache so we can release ref counts when the pane goes away. return m_container; } // if you ever want to load from a container, make sure to reference count the container // otherwise you WILL have problems!!! int AB_Pane::LoadContainer(AB_ContainerInfo * container) { // do we need to do anything to close out the old container??? if (m_container != container) // if they are different, do something.... { StartingUpdate(MSG_NotifyAll, 0, 0); if (m_container) { m_container->RemoveListener(this); m_container->Release(); // handles reference count m_container = NULL; } if (container) { m_container = container; m_container->AddListener(this); m_container->UpdatePageSize(m_pageSize); // adjust the reference count m_container->Acquire(); } EndingUpdate(MSG_NotifyAll, 0, 0); // Load the initial entries into the view m_container->PreloadSearch(this); } return AB_SUCCESS; // but what if the create failed?? hmmmm } int AB_Pane::TypedownSearch(const char * typedownValue, MSG_ViewIndex startIndex) { if (m_container) { // pass the buck to the container... return m_container->TypedownSearch(this, typedownValue, startIndex); } else return AB_INVALID_CONTAINER; } int AB_Pane::SearchDirectory(char * searchString) { // make sure the container is an LDAP container if (m_container) { AB_LDAPContainerInfo * ldapCtr = m_container->GetLDAPContainerInfo(); // push details of performing search into the LDAP container level if (ldapCtr) { m_IsSearching = TRUE; return ldapCtr->SearchDirectory(this, searchString); } } return AB_INVALID_CONTAINER; } int AB_Pane::LDAPSearchResults(MSG_ViewIndex index, int32 num) // we got burned here because I made the NC picker inherit from ab_pane...don't use m_container, // use the ctr for the index { if (m_container) { AB_LDAPContainerInfo * ldapCtr = m_container->GetLDAPContainerInfo(); if (ldapCtr) return ldapCtr->LDAPSearchResults(this, index, num); } return AB_FAILURE; } int AB_Pane::FinishSearch() { if (m_IsSearching) { MSG_InterruptSearch(m_context); // m_IsSearching = FALSE; // event if (m_container) { AB_LDAPContainerInfo * ldapCtr = m_container->GetLDAPContainerInfo(); if (ldapCtr) return ldapCtr->FinishSearch(this); } } return AB_SUCCESS; } void AB_Pane::ToggleExpansion(MSG_ViewIndex /* line */, int32 * /* numChanged */) { return; } int32 AB_Pane::ExpansionDelta (MSG_ViewIndex /* line */) { return 0; } int32 AB_Pane::GetNumLines() { if (m_container) return m_container->GetNumEntries(); else return 0; } void AB_Pane::NotifyPrefsChange(NotifyCode /* code */) { // not sure if we need to do anything here yet } AB_AttribID AB_Pane::ConvertCommandToAttribID(AB_CommandType command) { // Why do we need this function when we can hard code it in AB_CommandType switch statements you ask? // Good question. Because the mappings shown here are subject to change. In other words, AB_sortByEmailAddress // command may not always mean sort by email address. It could mean, sort by whatever attribute is in the email // address column. More details to come on this. switch(command) { case AB_SortByColumnID0: return AB_attribEntryType; case AB_SortByColumnID6: return AB_attribNickName; case AB_SortByColumnID5: /* Locality */ return AB_attribLocality; case AB_SortByColumnID2: /* EmailAddress */ return AB_attribEmailAddress; case AB_SortByColumnID3: /* CompanyName */ return AB_attribCompanyName; case AB_SortByColumnID1: /* FullNameCmd */ return AB_attribDisplayName; default: return AB_attribUnknown; } } ABErr AB_Pane::DoCommand(MSG_CommandType command, MSG_ViewIndex * indices, int32 numIndices) { int status = AB_SUCCESS; if (!m_container) return AB_FAILURE; XP_Bool sortedAscending = m_container->GetSortAscending(); AB_AttribID sortedBy; // what we are currently sorted by AB_AttribID sortBy; // what (if any) we want to sort by m_container->GetSortedBy(&sortedBy); // indices could be in any order so let's sort them. if (numIndices > 1) XP_QSORT(indices, numIndices, sizeof(MSG_ViewIndex), CompareViewIndices); FEStart(); // why do we call this?? switch(command) { case AB_UndoCmd: case AB_RedoCmd: break; case AB_ImportCmd: m_container->ImportData(this, NULL, 0, AB_PromptForFileName); break; case AB_SaveCmd: case AB_ExportCmd: m_container->ExportData(this, NULL, 0, AB_PromptForFileName); break; case AB_NewMessageCmd: ApplyCommandToIndices((AB_CommandType)command, indices, numIndices); break; // For sorting commands, if we are already sorted by this type, then toggle ascending/descending order case AB_SortByColumnID0: case AB_SortByColumnID1: case AB_SortByColumnID2: case AB_SortByColumnID3: case AB_SortByColumnID4: case AB_SortByColumnID5: case AB_SortByColumnID6: // handle all the sorting commands in one set of statements.. sortBy = ConvertCommandToAttribID((AB_CommandType)command); if (sortBy == AB_attribUnknown) // if given an invalid attribute, use the current... sortBy = sortedBy; StartingUpdate(MSG_NotifyScramble, 0, 0); if (sortedBy == sortBy) // are they the same? m_container->SetSortAscending(!sortedAscending); else m_container->SortByAttribute(sortBy); EndingUpdate(MSG_NotifyScramble, 0, 0); break; case AB_DeleteAllCmd: /* delete all occurrences...*/ case AB_DeleteCmd: // selected indices could be in any StartingUpdate(MSG_NotifyNone, 0, 0); ApplyCommandToIndices((AB_CommandType)command, indices, numIndices); EndingUpdate(MSG_NotifyNone, 0, 0); break; case AB_AddUserCmd: case AB_AddMailingListCmd: case AB_PropertiesCmd: case AB_ImportLdapEntriesCmd: ApplyCommandToIndices((AB_CommandType)command, indices, numIndices); break; default: status = AB_INVALID_COMMAND; break; } FEEnd(); return status; } int AB_Pane::ApplyCommandToIndices(AB_CommandType command, MSG_ViewIndex * indices, int32 numIndices) { int status = AB_FAILURE; switch(command) { case AB_NewMessageCmd: status = ComposeMessages(indices, numIndices); break; case AB_AddMailingListCmd: status = ShowPropertySheetForNewType(AB_MailingList); break; case AB_AddUserCmd: status = ShowPropertySheetForNewType(AB_Person); break; // nothing to do yet....will eventually want to do a show property sheet... case AB_PropertiesCmd: status = ShowPropertySheet(indices, numIndices); break; case AB_DeleteAllCmd: case AB_DeleteCmd: // we need to sort the indices for deleting to make sure we delete the correct indices first... if (numIndices > 1) XP_QSORT (indices, numIndices, sizeof(MSG_ViewIndex), CompareViewIndices); status = DeleteIndices(indices, numIndices); break; default: XP_ASSERT(0); } return AB_SUCCESS; } int AB_Pane::ComposeMessages (MSG_ViewIndex *indices, int32 numIndices) { char* buf = NULL; char* tmp = NULL; URL_Struct *url_struct; if (numIndices == 0) { // just open a compose window url_struct = NET_CreateURLStruct ("mailto:", NET_NORMAL_RELOAD); if (!url_struct) return eOutOfMemory; url_struct->internal_url = TRUE; GetURL (url_struct, FALSE); return AB_SUCCESS; } else { // get the addresses for the entries for (int32 i = numIndices - 1; i > -1; i--) { char * address = NULL; // okay, convert MSG_ViewIndex into an ABID. Then extract the displayable name completion string for the ABID ABID entryID = GetABIDForIndex (indices[i]); if (entryID != AB_ABIDUNKNOWN) // valid entry? { address = m_container->GetFullAddress(this, entryID); if (address) { if ((numIndices > 1) && (i != 0)) NET_SACat(&address, ", "); NET_SACat(&buf, address); } else { XP_ASSERT(0); // HACK ALERT!!! PROBABLY NEED TO ADD AN ERROR PRINTING DISPLAY MESSAGE HERE } XP_FREEIF(address); // we are done with the address...it has been copied into buf } } if (buf) // do we have at least one address? { tmp = NET_Escape(buf, URL_PATH); XP_FREEIF(buf); buf = tmp; tmp = XP_Cat("mailto:?to=", buf, (char *)/*For Win16*/ NULL); XP_FREEIF(buf); buf = tmp; if (buf) // have the XP_Cats succeeded? { url_struct = NET_CreateURLStruct (buf, NET_NORMAL_RELOAD); if (url_struct) { url_struct->internal_url = TRUE; FE_GetURL(m_context, url_struct); } XP_FREEIF(buf); } } } return AB_SUCCESS; } int AB_Pane::DeleteIndices(MSG_ViewIndex * indices, int32 numIndices) { if (m_container) { // convert the indices to ABIDs and hand the container an array of ABIDs. ABID * ids = (ABID *) XP_ALLOC( sizeof(ABID) * numIndices); if (ids) { for (int i =0; i < numIndices; i++) ids[i] = GetABIDForIndex(indices[i]); AB_CreateAndRunCopyInfoUrl(this, m_container, ids, numIndices, NULL, AB_CopyInfoDelete); // url will delete id array when url is finished... return AB_SUCCESS; } } return AB_INVALID_CONTAINER; } int AB_Pane::ShowPropertySheetForNewType(AB_EntryType entryType) { int status = AB_SUCCESS; MSG_Pane * pane = NULL; if (m_container) { if (entryType == AB_MailingList) pane = new AB_MailingListPane(m_context, m_master, m_container, AB_ABIDUNKNOWN); else if (entryType == AB_Person) pane = new AB_PersonPane(m_context, m_master, m_container, AB_ABIDUNKNOWN, this); } if (pane && m_entryPropSheetFunc) m_entryPropSheetFunc(pane, m_context); // non-blocking FE call back else { AB_ClosePane(pane); // otherwise, close person pane because it is unused.... status = AB_FAILURE; } return status; } int AB_Pane::ShowLDAPPropertySheet(MSG_ViewIndex * indices, int32 numIndices) // fire off HTML window { int status = AB_SUCCESS; char * ldapURL = NULL; // open a window for each entry that was selected for (int32 i = 0; i < numIndices; i++) { AB_ContainerInfo * ctrToUse = GetContainerForIndex(indices[i]); if (ctrToUse && ctrToUse->GetType() == AB_LDAPContainer) { AB_LDAPContainerInfo * ldapCtr = ctrToUse->GetLDAPContainerInfo(); if (ldapCtr) { AB_AttributeValue * value = NULL; AB_AttribID attrib = AB_attribDistName; uint16 numItems = 1; if (GetEntryAttributes(indices[i], &attrib, &value, &numItems ) == AB_SUCCESS) if (value->u.string && XP_STRLEN(value->u.string) > 0) // make sure the string is really there { ldapURL = ldapCtr->BuildLDAPUrl("ldap", value->u.string); if (ldapURL) /* then we need to create a struct for the url to run in */ { URL_Struct *url_s = NET_CreateURLStruct(ldapURL, NET_DONT_RELOAD); FE_CreateNewDocWindow(m_context /* hang it off our context */, url_s); XP_FREE(ldapURL); } AB_FreeEntryAttributeValues(value, numItems); } // getting entry attribute } // for loop } // server check } return status; } int AB_Pane::ShowPropertySheet(MSG_ViewIndex * indices, int32 numIndices) { int status = AB_SUCCESS; for (int i = 0; i < numIndices; i++) { // if we are a picker pane, m_container is not necessarily the ctr to use... // could also have a mixture of property sheet types to be shown.. AB_ContainerInfo * ctrToUse = GetContainerForIndex(indices[i]); if (ctrToUse && ctrToUse->GetType() == AB_LDAPContainer) ShowLDAPPropertySheet(&indices[i], 1); else if (ctrToUse) { // ref count ctr! ctrToUse->Acquire(); // turn the view index into an ABID ABID entryID = GetABIDForIndex(indices[i]); // determine if we are a person or mailing list and create an appropriate pane AB_AttributeValue * value = NULL; AB_EntryType entryType; MSG_Pane * pane = NULL; if (ctrToUse->GetEntryAttribute(this, entryID, AB_attribEntryType, &value) == AB_SUCCESS) { entryType = value->u.entryType; if (entryType == AB_Person) pane = new AB_PersonPane(m_context, m_master, ctrToUse, entryID, this); else if (entryType == AB_MailingList) pane = new AB_MailingListPane(m_context, m_master, ctrToUse, entryID, this); } if (value) AB_FreeEntryAttributeValue(value); if (pane && m_entryPropSheetFunc) m_entryPropSheetFunc(pane, m_context); // non-blocking FE call back else { AB_ClosePane(pane); // otherwise, close person pane because it is unused.... status = AB_FAILURE; } ctrToUse->Release(); ctrToUse = NULL; } // ctrToUse } // for each index return status; } ABErr AB_Pane::GetCommandStatus(MSG_CommandType command, const MSG_ViewIndex * /* indices */, int32 numIndices, XP_Bool * IsSelectableState, MSG_COMMAND_CHECK_STATE * checkedState, const char ** displayString, XP_Bool * pluralState) { int status = AB_FAILURE; XP_Bool isLDAPContainer = FALSE; XP_Bool isOfflineContainer = TRUE; AB_AttribID sortedBy = AB_attribGivenName; // what about a default value... XP_Bool ascending = TRUE; if (m_container) { m_container->GetSortedBy(&sortedBy); ascending = m_container->GetSortAscending(); isLDAPContainer = m_container->GetType() == AB_LDAPContainer; // isOfflineContainer = m_container->GetType() == AB_PABContainer; } // for each command, we need to determine (1) is it enabled? (2) it's check state: unused, checked, unchecked // and (3) the display string to use. // Our defaults are going to be state = MSG_NotUsed, enabled = FALSE, and displayText = 0; const char * displayText = 0; XP_Bool enabled = FALSE; MSG_COMMAND_CHECK_STATE state = MSG_NotUsed; XP_Bool selected = (numIndices > 0); XP_Bool plural = FALSE; switch ((AB_CommandType) command) { /* FILE MENU ========= */ case AB_NewMessageCmd: displayText = XP_GetString(MK_MSG_NEW_MAIL_MESSAGE); // who handles freeing memory for display string?? enabled = TRUE; break; case AB_ImportCmd: if (m_container && !isLDAPContainer) enabled = !(m_container->IsExporting() || m_container->IsImporting()); else enabled = FALSE; displayText = XP_GetString(XP_BKMKS_IMPORT_ADDRBOOK); break; case AB_SaveCmd: case AB_ExportCmd: if (m_container && !isLDAPContainer) enabled = !(m_container->IsExporting() || m_container->IsImporting()); else enabled = FALSE; displayText = XP_GetString(XP_BKMKS_SAVE_ADDRBOOK); break; case AB_CloseCmd: enabled = TRUE; break; /* EDIT MENU ========= */ case AB_UndoCmd: displayText = XP_GetString(MK_MSG_UNDO); enabled = CanUndo(); break; case AB_RedoCmd: displayText = XP_GetString(MK_MSG_REDO); enabled = CanRedo(); break; case AB_DeleteAllCmd: displayText = XP_GetString(MK_ADDR_DELETE_ALL); // HACK ONLY TEMPORARY!!! REALLY NEEDS TO BE A RESOURCE STRING!!! if (m_container) enabled = selected && (m_container->GetType() == AB_PABContainer); else enabled = FALSE; break; case AB_DeleteCmd: displayText = XP_GetString(XP_FILTER_DELETE); if (m_container) enabled = selected && (m_container->GetType() != AB_LDAPContainer ); else enabled = FALSE; // enabled = selected && !isLDAPContainer && !isOfflineContainer; break; case AB_LDAPSearchCmd: displayText = XP_GetString(MK_MSG_SEARCH_STATUS); enabled = isLDAPContainer && !m_IsSearching; // make sure we aren't currently searching! break; /* VIEW/SORT MENUS - disable them if we don't have a container loaded into the pane.. =============== */ // eventually these source strings are going to change to match the prefs.....but for now... case AB_SortByColumnID0: /* AB_SortByTypeCmd */ state = (m_container && sortedBy == AB_attribEntryType) ? MSG_Checked : MSG_Unchecked; displayText = XP_GetString(MK_MSG_BY_TYPE); // only enable sorting commands if a container has been loaded into the pane... enabled = m_container ? TRUE : FALSE; break; case AB_SortByColumnID1: /*AB_SortByFullNameCmd */ state = (m_container && sortedBy == AB_attribDisplayName) ? MSG_Checked : MSG_Unchecked; displayText = XP_GetString(MK_MSG_BY_NAME); enabled = m_container ? TRUE : FALSE; break; case AB_SortByColumnID2: /* AB_SortByEmailAddress */ state = (m_container && sortedBy == AB_attribEmailAddress) ? MSG_Checked : MSG_Unchecked; displayText = XP_GetString(MK_MSG_BY_EMAILADDRESS); enabled = m_container ? TRUE : FALSE; break; case AB_SortByColumnID3: /* AB_SortByCompanyName */ state = (m_container && sortedBy == AB_attribCompanyName) ? MSG_Checked : MSG_Unchecked; displayText = XP_GetString(MK_MSG_BY_COMPANY); enabled = m_container ? TRUE : FALSE; break; case AB_SortByColumnID4: /* Work Phone Number */ state = MSG_NotUsed; displayText = ""; enabled = m_container ? TRUE : FALSE; break; case AB_SortByColumnID5: /* AB_SortByLocality */ state = (m_container && sortedBy == AB_attribLocality) ? MSG_Checked : MSG_Unchecked; displayText = XP_GetString(MK_MSG_BY_LOCALITY); enabled = m_container ? TRUE : FALSE; break; case AB_SortByColumnID6: /* AB_SortByNickname */ state = (m_container && sortedBy == AB_attribNickName) ? MSG_Checked : MSG_Unchecked; displayText = XP_GetString(MK_MSG_BY_NICKNAME); enabled = m_container ? TRUE : FALSE; break; case AB_SortAscending: state = (m_container && ascending) ? MSG_Checked : MSG_Unchecked; displayText = XP_GetString(MK_MSG_SORT_BACKWARD); enabled = m_container ? TRUE : FALSE; break; case AB_SortDescending: state = (m_container && !ascending) ? MSG_Checked : MSG_Unchecked; displayText = XP_GetString(MK_MSG_SORT_DESCENDING); enabled = m_container ? TRUE : FALSE; break; /* ITEM MENU - disable if we don't have a container loaded ============ */ case AB_AddUserCmd: displayText = XP_GetString(MK_MSG_ADD_NAME); enabled = !isLDAPContainer && isOfflineContainer && m_container; break; case AB_AddMailingListCmd: displayText = XP_GetString(MK_MSG_ADD_LIST); enabled = !isLDAPContainer && m_container; break; case AB_PropertiesCmd: displayText = XP_GetString(MK_MSG_PROPERTIES); enabled = selected && m_container; break; case AB_CallCmd: displayText = XP_GetString(MK_MSG_CALL); enabled = (numIndices == 1) && !isLDAPContainer && m_container; break; case AB_ImportLdapEntriesCmd: displayText = XP_GetString(MK_ADDR_ADD); /* enabled = isLDAPContainer && selected && m_container; */ enabled = FALSE; break; default: status = AB_INVALID_COMMAND; // we don't recognize this as a cmd we know hot to enable enabled = FALSE; displayText = NULL; break; } if (IsSelectableState) *IsSelectableState = enabled; if (checkedState) *checkedState = state; if (displayString) *displayString = displayText; if (pluralState) *pluralState = plural; return status; } //****************************************************************** // Drag and Drop //****************************************************************** int AB_Pane::DragEntriesIntoContainer(const MSG_ViewIndex * indices, int32 numIndices, AB_ContainerInfo * destContainer, AB_DragEffect request) { int status = AB_FAILURE; if (m_container && destContainer && numIndices && indices) { if (m_container->GetType() == AB_LDAPContainer) // if ldap, fire off an ldap url for each entry... return AB_ImportLDAPEntriesIntoContainer(this, indices, numIndices, destContainer); // build list of ABIDs...use them with src ctr to copy // baby steps first...ignore what they want and just try copying... ABID * idArray = (ABID *) XP_ALLOC(sizeof(ABID)*numIndices); if (idArray) { for (int32 i = 0; i < numIndices; i++) idArray[i] = GetABIDForIndex(indices[i]); // we have a request....run it through the status to make sure we get a valid request...default request: request = DragEntriesIntoContainerStatus(indices, numIndices, destContainer, request); AB_AddressCopyInfoState state = AB_CopyInfoCopy; switch (request) { case AB_Require_Copy: state = AB_CopyInfoCopy; break; case AB_Require_Move: case AB_Default_Drag: state = AB_CopyInfoMove; break; default: /* including drag not allowed....do nothing */ break; } AB_CreateAndRunCopyInfoUrl(this, m_container, idArray, numIndices, destContainer, state); // idArray will be freed when the url is finished... } else status = AB_OUT_OF_MEMORY; } return status; } AB_DragEffect AB_Pane::DragEntriesIntoContainerStatus(const MSG_ViewIndex * /*indices*/, int32 /*numIndices*/, AB_ContainerInfo * destContainer, AB_DragEffect request) { // ask dest ctr if it accepts entries...LDAP doesn't..MList and PAB should... // if we are an LDAP ctr...we only support copying to destination... XP_Bool mustCopy = FALSE; XP_Bool destAccepts = FALSE; if (m_container && destContainer) // make sure both ctrs are around... { if (!m_container->CanDeleteEntries()) mustCopy = TRUE; if (destContainer->AcceptsNewEntries()) destAccepts = TRUE; if (destAccepts) if (mustCopy) return (AB_DragEffect) (request & AB_Require_Copy); else if ((request & AB_Require_Move) == AB_Require_Move) return AB_Require_Move; else // dest does accept and it doesn't have to be a copy so give them what they want return request; } return AB_Drag_Not_Allowed; } //****************************************************************** // Getting/Setting Entry attributes //****************************************************************** int AB_Pane::GetEntryAttributes(MSG_ViewIndex index, AB_AttribID * attribs, AB_AttributeValue ** values, uint16 * numItems) { // get the appropriate container... if (m_container) return m_container->GetEntryAttributesForIndex(this, index, attribs, values, numItems); else return AB_INVALID_CONTAINER; } int AB_Pane::SetEntryAttributes(MSG_ViewIndex index, AB_AttributeValue * valuesArray, uint16 numItems) { // get the appropriate container... if (m_container) return m_container->SetEntryAttributesForIndex(index, valuesArray, numItems); else return AB_INVALID_CONTAINER; } //****************************************************************** // Sorting //****************************************************************** int AB_Pane::SortByAttribute(AB_AttribID attrib, XP_Bool sortAscending) { if (m_container) return m_container->SortByAttribute(attrib, sortAscending); else return AB_INVALID_CONTAINER; } int AB_Pane::GetPaneSortedBy(AB_AttribID * attrib) { if (m_container) return m_container->GetSortedBy(attrib); else return AB_INVALID_CONTAINER; } //****************************************************************** // Selection //****************************************************************** XP_Bool AB_Pane::UseExtendedSelection() { return m_container->UseExtendedSelection(this); } XP_Bool AB_Pane::AddSelection(MSG_ViewIndex index) { void *entry = m_container->GetSelectionEntryForIndex(index); if (entry != NULL) { if (m_selectionList == NULL) m_selectionList = new XPSortedPtrArray(m_container->CompareSelections); if ( m_selectionList != NULL && m_selectionList->Add(m_container->CopySelectionEntry(entry)) >= 0) return TRUE; } return FALSE; } XP_Bool AB_Pane::IsSelected(MSG_ViewIndex index) { void *entry = m_container->GetSelectionEntryForIndex(index); if (entry != NULL && m_selectionList && m_selectionList->FindIndex(0, entry) >= 0) return TRUE; else return FALSE; } XP_Bool AB_Pane::RemoveSelection(MSG_ViewIndex index) { if (m_selectionList) { if (index == MSG_VIEWINDEXNONE) { for (int i = 0; i < m_selectionList->GetSize(); i++) m_container->DeleteSelectionEntry(m_selectionList->GetAt(i)); m_selectionList->RemoveAll(); return TRUE; } else { void *entry = m_container->GetSelectionEntryForIndex(index); if (entry != NULL) { int i = m_selectionList->FindIndex(0, entry); if (i >= 0) { m_container->DeleteSelectionEntry(m_selectionList->GetAt(i)); m_selectionList->RemoveAt(i); return TRUE; } } } } return FALSE; } /**************************************************************************** Mailing List Pane definitions here.... *****************************************************************************/ /***************************************************************************** Mailing List notification handlers ******************************************************************************/ MSG_ViewIndex AB_MailingListPane::GetIndexForABID(AB_ContainerInfo * ctr, ABID entryID) { if (ctr) { for (int32 i = 0; i < m_entriesView.GetSize(); i++) { AB_MailingListEntry * entry = m_entriesView.GetAt(i); if ( (entry->ctr == ctr) && (entry->entryID == entryID) ) return i; } } return MSG_VIEWINDEXNONE; } AB_MailingListEntry * AB_MailingListPane::GetEntryForIndex(MSG_ViewIndex index) { if (m_entriesView.IsValidIndex(index)) return m_entriesView.GetAt(index); else return NULL; } void AB_MailingListPane::OnContainerEntryChange (AB_ContainerInfo * ctr, AB_NOTIFY_CODE code, ABID entryID, AB_ContainerListener * /* instigator */ ) { // find ctr, entryID in our list.. MSG_ViewIndex index = GetIndexForABID(ctr, entryID); AB_MailingListEntry * entry = GetEntryForIndex(index); switch(code) { case AB_NotifyDeleted: if (index != MSG_VIEWINDEXNONE) { // now remove and free any attributes we may have set StartingUpdate(MSG_NotifyInsertOrDelete, index, -1); m_entriesView.Remove(entry); FreeMailingListEntry(entry); // also releases ref count EndingUpdate(MSG_NotifyInsertOrDelete, index, -1); } break; case AB_NotifyPropertyChanged: if (index != MSG_VIEWINDEXNONE && entry /* make sure it is something in our view */) { StartingUpdate(MSG_NotifyChanged, index, 1); // property change so notify FE EndingUpdate(MSG_NotifyChanged, index, 1); } break; case AB_NotifyInserted: if (m_container && m_container == ctr && !m_commitingEntries) /* make sure we weren't the ones adding the entry... */ { index = m_container->GetIndexForABID(entryID); // our index is bad because we don't have the entry... if (index != MSG_VIEWINDEXNONE) UpdateViewWithEntry(index, m_container, entryID); // insert the committed entry into our view and update FE } break; case AB_NotifyAll: if (ctr == m_container && !m_commitingEntries) { StartingUpdate(MSG_NotifyAll, 0, 0); // update our internal view in case items were inserted or deleted...could be an expensive operation!!! UpdateView(); EndingUpdate(MSG_NotifyAll, 0, 0); } break; default: break; // do nothing } } void AB_MailingListPane::OnContainerAttribChange(AB_ContainerInfo * /* ctr */, AB_NOTIFY_CODE /* code */, AB_ContainerListener * /* instigator */) { return; // we don't show any container attribs! } void AB_MailingListPane::OnAnnouncerGoingAway(AB_ContainerAnnouncer * /* instigator */) { FE_PaneChanged(this, FALSE, MSG_PaneClose, 0); // we are losing our container!!! } // pane listener notifications! void AB_MailingListPane::OnAnnouncerGoingAway(AB_PaneAnnouncer * /* announcer */) { // since we were tied to a pane, we must now instruct the FEs to close us FE_PaneChanged(this, FALSE, MSG_PaneClose, 0); // we are losing our container!!! } /***************************************************************************** Mailing List Pane -> Creating, Initializing, closing, destroying... ******************************************************************************/ AB_MailingListPane::AB_MailingListPane(MWContext * context, MSG_Master * master, AB_ContainerInfo * parentCtr, ABID listABID, AB_PaneAnnouncer * announcer) : MSG_LinedPane(context, master), AB_ABIDBasedContainerListener(), AB_PropertySheet(announcer) { m_ListABID = listABID; m_parentCtr = parentCtr; m_parentCtr->Acquire(); // make sure it doesn't go away on us m_container = NULL; m_commitingEntries = FALSE; m_numEntriesAdded = 0; } AB_MailingListPane::AB_MailingListPane (MWContext * context,MSG_Master * master, AB_MListContainerInfo * mListCtr, AB_PaneAnnouncer * announcer): MSG_LinedPane(context, master), AB_ABIDBasedContainerListener(), AB_PropertySheet(announcer) { m_container = NULL; m_parentCtr = NULL; m_ListABID = AB_ABIDUNKNOWN; if (mListCtr) { m_container = mListCtr; m_container->Acquire(); m_ListABID = mListCtr->GetContainerABID(); m_parentCtr = mListCtr->GetParentContainer(); if (m_parentCtr) m_parentCtr->Acquire(); } } AB_MailingListPane::~AB_MailingListPane() { XP_ASSERT(m_container == NULL); XP_ASSERT(m_entriesView.GetSize() == 0); XP_ASSERT(m_listAttributes.GetSize() == 0); } int AB_MailingListPane::Initialize() { // FE calls this after creating and receiving the mlist pane. They are ready to handle notifications on the pane int status = AB_SUCCESS; StartingUpdate(MSG_NotifyAll, 0, 0); if (m_parentCtr) { m_parentCtr->AddListener(this); // we have already acquired a ref count if (m_ListABID != AB_ABIDUNKNOWN && !m_container) status = AB_ContainerInfo::Create(m_context, m_parentCtr, m_ListABID, &m_container); } if (m_container) { m_newMailingList = FALSE; // we must not be new... m_container->AddListener(this); // we already got the ref count when we created it // now build up our entries view, getting an ABID for each entry for (uint32 index = 0; index < m_container->GetNumEntries(); index++) { AB_MailingListEntry * entry = new AB_MailingListEntry; // use new because it contains a class if (entry) { entry->newEntry = FALSE; // so we know to not commit it again // set up ctr related info entry->entryID = m_container->GetABIDForIndex(index); if (entry->entryID != AB_ABIDUNKNOWN) { entry->nakedAddress = NULL; entry->ctr = m_container; entry->ctr->Acquire(); m_entriesView.Add(entry); } else delete entry; // not going to use it } } } else m_newMailingList = TRUE; EndingUpdate(MSG_NotifyAll, 0, 0); return AB_SUCCESS; } /* static */ int AB_MailingListPane::Close(AB_MailingListPane * pane) { if (pane) { pane->CloseSelf(); delete pane; } return AB_SUCCESS; } int AB_MailingListPane::CloseSelf() // doesn't delete...just closes.. { // free any view entries for (int32 index = 0; index < m_entriesView.GetSize(); index++) FreeMailingListEntry(m_entriesView.GetAt(index)); m_entriesView.RemoveAll(); // free any mailing list attributes for (int j = 0; j < m_listAttributes.GetSize(); j++) AB_FreeEntryAttributeValue(m_listAttributes.GetAt(j)); m_listAttributes.RemoveAll(); if (m_parentCtr) { m_parentCtr->RemoveListener(this); m_parentCtr->Release(); m_parentCtr = NULL; } if (m_container) { m_container->RemoveListener(this); m_container->Release(); m_container = NULL; } return AB_SUCCESS; } AB_ContainerInfo * AB_MailingListPane::GetContainerForMailingList() { return m_container; } MSG_PaneType AB_MailingListPane::GetPaneType() { return AB_MAILINGLISTPANE; } void AB_MailingListPane::ToggleExpansion(MSG_ViewIndex /* line */, int32 * numChanged) { // mailing list entries are not expandable/collapsable *numChanged = 0; } int32 AB_MailingListPane::ExpansionDelta(MSG_ViewIndex /* line */) { return 0; } int32 AB_MailingListPane::GetNumLines() { return m_entriesView.GetSize(); } void AB_MailingListPane::NotifyPrefsChange(NotifyCode /* code */) { return; } ABID AB_MailingListPane::GetABIDForIndex(const MSG_ViewIndex index) { if (m_entriesView.IsValidIndex(index)) { AB_MailingListEntry * entry = GetEntryForIndex(index); if (entry) return entry->entryID; } return AB_ABIDUNKNOWN; // new entry code... } MSG_ViewIndex AB_MailingListPane::GetIndexForABID(ABID /* entryID */) { // do i really need to support this? return MSG_VIEWINDEXNONE; } XP_Bool AB_MailingListPane::CachedMailingListAttribute(AB_AttribID attrib, int * position) // returns the position in m_values if that attribute is stored there. returns TRUE if found, FALSE otherwise. { for (int i = 0; i < m_listAttributes.GetSize(); i++) if (m_listAttributes.GetAt(i)->attrib == attrib) { if (position) *position = i; return TRUE; } if (position) *position = 0; return FALSE; } // Getting / Setting mailing list properties int AB_MailingListPane::GetAttributes(AB_AttribID * attribs, AB_AttributeValue ** valuesArray , uint16 * numItems) { uint16 numItemsSet = 0; int position = 0; AB_AttributeValue * values = NULL; if (*numItems > 0) { values = (AB_AttributeValue *) XP_ALLOC(sizeof(AB_AttributeValue) * (*numItems)); if (values) for (uint16 i = 0; i < *numItems; i++) { if (CachedMailingListAttribute(attribs[i], &position)) // check if it is one of our cached attributes AB_CopyEntryAttributeValue(m_listAttributes.GetAt(position), &values[numItemsSet++]); else if (!m_newMailingList && m_ListABID && m_parentCtr) // is this not a new mailing list? { if (m_parentCtr->GetEntryAttribute(this, m_ListABID, attribs[i], &values[numItemsSet]) == AB_SUCCESS) numItemsSet++; } else AB_CopyDefaultAttributeValue(attribs[i], &values[numItemsSet++]); } } *valuesArray = values; *numItems = numItemsSet; return AB_SUCCESS; } int AB_MailingListPane::SetAttributes(AB_AttributeValue * valuesArray, uint16 numItems) { int position = 0; // step through the array of attribute values and add them to our pane for (uint16 i = 0; i < numItems; i++) { AB_AttributeValue * newValue = (AB_AttributeValue *) XP_ALLOC(sizeof(AB_AttributeValue)); if (newValue) { AB_CopyEntryAttributeValue(&valuesArray[i], newValue); // copy new attribute // determine if it is in our list already... if (CachedMailingListAttribute(valuesArray[i].attrib, &position)) { AB_FreeEntryAttributeValue(m_listAttributes.GetAt(position)); m_listAttributes.RemoveAt(position); m_listAttributes.InsertAt(position, newValue); } else m_listAttributes.Add(newValue); } } return AB_SUCCESS; } // Getting / Setting mailing list entry properties int AB_MailingListPane::GetEntryAttributes(const MSG_ViewIndex index, AB_AttribID * attribs, AB_AttributeValue ** valuesArray, uint16 * numItems) { // we have two types of entries we can fetch...entries with ctrs (they have valid ABIDs) or // entries which were formed from naked addresses if (m_entriesView.IsValidIndex(index)) { AB_MailingListEntry * entry = m_entriesView.GetAt(index); if (entry) if (entry->entryID != AB_ABIDUNKNOWN && entry->ctr) return entry->ctr->GetEntryAttributes(this, entry->entryID, attribs, valuesArray, numItems); else // attempt to fetch from the naked address... return GetEntryAttributesForNakedAddress(entry, attribs, valuesArray, numItems); } return AB_FAILURE; } int AB_MailingListPane::GetEntryAttributesForNakedAddress(AB_MailingListEntry * entry, AB_AttribID * attribs,AB_AttributeValue ** valuesArray, uint16 * numItems) { uint16 numItemsSet = 0; AB_AttributeValue * values = NULL; int status = AB_SUCCESS; if (numItems && (*numItems > 0 && entry)) // want at least one item and we have an entry for that index { values = (AB_AttributeValue *) XP_ALLOC(sizeof(AB_AttributeValue) * (*numItems)); if (values) { for (uint16 i = 0; i < *numItems; i++) // for each requested attribute { values[i].attrib = attribs[i]; if ((attribs[i] == AB_attribEmailAddress || attribs[i] == AB_attribDisplayName || attribs[i] == AB_attribFullAddress) && entry->nakedAddress) values[i].u.string = XP_STRDUP(entry->nakedAddress); else AB_CopyDefaultAttributeValue(attribs[i], &values[i]); //empty attribute } numItemsSet = *numItems; } else status = AB_OUT_OF_MEMORY; } else status = AB_FAILURE; if (numItems) *numItems = numItemsSet; if (valuesArray) *valuesArray = values; return AB_SUCCESS; } int AB_MailingListPane::SetEntryAttributes(const MSG_ViewIndex /*index*/, AB_AttributeValue * /*valuesArray*/, uint16 /*numItems*/) { // no setting of attributes from mailing list pane return AB_SUCCESS; } AB_AttributeValue * AB_MailingListPane::ConvertListAttributesToArray(AB_AttribValueArray * values, uint16 * numItems) { uint16 numAttributes = 0; AB_AttributeValue * newValues = NULL; if (values) { numAttributes = values->GetSize(); if (numAttributes) { newValues = (AB_AttributeValue *) XP_ALLOC(sizeof(AB_AttributeValue) * numAttributes); if (newValues) for (int i = 0; i < numAttributes; i++) AB_CopyEntryAttributeValue(values->GetAt(i), &newValues[i]); } } if (numItems) *numItems = numAttributes; return newValues; } int AB_MailingListPane::SetEntryType() { int position = 0; int status = AB_SUCCESS; if (!CachedMailingListAttribute(AB_attribEntryType, &position)) // if it is not set already.... { AB_AttributeValue * newValue = (AB_AttributeValue *) XP_ALLOC(sizeof(AB_AttributeValue)); if (newValue) { newValue->attrib = AB_attribEntryType; newValue->u.entryType = AB_MailingList; // now add the new value m_listAttributes.Add(newValue); } else status = AB_OUT_OF_MEMORY; } return AB_SUCCESS; } int AB_MailingListPane::CommitNewContainer() { int status = AB_SUCCESS; // create an entry in the parent container for our mailing list, set our list attributes then open a container on it. if (m_newMailingList && m_parentCtr) { SetEntryType(); // convert our mailing list attribute array object into an array of attributes uint16 numAttributes = 0; AB_AttributeValue * newValues = ConvertListAttributesToArray(&m_listAttributes, &numAttributes); if (newValues && numAttributes) { status = m_parentCtr->AddEntry(newValues, numAttributes, &m_ListABID); AB_FreeEntryAttributeValues(newValues, numAttributes); // now create a new container for the mailing list if adding entry succeeded if (m_ListABID != AB_ABIDUNKNOWN) status = AB_ContainerInfo::Create(m_context, m_parentCtr, m_ListABID, &m_container); m_newMailingList = FALSE; } else status = AB_FAILURE; // in order to create a mailing list, we need to have at least one attribute!!! } else status = AB_FAILURE; return status; } int AB_MailingListPane::CommitNakedAddress(AB_MailingListEntry * entry) { int status = AB_FAILURE; AB_AttributeValue * values = NULL; uint16 numItems = 0; // winfe is currently passing in an empty naked address for empty mailing lists...filter that out if (m_container && entry->nakedAddress) { if (XP_STRLEN(entry->nakedAddress)) { status = AB_CreateAttributeValuesForNakedAddress(entry->nakedAddress, &values, &numItems); if (status == AB_SUCCESS) { status = m_container->AddEntry(values, numItems, &entry->entryID); if (status == AB_SUCCESS) // change entry from a naked address to a ctr entry { entry->ctr = m_container; entry->ctr->Acquire(); XP_FREE(entry->nakedAddress); entry->nakedAddress = NULL; } } } AB_FreeEntryAttributeValues(values, numItems); } return status; } int AB_MailingListPane::CommitEntries() // commit entries to the container { // commit mailing list entries by scanning through the view and committing any new entries we find int status = AB_SUCCESS; for (int32 index = 0; index < m_entriesView.GetSize(); index++) { status = AB_FAILURE; AB_MailingListEntry * entry = m_entriesView.GetAt(index); if (entry && entry->newEntry) { if (entry->ctr) // if we have a ctr status = entry->ctr->CopyEntriesTo(m_container, &entry->entryID, 1); else // it is a naked address status = CommitNakedAddress(entry); if (status == AB_SUCCESS) entry->newEntry = FALSE; // entry has now been committed } // if entry else status = AB_FAILURE; } // for loop through the view return status; } int AB_MailingListPane::RemoveDeletedEntriesFromContainer() { int status = AB_FAILURE; int32 numEntries = m_deletedEntries.GetSize(); if (numEntries && m_container) { ABID * idArray = (ABID *) XP_ALLOC(numEntries * sizeof(ABID)); if (idArray) { for (int32 i = 0; i < numEntries; i++) idArray[i] = m_deletedEntries.GetAt(i); m_container->DeleteEntries(idArray,numEntries,this); XP_FREE(idArray); status = AB_SUCCESS; } else status = AB_OUT_OF_MEMORY; } return status; } int AB_MailingListPane::CommitChanges() { // this function is too big right now...unresolved commit issues: do we abort after the first error or keep going? // We have a four step commit process.... // (1) If we are a new mailing list, create a mailing list container // (2) remove any ABIDs from the mailing list container that were deleted from the mailing list pane. // (3) commit mailing list attribute changes // (4) commit mailing list entry attributes (this will in turn generate new mailing list entries in the container if necessary) int status = AB_SUCCESS; m_commitingEntries = TRUE; // mark ourselves as committing if (m_newMailingList && m_parentCtr) // if we are new, then use parent ctr to create new entry status = CommitNewContainer(); // set up m_container in the parent ctr if (m_parentCtr && m_container && status == AB_SUCCESS) { // (2) delete any entries the user deleted from the mailing list pane! RemoveDeletedEntriesFromContainer(); // (3) commit mailing list attribute changes... uint16 numAttributes = 0; AB_AttributeValue * newValues = ConvertListAttributesToArray(&m_listAttributes, &numAttributes); if (newValues && numAttributes > 0) { status = m_parentCtr->SetEntryAttributes(m_ListABID, newValues, numAttributes); AB_FreeEntryAttributeValues(newValues, numAttributes); } // (4) commit mailing list entries by scanning through the view and committing any new entry status = CommitEntries(); } // m_container // remember...actually closing the pane will have the effect of releasing the containers m_commitingEntries = FALSE; // mark ourselves as finished return status; } void AB_MailingListPane::FreeMailingListEntry(AB_MailingListEntry * entry) { if (entry) { if (entry->ctr) { entry->ctr->Release(); entry->ctr = NULL; } if (entry->nakedAddress) XP_FREE(entry->nakedAddress); XP_FREE(entry); } } int AB_MailingListPane::DeleteEntries(const MSG_ViewIndex * indices, int32 numIndices) { // do we need to record the ABIDs we are deleting after we take them out of the view? // I think we do....then at commit time, we remove them from the mailing list container for (int32 i = 0; i < numIndices; i++) if (m_entriesView.IsValidIndex(indices[i])) { AB_MailingListEntry * entry = m_entriesView.GetAt(indices[i]); if (entry) { // we only need to add the entryID to our deleted list if it is in the list ctr already and it is not a newEntry... // if it is a new entry or lives in another ctr, then we don't have to keep track of its removal. if (entry->entryID != AB_ABIDUNKNOWN && entry->ctr == m_container && !entry->newEntry) // does it really exist in the container? // add it to our delete cache so that on commit, we remove it from the container m_deletedEntries.Add((void *) entry->entryID); // now remove and free any attributes we may have set StartingUpdate(MSG_NotifyInsertOrDelete, indices[i], -1); m_entriesView.RemoveAt(indices[i]); FreeMailingListEntry(entry); EndingUpdate(MSG_NotifyInsertOrDelete, indices[i], -1); } } return AB_SUCCESS; } // inserting, replacing entries into the pane.. int AB_MailingListPane::AddEntry(const MSG_ViewIndex index, AB_MailingListEntry * entry, XP_Bool ReplaceCurrent) { MSG_ViewIndex position = m_entriesView.GetSize(); if (m_entriesView.IsValidIndex(index)) position = index; if (ReplaceCurrent) DeleteEntries(&position, 1); StartingUpdate(MSG_NotifyInsertOrDelete, position, 1); m_entriesView.InsertAt(position, entry); EndingUpdate(MSG_NotifyInsertOrDelete, position, 1); return AB_SUCCESS; } int AB_MailingListPane::UpdateViewWithEntry(const MSG_ViewIndex /*index*/ /* to insert at */, AB_ContainerInfo * ctr, ABID entryID) // this function creates a new entry in the view but treats it as an old entry...in other words it was added to the database by // someone else, not the mailing list pane and as such is already committed to the database...so we don't want to commit it again. // Do NOT use this function for everyday adding of entries to the miling list pane. { if (ctr && entryID != AB_ABIDUNKNOWN) { // make sure the entry is not already in the view... MSG_ViewIndex index = GetIndexForABID(ctr, entryID); if (index == MSG_VIEWINDEXNONE) // not found in our view already... { // make the entry then call insert or replace handler... AB_MailingListEntry * entry = (AB_MailingListEntry *) XP_ALLOC(sizeof(AB_MailingListEntry)); if (entry) { entry->ctr = ctr; entry->ctr->Acquire(); // will release when view is closed... entry->entryID = entryID; entry->nakedAddress = NULL; entry->newEntry = FALSE; // because we are adding an entry that was already committed return AddEntry(index, entry, FALSE /* do not replace any existing entries */); } } } return AB_FAILURE; } int AB_MailingListPane::AddEntry(const MSG_ViewIndex index, AB_NameCompletionCookie * cookie, XP_Bool ReplaceCurrent) { if (cookie) { if (cookie->GetEntryContainer()) { // make the entry then call insert or replace handler... AB_MailingListEntry * entry = (AB_MailingListEntry *) XP_ALLOC(sizeof(AB_MailingListEntry)); if (entry) { entry->ctr = cookie->GetEntryContainer(); entry->ctr->Acquire(); // will release when view is closed... entry->entryID = cookie->GetEntryABID(); entry->nakedAddress = NULL; entry->newEntry = TRUE; return AddEntry(index, entry, ReplaceCurrent); } } else // otherwise, try it as a naked address... return AddEntry(index, cookie->GetNakedAddress(), ReplaceCurrent); } return AB_FAILURE; } int AB_MailingListPane::AddEntry(const MSG_ViewIndex index, const char * nakedAddress, XP_Bool ReplaceCurrent) { int status = AB_SUCCESS; if (nakedAddress) { AB_MailingListEntry * entry = (AB_MailingListEntry *) XP_ALLOC(sizeof(AB_MailingListEntry)); if (entry) { entry->entryID = AB_ABIDUNKNOWN; entry->ctr = NULL; entry->nakedAddress = XP_STRDUP(nakedAddress); entry->newEntry = TRUE; status = AddEntry(index, entry, ReplaceCurrent); if (status != AB_SUCCESS) FreeMailingListEntry(entry); } else status = AB_OUT_OF_MEMORY; } return status; } int AB_MailingListPane::InsertEntry (const MSG_ViewIndex index) { // okay, we want to insert an entry at the specified index AB_MailingListEntry * entry = new AB_MailingListEntry; if (entry) { entry->entryID = AB_ABIDUNKNOWN; // we are inserting a new (blank) mailing list entry MSG_ViewIndex position = m_entriesView.GetSize(); // default is always end of view.... if (m_entriesView.IsValidIndex(index)) position = index; // notify FE that we have inserted an entry StartingUpdate(MSG_NotifyInsertOrDelete, position, 1); m_entriesView.InsertAt(position, entry); EndingUpdate(MSG_NotifyInsertOrDelete, position, 1); return AB_SUCCESS; } return AB_FAILURE; } int AB_MailingListPane::ReplaceEntry (const MSG_ViewIndex /* index */) { return AB_SUCCESS; } /***************************************************************************** Mailing List Pane -> Do command and command status ******************************************************************************/ ABErr AB_MailingListPane::DoCommand(MSG_CommandType command, MSG_ViewIndex * indices, int32 numIndices) { switch ((AB_CommandType) command) { case AB_ReplaceLineCmd: // we used to support these commands...but not anymore...they are phasing out case AB_InsertLineCmd: break; case AB_DeleteCmd: return DeleteEntries(indices, numIndices); default: break; } return AB_SUCCESS; } ABErr AB_MailingListPane::GetCommandStatus(MSG_CommandType command, const MSG_ViewIndex * /* indices */, int32 numIndices, XP_Bool * IsSelectableState, MSG_COMMAND_CHECK_STATE * checkedState, const char ** displayString, XP_Bool * pluralState) { // Our defaults are going to be state = MSG_NotUsed, enabled = FALSE, and displayText = 0; const char * displayText = 0; XP_Bool enabled = FALSE; MSG_COMMAND_CHECK_STATE state = MSG_NotUsed; XP_Bool selected = (numIndices > 0); XP_Bool plural = FALSE; switch( (AB_CommandType) command) { case AB_DeleteCmd: enabled = selected; // if something is selected, then we can enable this command displayText = XP_GetString(XP_FILTER_DELETE); // should verify this resource string...I got it from 4.0 break; case AB_ReplaceLineCmd: // no longer supportng these cmds case AB_InsertLineCmd: enabled = FALSE; break; default: break; } if (IsSelectableState) *IsSelectableState = enabled; if (pluralState) *pluralState = plural; if (checkedState) *checkedState = state; if (displayString) *displayString = displayText; return AB_SUCCESS; } int AB_MailingListPane::UpdateView() { // okay, we have no idea if things were added or deleted behind us... // expensive operation.......try to avoid having to call this. // make one pass of m_container looking for additions that we don't have... if (m_container) { ABID entryID = AB_ABIDUNKNOWN; for (MSG_ViewIndex index = 0; index < m_container->GetNumEntries(); index++) { entryID = m_container->GetABIDForIndex(index); if (entryID != AB_ABIDUNKNOWN) { // scan through our view, making sure we have the entry XP_Bool found = FALSE; for (MSG_ViewIndex entriesIndex = 0; entriesIndex < (uint32) m_entriesView.GetSize() && !found; entriesIndex++) { AB_MailingListEntry * entry = GetEntryForIndex(entriesIndex); if (entry && entry->ctr == m_container && entry->entryID == entryID) // match?? found = TRUE; } if (!found) // our view doesn't have the entry so add it.... UpdateViewWithEntry(index, m_container,entryID); } } entryID = AB_ABIDUNKNOWN; // make a pass removing any entries which were removed from us behind the scenes... for (MSG_ViewIndex entriesIndex = 0; entriesIndex < (uint32) m_entriesView.GetSize(); entriesIndex++) { AB_MailingListEntry * entry = GetEntryForIndex(entriesIndex); if (entry) { if (m_container == entry->ctr) DeleteEntries(&entriesIndex, 1); } } } return AB_SUCCESS; } /***************************************************************************** Mailing List Pane -> Drag and Drop ******************************************************************************/ int AB_MailingListPane::DragEntriesIntoContainer(const MSG_ViewIndex * indices, int32 numIndices, AB_ContainerInfo * destContainer, AB_DragEffect /*request*/) { // you should be able to copy or move entries from the mailing list into other ctrs. This is a complicated operation. // Just allowing copy for now because moving is ugly. Remember, the entries in the mailing list pane do not all live in the // ctr unless they have been committed. So we really need to process each one individually. int status = AB_FAILURE; if (destContainer && numIndices && indices) { // extract src ctr for each selected index and perform the action on it. for (int32 i = 0; i < numIndices; i++) { AB_MailingListEntry * entry = GetEntryForIndex(indices[i]); if (entry && entry->ctr && (entry->entryID != AB_ABIDUNKNOWN)) // ignore naked addresses for now { if (entry->ctr->GetType() == AB_LDAPContainer) // if src container is LDAP, build ldap url AB_ImportLDAPEntriesIntoContainer(this, &indices[i], 1, destContainer); else // always perform a copy for now... { AB_AddressCopyInfoState state = AB_CopyInfoCopy; // AB_CreateAndRunCopyInfoUrl wants its own copy of the IDArray...so let's give it to them... ABID * entryID = (ABID *) XP_ALLOC(sizeof(ABID)); if (entryID) { *entryID = entry->entryID; AB_CreateAndRunCopyInfoUrl(this, entry->ctr, entryID, 1, destContainer, state); } else status = AB_OUT_OF_MEMORY; } } } status = AB_SUCCESS; } return status; } AB_DragEffect AB_MailingListPane::DragEntriesIntoContainerStatus(const MSG_ViewIndex * /*indices*/, int32 /*numIndices*/, AB_ContainerInfo * destContainer, AB_DragEffect request) { // ask dest ctr if it accepts entries...LDAP doesn't..MList and PAB should... // if we are an LDAP ctr...we only support copying to destination... XP_Bool mustCopy = FALSE; XP_Bool destAccepts = FALSE; if (m_container && destContainer) // make sure both ctrs are around... { if (m_container->CanDeleteEntries()) mustCopy = TRUE; if (destContainer->AcceptsNewEntries()) destAccepts = TRUE; if (destAccepts) if (mustCopy) return (AB_DragEffect) (request & AB_Require_Copy); else if ((request & AB_Require_Move) == AB_Require_Move) return AB_Require_Move; else // dest does accept and it doesn't have to be a copy so give them what they want return request; } return AB_Drag_Not_Allowed; } /*************************************************************************** AB_PropertySheet base class definitions. AB_PersonPane and AB_MailingListPane are both AB_PropertySheets. We use this so we can have a generic AB_ABPaneView class later on. *****************************************************************************/ AB_PropertySheet::AB_PropertySheet(AB_PaneAnnouncer * announcer) { m_abPaneView = new AB_ABPaneView(announcer, this); } AB_PropertySheet::~AB_PropertySheet() { if (m_abPaneView) delete m_abPaneView; } /**************************************************************************** Person Entry Pane definitions here....(and helper AB_ABPaneView class) *****************************************************************************/ AB_ABPaneView::AB_ABPaneView(AB_PaneAnnouncer * announcer, AB_PropertySheet * propSheet) { if (propSheet) m_propertySheet = propSheet; else m_propertySheet = NULL; if (announcer) { m_paneAnnouncer = announcer; m_paneAnnouncer->AddListener(this); } else m_paneAnnouncer = NULL; } AB_ABPaneView::~AB_ABPaneView() { if (m_paneAnnouncer) { m_paneAnnouncer->RemoveListener(this); // I'm going away! m_paneAnnouncer = NULL; } } void AB_ABPaneView::OnAnnouncerGoingAway(AB_PaneAnnouncer * instigator) { if (instigator == m_paneAnnouncer) { m_paneAnnouncer->RemoveListener(this); m_paneAnnouncer = NULL; if (m_propertySheet) m_propertySheet->OnAnnouncerGoingAway(instigator); // forward the notification onto the person pane } } /* AB_PersonPane */ AB_PersonPane::AB_PersonPane(MWContext * context, MSG_Master * master, AB_ContainerInfo * ctr, ABID entryID, AB_PaneAnnouncer * announcer, XP_Bool identityPane) : MSG_Pane(context, master), AB_ABIDBasedContainerListener(), AB_PropertySheet(announcer) { m_container = ctr; m_entryID = entryID; // look up ABID in container, if it exists, load attributes set. if (m_container) m_newEntry = !m_container->IsInContainer(entryID); else m_newEntry = TRUE; m_identityPane = identityPane; if (m_identityPane) m_newEntry = FALSE; Initialize(); } /* static */ int AB_PersonPane::CreateIdentityPane(MWContext * context, MSG_Master * master, AB_AttributeValue * identityValues, uint16 numItems, MSG_Pane ** pane) { int status = AB_SUCCESS; AB_PersonPane * pPane = NULL; if (pane) { pPane = new AB_PersonPane(context, master, NULL,AB_ABIDUNKNOWN, NULL, TRUE); if (pPane) status = pPane->InitializeWithAttributes(identityValues, numItems); else status = AB_FAILURE; *pane = pPane; } return status; } AB_PersonPane::~AB_PersonPane() { XP_ASSERT(m_container == NULL); XP_ASSERT(m_values.GetSize() == 0);; } void AB_PersonPane::OnAnnouncerGoingAway(AB_PaneAnnouncer * /* announcer */) { // since we were tied to a pane, we must now instruct the FEs to close us FE_PaneChanged(this, FALSE, MSG_PaneClose, 0); // we are losing our container!!! } int AB_PersonPane::CloseSelf() // non-static version { if (m_container) { m_container->RemoveListener(this); m_container->Release(); m_container = NULL; } for (int i = 0; i < m_values.GetSize(); i++) AB_FreeEntryAttributeValue(m_values.GetAt(i)); m_values.RemoveAll(); return AB_SUCCESS; } /* static */ int AB_PersonPane::Close(AB_PersonPane * pane) { // eventually we will have to destroy our attribute values array if it is still around (i.e. user was never committed) if (pane) { pane->CloseSelf(); delete pane; } return AB_SUCCESS; } void AB_PersonPane::Initialize() { if (m_container) { m_container->AddListener(this); m_container->Acquire(); // ref count our container! } } /* Use this function to load the property sheet with a set of attribute values. The property sheet makes copies of the attributes...you are still responsible for freeing your attrib value array */ int AB_PersonPane::InitializeWithAttributes(AB_AttributeValue * values, uint16 numItems) { return SetAttributes(values, numItems); } MSG_PaneType AB_PersonPane::GetPaneType() { return AB_PERSONENTRYPANE; } void AB_PersonPane::OnContainerAttribChange(AB_ContainerInfo * /* ctr */, AB_NOTIFY_CODE /* code */, AB_ContainerListener * /* instigator */) { return; // we don't show any container attribs! } void AB_PersonPane::OnContainerEntryChange (AB_ContainerInfo * /* ctr */, AB_NOTIFY_CODE code, ABID entryID, AB_ContainerListener * /* instigator */ ) { if (entryID == m_entryID) // are we effected? { switch(code) { case AB_NotifyDeleted: FE_PaneChanged(this, FALSE, MSG_PaneClose, 0); break; case AB_NotifyPropertyChanged: FE_PaneChanged(this, FALSE, MSG_PaneChanged, 0); break; default: break; // do nothing } } } void AB_PersonPane::OnAnnouncerGoingAway(AB_ContainerAnnouncer * /* instigator */) { FE_PaneChanged(this, FALSE, MSG_PaneClose, 0); // we are losing our container!!! } AB_ContainerInfo * AB_PersonPane::GetContainerForPerson() { return m_container; // should we pass back a const return type instead?? } ABID AB_PersonPane::GetABIDForPerson() { return m_entryID; } XP_Bool AB_PersonPane::CachedAttributePosition(AB_AttribID attrib, int * position) // returns the position in m_values if that attribute is stored there. returns TRUE if found, FALSE otherwise. { for (int i = 0; i < m_values.GetSize(); i++) if (m_values.GetAt(i)->attrib == attrib) { *position = i; return TRUE; } *position = 0; return FALSE; } int AB_PersonPane::SetAttributes(AB_AttributeValue * valuesArray, uint16 numItems) { int position = 0; // step through the array of attribute values and add them to our pane for (uint16 i = 0; i < numItems; i++) { AB_AttributeValue * newValue = (AB_AttributeValue *) XP_ALLOC(sizeof(AB_AttributeValue)); if (newValue) { AB_CopyEntryAttributeValue(&valuesArray[i], newValue); // copy new attribute // determine if it is in our list already... if (CachedAttributePosition(valuesArray[i].attrib, &position)) { AB_FreeEntryAttributeValue(m_values.GetAt(position)); m_values.RemoveAt(position); m_values.InsertAt(position, newValue); } else m_values.Add(newValue); } } return AB_SUCCESS; } int AB_PersonPane::GetAttributes(AB_AttribID * attribs, AB_AttributeValue ** valuesArray, uint16 * numItems) { // in the long run, we are either going to want to return attributes from the database (if it is not a new // entry and they have not set any attributes) OR return our cache of attributes that have previously been set. uint16 numItemsSet = 0; int position = 0; AB_AttributeValue * values = NULL; if (*numItems > 0) { values = (AB_AttributeValue *) XP_ALLOC(sizeof(AB_AttributeValue) * (*numItems)); if (values) for (uint16 i = 0; i < *numItems; i++) { if (CachedAttributePosition(attribs[i], &position)) // check if it is one of our cached attributes AB_CopyEntryAttributeValue(m_values.GetAt(position), &values[numItemsSet++]); else if (!m_newEntry && m_container) // does it exist in the container? if (m_container->GetEntryAttribute(this, m_entryID, attribs[i], &values[numItemsSet]) == AB_SUCCESS) numItemsSet++; } } *valuesArray = values; *numItems = numItemsSet; return AB_SUCCESS; } int AB_PersonPane::SetEntryType() { int position = 0; int status = AB_SUCCESS; if (!CachedAttributePosition(AB_attribEntryType, &position)) // if it is not set already.... { AB_AttributeValue * newValue = (AB_AttributeValue *) XP_ALLOC(sizeof(AB_AttributeValue)); if (newValue) { newValue->attrib = AB_attribEntryType; newValue->u.entryType = AB_Person; // now add the new value m_values.Add(newValue); } else status = AB_OUT_OF_MEMORY; } return AB_SUCCESS; } int AB_PersonPane::CommitIdentityPane(AB_AttributeValue * values, uint16 numAttributes) // write any attributes out to the personal identity vcard which is stored in prefs. { int status = AB_SUCCESS; if (values) { char * vCard = NULL; status = AB_ConvertAttribValuesToVCard(values, numAttributes, &vCard); if (vCard) { status = AB_ExportVCardToPrefs(vCard); XP_FREE(vCard); } } return status; } int AB_PersonPane::CommitChanges() { int status = AB_SUCCESS; // write out any cached attributes to the container if (m_values.GetSize() > 0) { if (m_newEntry) SetEntryType(); uint16 numAttributes = m_values.GetSize(); // we already know this is > 0 AB_AttributeValue * newValues = (AB_AttributeValue *) XP_ALLOC(sizeof(AB_AttributeValue) * numAttributes); if (newValues) { for (int i = 0; i < m_values.GetSize(); i++) AB_CopyEntryAttributeValue(m_values.GetAt(i), &newValues[i]); if (m_identityPane) status = CommitIdentityPane(newValues, numAttributes); else if (m_newEntry && m_container) status = m_container->AddEntry(newValues, numAttributes, &m_entryID); else if (m_container) // entry must already exist.... status = m_container->SetEntryAttributes(m_entryID, newValues, numAttributes); AB_FreeEntryAttributeValues(newValues, numAttributes); } else status = AB_OUT_OF_MEMORY; } return status; }