/* -*- 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 "msg.h" #include "errcode.h" #include "xp_str.h" #include "xpgetstr.h" #include "msgfpane.h" #include "msgimap.h" #include "newshost.h" #include "imaphost.h" #include "msgtpane.h" #include "msgmpane.h" #include "msgprefs.h" #include "msgdbvw.h" #include "mailhdr.h" #include "maildb.h" #include "msgmast.h" #include "msg_filt.h" #include "pmsgfilt.h" #include "pmsgsrch.h" #include "prsembst.h" #include "grpinfo.h" #include "msgrulet.h" #include "dwordarr.h" #include "newshost.h" #include "msgundac.h" #include "msgurlq.h" #include "hosttbl.h" #include "xp_qsort.h" #include "prefapi.h" extern "C" { extern int MK_OUT_OF_MEMORY; extern int MK_MSG_OFFER_COMPRESS; extern int MK_MSG_OPEN_FOLDER; extern int MK_MSG_OPEN_FOLDER2; extern int MK_MSG_NEW_FOLDER; extern int MK_MSG_COMPRESS_FOLDER; extern int MK_MSG_COMPRESS_ALL_FOLDER; extern int MK_MSG_OPEN_NEWS_HOST_ETC; extern int MK_MSG_ADD_NEWS_GROUP; extern int MK_MSG_EMPTY_TRASH_FOLDER; extern int MK_MSG_UNDO; extern int MK_MSG_REDO; extern int MK_MSG_DELETE_FOLDER; extern int MK_MSG_RMV_NEWS_HOST; extern int MK_MSG_SUBSCRIBE; extern int MK_MSG_UNSUBSCRIBE; extern int MK_MSG_NEW_NEWS_MESSAGE; extern int MK_MSG_MARK_ALL_READ; extern int MK_MSG_CANT_OPEN; extern int MK_MSG_ENTER_FOLDERNAME; extern int MK_MSG_RENAME_FOLDER; extern int MK_MSG_DELETE_FOLDER_MESSAGES; extern int MK_MSG_FOLDER_ALREADY_EXISTS; extern int MK_MSG_CAN_ONLY_DELETE_MAIL_FOLDERS; extern int XP_NEWS_PROMPT_ADD_NEWSGROUP; extern int MK_MSG_UNSUBSCRIBE_GROUP; extern int MK_MSG_UNSUBSCRIBE_PROFILE_GROUP; extern int MK_MSG_PANES_OPEN_ON_FOLDER; extern int MK_MSG_ILLEGAL_CHARS_IN_NAME; extern int MK_MSG_NEW_NEWSGROUP; extern int MK_MSG_CANT_MOVE_FOLDER_TO_CHILD; extern int MK_MSG_NEW_GROUPS_DETECTED; extern int MK_MSG_REMOVE_HOST_CONFIRM; extern int MK_MSG_NO_POP_HOST; extern int MK_MSG_RENAME_FOLDER_CAPTION; extern int MK_MSG_NEW_FOLDER_CAPTION; extern int MK_MSG_MANAGE_MAIL_ACCOUNT; extern int MK_MSG_UNABLE_MANAGE_MAIL_ACCOUNT; extern int MK_POP3_NO_MESSAGES; extern int MK_MSG_MODERATE_NEWSGROUP; extern int MK_MSG_CANT_MOVE_INBOX; extern int MK_MSG_CONFIRM_MOVE_MAGIC_FOLDER; extern int MK_MSG_UPDATE_MSG_COUNTS; extern int MK_MSG_MOVE_TARGET_NO_INFERIORS; extern int MK_MSG_PARENT_TARGET_NO_INFERIORS; extern int MK_MSG_TRASH_NO_INFERIORS; extern int MK_MSG_TRACK_FOLDER_MOVE; extern int MK_MSG_TRACK_FOLDER_DEL; extern int MK_MSG_NO_MAIL_TO_NEWS; extern int MK_MSG_NO_NEWS_TO_MAIL; extern int MK_MSG_UNIQUE_TRASH_RENAME_FAILED; extern int MK_MSG_NEEDED_UNIQUE_TRASH_NAME; extern int MK_MSG_CANT_MOVE_FOLDER; extern int MK_MSG_CONFIRM_MOVE_FOLDER_TO_TRASH; extern int MK_MSG_REMOVE_IMAP_HOST_CONFIRM; } #ifdef XP_OS2 #define qsort(a,b,c,d) qsort(a,b,c,(int (_Optlink*) (const void*,const void*)) d) #endif MSG_FolderPane::MSG_FolderPane(MWContext* context, MSG_Master* master) : MSG_LinedPane(context, master) { } MsgERR MSG_FolderPane::Init() { FEStart(); MSG_FolderInfo* folderTree = m_master->GetFolderTree(); int32 num = folderTree->GetNumSubFolders(); if (num != 0) { for (int32 i = 0; i < num; i++) { // Add the top level folder MSG_FolderInfo *folder = folderTree->GetSubFolder((MSG_ViewIndex) i); if (folder->CanBeInFolderPane ()) { int index = m_folderView.Add (folder); m_flagsArray.Add(folder->GetFlags() & 0xFF); // Add any children of the folder which might be expanded if (!(folder->GetFlags() & MSG_FOLDER_FLAG_ELIDED) && !(folder->GetType() == FOLDER_CATEGORYCONTAINER)) { MSG_FolderArray array; GetExpansionArray (folder, array); if (array.GetSize() != 0) { InsertFlagsArrayAt(++index, array); m_folderView.InsertAt (index, &array); } } } } } FEEnd(); // for fun, ### dmb - remove when we figure out how notifications should work FlushUpdates(); return eSUCCESS; } MSG_FolderPane::~MSG_FolderPane() { if (m_scanTimer) FE_ClearTimeout (m_scanTimer); } void MSG_FolderPane::RedisplayAll() { StartingUpdate(MSG_NotifyAll, 0, 0); m_folderView.RemoveAll(); m_flagsArray.RemoveAll(); Init(); EndingUpdate(MSG_NotifyAll, 0, 0); } MSG_PaneType MSG_FolderPane::GetPaneType() { return MSG_FOLDERPANE; } void MSG_FolderPane::NotifyPrefsChange(NotifyCode) { // ###tw Write me! } void MSG_FolderPane::InsertFlagsArrayAt(MSG_ViewIndex index, MSG_FolderArray &folders) { for (int32 arrayIndex = 0; arrayIndex < folders.GetSize(); arrayIndex++) { MSG_FolderInfo *arrayFolder = folders.GetAt(arrayIndex); m_flagsArray.InsertAt(index + arrayIndex, arrayFolder->GetFlags() & 0xFF); } } XP_Bool MSG_FolderPane::ScanFolder_1() { // ###tw To heck with it. This should search through all the folders, and // find one that does not have an accurate count of unread messages in // memory. If it finds one, then this should attempt to quickly get that // number from the database, and then return TRUE. If every folder either // has an accurate count or we have already determined an accurate count // can't be gotten quickly, then this routine should return FALSE. for (int32 i=0 ; iGetNumUnread() == -1) { if (folder->GetType() == FOLDER_MAIL || folder->IsNews()) { folder->UpdateSummaryTotals(); GetMaster()->BroadcastFolderChanged(folder); return TRUE; } } } return FALSE; } void MSG_FolderPane::ScanFolder_s(void* closure) { ((MSG_FolderPane*) closure)->ScanFolder(); } void MSG_FolderPane::ScanFolder() { m_scanTimer = NULL; if (ScanFolder_1()) { if (m_scanTimer == NULL) { m_scanTimer = FE_SetTimeout((TimeoutCallbackFunction)MSG_FolderPane::ScanFolder_s, this, 1); } } else { if (m_wantCompress && !m_offeredCompress) { char* buf; if (NET_AreThereActiveConnectionsForWindow(m_context)) { /* Looks like we're busy doing something, probably incorporating new mail. Don't interrupt that; we can wait until we're idle to ask our silly compression question. Just try again in a half-second. */ m_scanTimer = FE_SetTimeout((TimeoutCallbackFunction) MSG_FolderPane::ScanFolder_s, this, 500); return; } buf = PR_smprintf(XP_GetString(MK_MSG_OFFER_COMPRESS), /*###tw msg_addup_wasted_bytes(f->folders)*/ 0 / 1024); if (buf) { m_offeredCompress = TRUE; /* Set it now, before FE_Confirm gets called, as timers and things can get called from FE_Confirm, and we could end up back here again. */ if (FE_Confirm(m_context, buf)) { msg_InterruptContext(m_context, FALSE); CompressAllFolders(); } XP_FREE(buf); } } m_offeredCompress = TRUE; } } void MSG_FolderPane::FlushUpdates() { // ###tw This stuff probably needs to get moved somewhere else, and this // FlushUpdates() method should probably go away... if (m_scanTimer == NULL) { /* What a disgusting way to do a background job. ### */ m_scanTimer = FE_SetTimeout((TimeoutCallbackFunction) MSG_FolderPane::ScanFolder_s, this, 1); } } XP_Bool MSG_FolderPane::GetFolderLineByIndex(MSG_ViewIndex line, int32 numlines, MSG_FolderLine* data) { if (line == MSG_VIEWINDEXNONE || line + numlines > (MSG_ViewIndex) m_folderView.GetSize() || numlines < 1) return FALSE; for (int i=0 ; iGetFolderLineById(folder, data + i); int8 flags = m_flagsArray.GetAt(line + i); if (flags & MSG_FOLDER_FLAG_ELIDED) data->flags |= MSG_FOLDER_FLAG_ELIDED; else data->flags &= ~MSG_FOLDER_FLAG_ELIDED; XP_ASSERT(result); if (!result) return FALSE; } return TRUE; } int MSG_FolderPane::GetFolderLevelByIndex(MSG_ViewIndex line) { if (line == MSG_VIEWINDEXNONE || line >= (MSG_ViewIndex) m_folderView.GetSize()) return 0; MSG_FolderLine data[1]; MSG_FolderInfo *folder = m_folderView.GetAt(line); XP_Bool result = m_master->GetFolderLineById(folder, data); XP_ASSERT(result); if (!result) return 0; return data[0].level; } int MSG_FolderPane::GetFolderMaxDepth() { int result = 0; for (MSG_ViewIndex i=0 ; i < (MSG_ViewIndex) m_folderView.GetSize() ; i++) { uint8 depth = m_folderView.GetAt(i)->GetDepth(); if (result < depth) result = depth; } return result; } char* MSG_FolderPane::GetFolderName(MSG_ViewIndex line) { XP_ASSERT(int(line) >= 0 && line < (MSG_ViewIndex) m_folderView.GetSize()); XP_ASSERT ( m_folderView.GetAt(line)->IsMail() ); if (int(line) >= 0 && line < (MSG_ViewIndex) m_folderView.GetSize()) { MSG_FolderInfoMail* f = m_folderView.GetAt(line)->GetMailFolderInfo(); if (f && (f->GetType() == FOLDER_MAIL) || (f->GetType() == FOLDER_IMAPMAIL)) return XP_STRDUP(f->GetPathname()); } return NULL; } void MSG_FolderPane::MakeFolderVisible (MSG_FolderInfo *folder) { MSG_ViewIndex idx = GetFolderIndex (folder); if (idx == MSG_VIEWINDEXNONE) { MSG_FolderInfo *parent = m_master->GetFolderTree()->FindParentOf (folder); if (parent) { MakeFolderVisible (parent); idx = GetFolderIndex (folder); } } if (idx != MSG_VIEWINDEXNONE && m_flagsArray.GetAt(idx) & MSG_FOLDER_FLAG_ELIDED) { int32 unused; ToggleExpansion (idx, &unused); } } MSG_ViewIndex MSG_FolderPane::GetFolderIndex(MSG_FolderInfo *folder, XP_Bool expand /*= FALSE*/) { MSG_ViewIndex idx = (MSG_ViewIndex) m_folderView.FindIndex (0, folder); if (idx == MSG_VIEWINDEXNONE && expand) { MakeFolderVisible (folder); idx = (MSG_ViewIndex) m_folderView.FindIndex (0, folder); } return idx; } MSG_FolderInfo *MSG_FolderPane::GetFolderInfo(MSG_ViewIndex index) { XP_ASSERT(index != MSG_VIEWINDEXNONE); XP_ASSERT(index < (MSG_ViewIndex) m_folderView.GetSize()); if (index == MSG_VIEWINDEXNONE || index >= (MSG_ViewIndex) m_folderView.GetSize()) return NULL; return m_folderView.GetAt(index); } /* becoming obsolete with switch to new Subscribe MSG_Host* APIs */ MSG_NewsHost *MSG_FolderPane::GetNewsHostFromIndex (MSG_ViewIndex index) { MSG_NewsHost *host = NULL; MSG_FolderInfo *folder = GetFolderInfo (index); msg_HostTable *table = m_master->GetHostTable(); if (folder && table) host = table->FindNewsHost (folder); return host; } MSG_Host *MSG_FolderPane::GetHostFromIndex (MSG_ViewIndex index) { MSG_FolderInfo *folder = GetFolderInfo (index); if (folder) { MSG_FolderInfoNews *newsFolder = folder->GetNewsFolderInfo(); if (newsFolder) return newsFolder->GetHost(); else if (FOLDER_CONTAINERONLY == folder->GetType()) return ((MSG_FolderInfoContainer*) folder)->GetHost(); else if (FOLDER_IMAPSERVERCONTAINER == folder->GetType()) return ((MSG_IMAPFolderInfoContainer*) folder)->GetIMAPHost(); else { MSG_IMAPFolderInfoMail *imapFolder = folder->GetIMAPFolderInfoMail(); if (imapFolder) { return imapFolder->GetIMAPHost(); } } } return NULL; } void MSG_FolderPane::OnFolderChanged(MSG_FolderInfo *folderInfo) { MSG_ViewIndex index = GetFolderIndex(folderInfo); if (index != MSG_VIEWINDEXNONE) { StartingUpdate (MSG_NotifyChanged, index, 1); EndingUpdate (MSG_NotifyChanged, index, 1); } MSG_LinedPane::OnFolderChanged (folderInfo); } void MSG_FolderPane::OpenFolderCB_s(MWContext *context, char *file_name, void *closure) { XP_ASSERT(((MSG_FolderPane*) closure)->m_context == context); ((MSG_FolderPane*) closure)->OpenFolderCB(file_name); } void MSG_FolderPane::OpenFolderCB(char* file_name) { if (!file_name) return; MSG_FolderInfo* folder = FindMailFolder(file_name, TRUE); if (folder) { // ###tw Uh, do something to let the FE's know about it? Maybe not... } } MsgERR MSG_FolderPane::OpenFolder () { FE_PromptForFileName (m_context, XP_GetString(MK_MSG_OPEN_FOLDER), 0, /* default_path */ TRUE, FALSE, (ReadFileNameCallbackFunction) MSG_FolderPane::OpenFolderCB_s, this); return 0; } // The IMAP libnet module is still discovering folders after this // pane was created. So we need a way to add folders as they come in. void MSG_FolderPane::OnFolderAdded(MSG_FolderInfo *addedFolder, MSG_Pane *instigator) { MSG_FolderInfo *tree = m_master->GetFolderTree(); XP_ASSERT(tree); MSG_FolderInfo *parentFolder = tree->FindParentOf(addedFolder); if (parentFolder) { // at this point the parent folder already contains the child. // find the index of the child within the parent's list MSG_FolderArray subFolders; parentFolder->GetVisibleSubFolders(subFolders); int32 newPosition = subFolders.FindIndex (0, addedFolder); // Add the folder to the folder pane's view AddFolderToView(addedFolder, parentFolder, newPosition); // Tell the FE that a new folder was created that they should select // notification for imap folders happens in create url exit function if (!(addedFolder->GetFlags() & MSG_FOLDER_FLAG_IMAPBOX)) FE_PaneChanged (this, FALSE, MSG_PaneNotifySelectNewFolder, MSG_VIEWINDEXNONE); } else { // this is likely the container? // this was being hit when a UofW server was serving up /u/kmcentee XP_ASSERT(FALSE); } MSG_LinedPane::OnFolderAdded (addedFolder, instigator); } void MSG_FolderPane::PostProcessRemoteCopyAction() { if (m_RemoteCopyState) { MsgERR err = m_RemoteCopyState->DoNextAction(); if (err) { delete m_RemoteCopyState; m_RemoteCopyState = NULL; } } } void MSG_FolderPane::AddFoldersToView (MSG_FolderArray &folders, MSG_FolderInfo *parent, int32 newFolderIdx) { // This is a hack to allow creating folders inside the folderPane c-tor. Yuck. if (m_folderView.GetSize() == 0) return; if (parent == NULL) parent = GetMaster()->GetFolderTree(); // If the parent is not already visible, expand any folders we need to so the // newly added children will be visible. MSG_ViewIndex parentIdx = GetFolderIndex (parent, TRUE /*expand*/); // If this is the first child for this parent, the FE needs to // redraw the parent line to get the expand/collapse widget if ((parent->GetNumSubFolders() == 1) && (parentIdx != MSG_VIEWINDEXNONE)) { StartingUpdate (MSG_NotifyChanged, parentIdx, 1); parent->SetFlag (MSG_FOLDER_FLAG_DIRECTORY); // tell the view ClearFlagForFolder(parent, MSG_FOLDER_FLAG_ELIDED); // make sure elided bit isn't set. EndingUpdate (MSG_NotifyChanged, parentIdx, 1); } if (parentIdx != MSG_VIEWINDEXNONE && !(m_flagsArray.GetAt(parentIdx) & MSG_FOLDER_FLAG_ELIDED)) { // The new parent may have expanded folders in the folder pane between the parent // index and the position of the new child. Therefore, we need to adjust [the index // of the new child relative to its parent] into [an index into the folder pane's view]. int32 expansionOffset = 0; MSG_FolderArray visibleSubFolders; parent->GetVisibleSubFolders(visibleSubFolders); for (int i = 0; i < newFolderIdx; i++) { MSG_FolderInfo *folder = visibleSubFolders.GetAt(i); uint32 flags = folder->GetFlags(); // ### dmb - should this use the flag array? if ((flags & MSG_FOLDER_FLAG_DIRECTORY) && !(m_flagsArray.GetAt(parentIdx + i + expansionOffset + 1) & MSG_FOLDER_FLAG_ELIDED)) { MSG_FolderArray array; GetExpansionArray (folder, array); expansionOffset += array.GetSize(); } } expansionOffset += newFolderIdx; // Now that we know where the new folder goes in the folder pane, tell // the FE to redraw at that index if (MSG_VIEWINDEXNONE == GetFolderIndex (folders.GetAt(0))) { MSG_ViewIndex newIdx = expansionOffset; if (parentIdx != MSG_VIEWINDEXNONE) newIdx += parentIdx + 1; StartingUpdate (MSG_NotifyInsertOrDelete, newIdx, folders.GetSize()); InsertFlagsArrayAt(newIdx, folders); m_folderView.InsertAt (newIdx, &folders); EndingUpdate (MSG_NotifyInsertOrDelete, newIdx, folders.GetSize()); } } else if (parentIdx != MSG_VIEWINDEXNONE) { int32 unused; ToggleExpansion(parentIdx, &unused); } else if (parentIdx == MSG_VIEWINDEXNONE) { // The new parent may have expanded folders in the folder pane between the parent // index and the position of the new child. Therefore, we need to adjust [the index // of the new child relative to its parent] into [an index into the folder pane's view]. int32 expansionOffset = 0; MSG_FolderArray visibleSubFolders; parent->GetVisibleSubFolders(visibleSubFolders); for (int i = 0; i < newFolderIdx; i++) { MSG_FolderInfo *folder = visibleSubFolders.GetAt(i); uint32 flags = folder->GetFlags(); // ### dmb - should this use the flag array? if ((flags & MSG_FOLDER_FLAG_DIRECTORY) && !(m_flagsArray.GetAt(parentIdx + i + expansionOffset + 1) & MSG_FOLDER_FLAG_ELIDED)) { MSG_FolderArray array; GetExpansionArray (folder, array); expansionOffset += array.GetSize(); } } expansionOffset += newFolderIdx; // this case is for when the root object is the parent... StartingUpdate (MSG_NotifyInsertOrDelete, expansionOffset, folders.GetSize()); InsertFlagsArrayAt(expansionOffset, folders); m_folderView.InsertAt (expansionOffset, &folders); EndingUpdate (MSG_NotifyInsertOrDelete, expansionOffset, folders.GetSize()); // add all the expanded children for (int index = 0; index < folders.GetSize(); index++) { MSG_FolderInfo *folder = folders.GetAt(index); if (!(folder->GetFlags() & MSG_FOLDER_FLAG_ELIDED)) { MSG_FolderArray array; GetExpansionArray (folder, array); if (array.GetSize() != 0) { StartingUpdate (MSG_NotifyInsertOrDelete, ++expansionOffset, array.GetSize()); InsertFlagsArrayAt(expansionOffset, array); m_folderView.InsertAt (expansionOffset, &array); EndingUpdate (MSG_NotifyInsertOrDelete, expansionOffset, array.GetSize()); expansionOffset += folders.GetSize(); } } } } } void MSG_FolderPane::SetFlagForFolder (MSG_FolderInfo *folder, uint32 which) { MSG_ViewIndex viewIndex = GetFolderIndex (folder); if (viewIndex != MSG_VIEWINDEXNONE) { m_flagsArray.SetAt(viewIndex, m_flagsArray.GetAt(viewIndex) | which); } } void MSG_FolderPane::ClearFlagForFolder(MSG_FolderInfo *folder, uint32 flag) { MSG_ViewIndex viewIndex = GetFolderIndex (folder); if (viewIndex != MSG_VIEWINDEXNONE) { m_flagsArray.SetAt(viewIndex, m_flagsArray.GetAt(viewIndex) & ~flag); } } void MSG_FolderPane::AddFolderToView (MSG_FolderInfo *folder, MSG_FolderInfo *parent, int32 newPos) { // Just a little convenience routine since not all folder operations have an array MSG_FolderArray array; array.Add(folder); AddFoldersToView (array, parent, newPos); } MsgERR MSG_FolderPane::NewFolderWithUI (MSG_ViewIndex *indices, int32 numIndices) { MsgERR status = 0; XP_ASSERT (numIndices == 1); MSG_FolderInfo *folder = GetFolderInfo (indices[0]); if (folder && (folder->GetFolderPrefFlags() & MSG_FOLDER_PREF_IMAPNOINFERIORS)) { FE_Alert(GetContext(), XP_GetString(MK_MSG_PARENT_TARGET_NO_INFERIORS)); // leave status == 0, since we already annoyed the user } else if (folder) { char *name = GetValidNewFolderName (folder, MK_MSG_NEW_FOLDER_CAPTION, folder); // If the user didn't click cancel, (name non-null) create the folder if (name) { status = GetMaster()->CreateMailFolder (this, folder, name); XP_FREE (name); } } return status; } // remove the folders from the visible folder pane. leave disk storage intact MsgERR MSG_FolderPane::RemoveFolderFromView(MSG_FolderInfo *doomedFolder) { MSG_FolderArray children; GetExpansionArray (doomedFolder, children); MSG_ViewIndex doomedIndex = GetFolderIndex(doomedFolder); MSG_FolderInfo *parentOfDoomed = m_master->GetFolderTree()->FindParentOf (doomedFolder); return RemoveIndexedFolderFromView(doomedIndex, children, (doomedIndex == MSG_VIEWINDEXNONE) ? 0 : m_flagsArray.GetAt(doomedIndex), parentOfDoomed); } // remove the indexed folders from the visible folder pane. MsgERR MSG_FolderPane::RemoveIndexedFolderFromView(MSG_ViewIndex doomedIndex, MSG_FolderArray &children, uint32 doomedFlags, MSG_FolderInfo *parentOfDoomed) { if (MSG_VIEWINDEXNONE != doomedIndex) { // total number of changing lines is 'folder' plus any visible children // since we're deleting, the count is negative int32 countVector = -1; // default for collapsed if (!(doomedFlags & MSG_FOLDER_FLAG_ELIDED)) countVector = -(children.GetSize() + 1); StartingUpdate (MSG_NotifyInsertOrDelete, doomedIndex, countVector); // remove children, if any and parent expanded, from view if (!(doomedFlags & MSG_FOLDER_FLAG_ELIDED)) { m_folderView.RemoveAt (doomedIndex + 1, &children); m_flagsArray.RemoveAt(doomedIndex + 1, children.GetSize()); } // remove 'folder' from view m_folderView.RemoveAt (doomedIndex); m_flagsArray.RemoveAt(doomedIndex); EndingUpdate (MSG_NotifyInsertOrDelete, doomedIndex, countVector); } return 0; } // delete the folders and associated disk storage, calls RemoveFolderFromView MsgERR MSG_FolderPane::DeleteFolder (MSG_FolderInfoMail *folder, XP_Bool performPreflight) { MsgERR status = 0; if (performPreflight) // preflight is not performed to delete non-existant imap folders. status = PreflightDeleteFolder (folder, TRUE /*getUserConfirmation*/); if (status != 0) return 0; // ###phil not nice to drop this error, but we don't want another alert box. if (folder->GetType() == FOLDER_MAIL) { MSG_FolderInfo *tree = m_master->GetFolderTree(); XP_ASSERT (tree); if (tree) { // I'm leaving the FindParent split out in case we put in parent pointers, // which would make this an O(1) operation MSG_FolderInfo *parent = tree->FindParentOf (folder); status = parent->PropagateDelete ((MSG_FolderInfo**) &folder); } } else if (folder->GetType() == FOLDER_IMAPMAIL) { char *deleteMailboxURL = CreateImapMailboxDeleteUrl(((MSG_IMAPFolderInfoMail *)folder)->GetHostName(), ((MSG_IMAPFolderInfoMail *)folder)->GetOnlineName(), ((MSG_IMAPFolderInfoMail *)folder)->GetOnlineHierarchySeparator()); if (deleteMailboxURL) { MSG_UrlQueue *q = MSG_UrlQueue::AddUrlToPane(deleteMailboxURL, NULL, this, NET_SUPER_RELOAD); XP_ASSERT(q); if (q) q->AddInterruptCallback (SafeToSelectInterruptFunction); XP_FREE(deleteMailboxURL); } else status = MK_OUT_OF_MEMORY; } else { XP_ASSERT(FALSE); // should have been caught by command status status = eUNKNOWN; } return status; } MsgERR MSG_FolderPane::DeleteVirtualNewsgroup (MSG_FolderInfo *folder) { MSG_FolderInfoNews *virtualGroup = folder->GetNewsFolderInfo(); XP_ASSERT(virtualGroup); XP_ASSERT(virtualGroup->GetFlags() & MSG_FOLDER_FLAG_PROFILE_GROUP); // Can't use BuildUrl because a group name in the URL will open a new window char *profileUrl = PR_smprintf("%s/dummy?profile/PROFILE DEL %s", virtualGroup->GetHost()->GetURLBase(), virtualGroup->GetName()); if (profileUrl) { URL_Struct* urlStruct = NET_CreateURLStruct (profileUrl, NET_DONT_RELOAD); if (urlStruct) GetURL (urlStruct, FALSE); XP_FREE(profileUrl); } return eSUCCESS; } void MSG_FolderPane::OnFolderDeleted(MSG_FolderInfo *folder) { // Here we catch the broadcast message that a folder has been deleted. // In the context of the folder pane, this means we remove the folder // from the folder view, since that view element has a stale pointer RemoveFolderFromView (folder); // Call our parent in case they need to respond to this notification // // I think it makes sense to send the folderDeleted notification even if // the folder isn't in view. This is different from a list notification MSG_LinedPane::OnFolderDeleted (folder); } MsgERR MSG_FolderPane::TrashFolders (const MSG_ViewIndex *indices, int32 numIndices, XP_Bool noTrash /*=FALSE*/) { MSG_FolderArray srcFolders; MsgERR status = MakeMoveFoldersArray (indices, numIndices, &srcFolders); if (eSUCCESS == status) status = TrashFolders (srcFolders, noTrash); return status; } MsgERR MSG_FolderPane::TrashFolders (MSG_FolderArray &srcFolders, XP_Bool noTrash /*=FALSE*/) { MSG_FolderInfo *folder = NULL; MsgERR status = -1; // assume error, because there are so many error paths. XP_Bool userCancelled = FALSE; // If the prefs say not to confirm, pretend the user already has confirmed XP_Bool trashFoldersConfirmed = !(GetPrefs()->GetConfirmMoveFoldersToTrash()); for (int i = 0; (i < srcFolders.GetSize()) && (!userCancelled); i++) { folder = srcFolders.GetAt(i); XP_ASSERT (folder); int32 flags = folder->GetFlags(); if ((flags & MSG_FOLDER_FLAG_NEWS_HOST) || folder->GetType() == FOLDER_IMAPSERVERCONTAINER) { MSG_Host* host; int confirmStrID; if (flags & MSG_FOLDER_FLAG_NEWS_HOST) { host = ((MSG_NewsFolderInfoContainer*) folder)->GetHost(); confirmStrID = MK_MSG_REMOVE_HOST_CONFIRM; } else { host = ((MSG_IMAPFolderInfoContainer*) folder)->GetIMAPHost(); confirmStrID = MK_MSG_REMOVE_IMAP_HOST_CONFIRM; } char* tmp = PR_smprintf(XP_GetString(confirmStrID), host->getFullUIName()); if (tmp) { XP_Bool doit = FE_Confirm(GetContext(), tmp); XP_FREE(tmp); // I don't think we need to preflight this - we need to just do it. if (doit /* && PreflightDeleteFolder(folder) == 0 */) { status = host->RemoveHost(); #if !defined(XP_WIN32) && !defined(XP_WIN16) //bug# 112472 RedisplayAll(); #endif } } break; // Only allow one newshost to be deleted at // a time (since it is a background operation). } if (folder->GetType() != FOLDER_MAIL && folder->GetType() != FOLDER_IMAPMAIL && !folder->IsNews()) { // Shouldn't have been able to get here, because the menu item should have been disabled. Right? XP_ASSERT(0); FE_Alert (GetContext(), XP_GetString(MK_MSG_CAN_ONLY_DELETE_MAIL_FOLDERS)); continue; } if (folder->IsNews()) { XP_Bool isVirtualGroup = folder->TestFlag(MSG_FOLDER_FLAG_PROFILE_GROUP); char *vgPrompt = ""; if (isVirtualGroup) vgPrompt = PR_smprintf (XP_GetString(MK_MSG_UNSUBSCRIBE_PROFILE_GROUP), folder->GetName()); char *unsubPrompt = PR_smprintf (XP_GetString (MK_MSG_UNSUBSCRIBE_GROUP), folder->GetPrettiestName()); if (vgPrompt && unsubPrompt) { char *wholePrompt = PR_smprintf ("%s%s",vgPrompt, unsubPrompt); if (wholePrompt) { if (!FE_Confirm (GetContext(), wholePrompt)) { XP_FREE(wholePrompt); if (isVirtualGroup) XP_FREE(vgPrompt); XP_FREE(unsubPrompt); continue; } else if (isVirtualGroup) status = DeleteVirtualNewsgroup (folder); XP_FREE(wholePrompt); } else continue; // out of memory if (isVirtualGroup) XP_FREE(vgPrompt); XP_FREE(unsubPrompt); } else continue; // out of memory } MSG_FolderInfoMail *mailFolder = folder->GetMailFolderInfo(); if ((mailFolder) && (folder->GetType() == FOLDER_MAIL)) { // Find the Trash folder MSG_FolderInfoMail *tree = m_master->GetLocalMailFolderTree(); XP_ASSERT(tree); MSG_FolderInfo *trashFolder = NULL; tree->GetFoldersWithFlag (MSG_FOLDER_FLAG_TRASH, &trashFolder, 1); // If the folder already lives in the Trash, really delete it, // otherwise move it to the Trash folder. if (trashFolder != NULL) { if (noTrash || trashFolder->IsParentOf(mailFolder)) status = DeleteFolder (mailFolder); else { // first see if the trash has a folder with the same name XP_Bool needUnique = trashFolder->ContainsChildNamed(mailFolder->GetName()); const char *uniqueName = NULL; char *oldName = XP_STRDUP(mailFolder->GetName()); status = 0; if (needUnique) { // if so, first move the folder to a unique name MSG_FolderInfo *parentOfCurrent = m_master->GetFolderTree()->FindParentOf (mailFolder); uniqueName = trashFolder->GenerateUniqueSubfolderName(mailFolder->GetName(), parentOfCurrent); if (uniqueName) status = RenameFolder(mailFolder,uniqueName); if (!uniqueName || ((int32) status < 0)) { // if the unique rename fails, simply alert the user and don't trash it char *templateString = XP_GetString(MK_MSG_UNIQUE_TRASH_RENAME_FAILED); char *alertString = NULL; if (templateString) alertString = PR_smprintf(templateString,mailFolder->GetName()); if (alertString) FE_Alert(GetContext(),alertString); FREEIF(alertString); } } // move it to the trash. if ((int32) status >= 0) { MSG_FolderArray tempArray; tempArray.Add (folder); if (!trashFoldersConfirmed) { if (FE_Confirm (GetContext(), XP_GetString(MK_MSG_CONFIRM_MOVE_FOLDER_TO_TRASH))) status = MoveFolders (tempArray, trashFolder); else userCancelled = TRUE; trashFoldersConfirmed = TRUE; } else status = MoveFolders (tempArray, trashFolder); } if (needUnique && (status == 0)) { char *templateString = XP_GetString(MK_MSG_NEEDED_UNIQUE_TRASH_NAME); char *alertString = NULL; if (templateString) alertString = PR_smprintf(templateString,oldName,uniqueName); if (alertString) FE_Alert(GetContext(),alertString); FREEIF(alertString); } FREEIF(oldName); } } else FE_Alert(GetContext(),"Could not locate Trash folder!"); } else if ((mailFolder) && (folder->GetType() == FOLDER_IMAPMAIL) && !folder->DeleteIsMoveToTrash()) { status = DeleteFolder (mailFolder); } else if ((mailFolder) && (folder->GetType() == FOLDER_IMAPMAIL)) { MSG_IMAPFolderInfoMail *imapFolder = folder->GetIMAPFolderInfoMail(); // Find the IMAP Trash folder MSG_FolderInfo *trashFolder = MSG_GetTrashFolderForHost(imapFolder->GetIMAPHost()); // If the folder already lives in the Trash, really delete it, // otherwise move it to the Trash folder. if (trashFolder != NULL) { if (noTrash || trashFolder->IsParentOf(mailFolder)) status = DeleteFolder (mailFolder); else if (trashFolder->GetFolderPrefFlags() & MSG_FOLDER_PREF_IMAPNOINFERIORS) { if (FE_Confirm(GetContext(), XP_GetString(MK_MSG_TRASH_NO_INFERIORS)) ) status = DeleteFolder (mailFolder); } else { // first see if the trash has a folder with the same name XP_Bool needUnique = trashFolder->ContainsChildNamed(mailFolder->GetName()); if (needUnique) { // if the unique rename fails, simply alert the user and don't trash it char *templateString = XP_GetString(MK_MSG_UNIQUE_TRASH_RENAME_FAILED); char *alertString = NULL; if (templateString) alertString = PR_smprintf(templateString,mailFolder->GetName()); if (alertString) FE_Alert(GetContext(),alertString); FREEIF(alertString); } else { // With the three pane UI, it's sometimes hard to tell which pane has focus, and // sometimes users can delete a folder when they think they're deleting a message. MSG_FolderArray tempArray; tempArray.Add (folder); if (!trashFoldersConfirmed) { if (FE_Confirm (GetContext(), XP_GetString(MK_MSG_CONFIRM_MOVE_FOLDER_TO_TRASH))) status = MoveFolders (tempArray, trashFolder); else userCancelled = TRUE; trashFoldersConfirmed = TRUE; // only ask once } else status = MoveFolders (tempArray, trashFolder); } } } else FE_Alert(GetContext(),"Could not locate Trash folder!"); } else if (folder->IsNews()) { MSG_FolderInfoNews *newsFolder = folder->GetNewsFolderInfo(); if (newsFolder) { newsFolder->GetHost()->RemoveGroup (newsFolder); status = 0; // lovely remove group is void } } else { XP_ASSERT(FALSE); continue; } } // Must add this URL after all the IMAP delete URLs (if any) to tell the FE when // it's safe to select a folder (which runs a URL which might interrupt the delete) MSG_UrlQueue::AddUrlToPane ("mailbox:?null", SafeToSelectExitFunction, this); return status; } /*static*/ void MSG_FolderPane::RuleTrackCB (void *cookie, XPPtrArray& rules, XPDWordArray &actions, XP_Bool isDelete) { MWContext *context = ((MSG_FolderPane*) cookie)->GetContext(); MSG_RuleTrackAction action = dontChange; // Ask once what the user wants to do. Asking for every rule is pretty annoying if (!isDelete) { if (FE_Confirm (context, XP_GetString(MK_MSG_TRACK_FOLDER_MOVE))) action = trackChange; else action = dontChange; } else { if (FE_Confirm (context, XP_GetString(MK_MSG_TRACK_FOLDER_DEL))) action = disableRule; else action = dontChange; } // Set the user's choice into the array to apply to each affected rule for (int i = 0; i < rules.GetSize(); i++) actions.SetAtGrow (i, action); } MsgERR MSG_FolderPane::MakeMoveFoldersArray (const MSG_ViewIndex *indices, int32 numIndices, MSG_FolderArray *outArray) { // When we move a group of folders, constrain the list of source folders // such that we don't try to move a folder whose parent is also in the // list. This allows us to preserve hierarchy as much as possible, and also // to reduce the number of errors we need to handle (e.g. uh-oh, this // folder's gone; I wonder if I moved it already) // First, put 'em all in a temporary list int i; MSG_FolderArray tmpArray; for (i = 0; i < numIndices; i++) tmpArray.Add(GetFolderInfo(indices[i])); // Now sort the list to get the shallowest folders first. Doing this // assures that one pass through the folders will catch all the // parent-child relationships. tmpArray.QuickSort (MSG_FolderInfo::CompareFolderDepth); // Add elements to the array if they aren't a child of another array element for (i = 0; i < tmpArray.GetSize(); i++) { MSG_FolderInfo *folder = tmpArray.GetAt(i); XP_Bool alreadyHaveParent = FALSE; for (int j = 0; j < outArray->GetSize(); j++) { alreadyHaveParent = outArray->GetAt(j)->IsParentOf(folder); if (alreadyHaveParent) break; } if (!alreadyHaveParent) outArray->Add(folder); } return eSUCCESS; } int MSG_FolderPane::PreflightMoveFolder (MSG_FolderInfo *srcFolder, MSG_FolderInfo *srcParent, MSG_FolderInfo *destFolder) { // can't move categories around. // if (srcFolder->IsCategory() && srcFolder->GetType() != FOLDER_CATEGORYCONTAINER) // return -1; if (srcParent == destFolder || srcFolder == destFolder) { // Moving folder into same hierarchy level it's already in -- do nothing. return -1; } // can't move catgories, but can move category containers if (srcFolder->GetNewsFolderInfo() && srcFolder->GetNewsFolderInfo()->IsCategory() && srcFolder->GetType() != FOLDER_CATEGORYCONTAINER) { return -1; } // can't drop onto categories. if (destFolder->GetNewsFolderInfo() && destFolder->GetNewsFolderInfo()->IsCategory()) return -1; if (srcFolder->GetFlags() & MSG_FOLDER_FLAG_NEWS_HOST) { if (destFolder->TestFlag (MSG_FOLDER_FLAG_TRASH)) return 0; // dragging news host to trash ==> remove host return -1; // can't move news hosts currently } else if (srcFolder->GetType() == FOLDER_IMAPSERVERCONTAINER) { // we were not catching this because the srcFolder and destFolder were of different types return (destFolder->GetType() == FOLDER_IMAPSERVERCONTAINER) ? 0 : -1; } else if (destFolder->GetType() != srcFolder->GetType()) { // Allow unsubscribe via move to Trash if (srcFolder->IsNews() && destFolder->GetFlags() & MSG_FOLDER_FLAG_TRASH) return 0; if (srcFolder->IsMail()) { if (destFolder->IsNews() || destFolder->GetType() == FOLDER_CONTAINERONLY) return MK_MSG_NO_MAIL_TO_NEWS; else return 0; } else if (destFolder->IsMail()) { if (srcFolder->IsNews() || srcFolder->GetType() == FOLDER_CONTAINERONLY) return MK_MSG_NO_NEWS_TO_MAIL; else return 0; } else if (destFolder->GetType() == FOLDER_IMAPSERVERCONTAINER) { if (srcFolder->IsNews() || srcFolder->GetType() == FOLDER_CONTAINERONLY) return MK_MSG_NO_NEWS_TO_MAIL; } } else if (destFolder->IsNews()) { if (srcFolder->IsNews()) { MSG_FolderInfoNews *srcGroup = srcFolder->GetNewsFolderInfo(); MSG_FolderInfoNews *destGroup = destFolder->GetNewsFolderInfo(); // Newsgroups can only be reordered if they live on the same host if (srcGroup->GetHost() != destGroup->GetHost()) return -1; } else return 0; } // Don't allow a folder to be moved into one of its own children if (srcFolder->IsParentOf (destFolder)) return MK_MSG_CANT_MOVE_FOLDER_TO_CHILD; // don't allow a destFolder where the MSG_FOLDER_PREF_IMAPNOINFERIORS pref flag is set if (destFolder->GetFolderPrefFlags() & MSG_FOLDER_PREF_IMAPNOINFERIORS) return MK_MSG_MOVE_TARGET_NO_INFERIORS; // Don't allow the user to move their imap inbox uint32 flags = srcFolder->GetFlags(); if ( (flags & MSG_FOLDER_FLAG_INBOX) && (flags & MSG_FOLDER_FLAG_IMAPBOX) ) return MK_MSG_CANT_MOVE_INBOX; return 0; } int MSG_FolderPane::PreflightMoveFolderWithUI (MSG_FolderInfo *srcFolder, MSG_FolderInfo *srcParent, MSG_FolderInfo *destFolder) { int err = PreflightMoveFolder (srcFolder, srcParent, destFolder); if (0 == err) { // Since these folders are special based on their name and location, // changing the location makes them not special. Make sure the user // really wants to do this. if (srcFolder->GetDepth() == 2) { int32 flags = srcFolder->GetFlags(); if (flags & MSG_FOLDER_FLAG_INBOX || flags & MSG_FOLDER_FLAG_DRAFTS || flags & MSG_FOLDER_FLAG_QUEUE || flags & MSG_FOLDER_FLAG_TRASH || flags & MSG_FOLDER_FLAG_TEMPLATES) { char *prompt = PR_smprintf (XP_GetString(MK_MSG_CONFIRM_MOVE_MAGIC_FOLDER), srcFolder->GetName(), srcFolder->GetName()); if (prompt) { XP_Bool allowMove = FE_Confirm(GetContext(), prompt); XP_FREE(prompt); if (!allowMove) err = -1; } else err = MK_OUT_OF_MEMORY; } } } else { if (err != -1) // -1 is a generic failure -- no specific error string FE_Alert (GetContext(), XP_GetString (err)); } return err; } XP_Bool MSG_FolderPane::OneFolderInArrayIsIMAP(MSG_FolderArray &theFolders) { for (int32 i = 0; i < theFolders.GetSize(); i++) { MSG_FolderInfo *srcFolder = theFolders.GetAt(i); if (srcFolder->GetType() == FOLDER_IMAPMAIL) return TRUE; } return FALSE; } MSG_DragEffect MSG_FolderPane::DragFoldersStatus ( const MSG_ViewIndex *indices, int32 numIndices, MSG_FolderInfo *destFolder, MSG_DragEffect request) { if ((request & MSG_Require_Move) != MSG_Require_Move) return MSG_Drag_Not_Allowed; // IMAP ACLs - check destination folder MSG_IMAPFolderInfoMail *imapFolder = destFolder->GetIMAPFolderInfoMail(); if (imapFolder) { if (!imapFolder->GetCanDropFolderIntoThisFolder()) return MSG_Drag_Not_Allowed; } for (int i = 0; i < numIndices; i++) { MSG_FolderInfo *src = GetFolderInfo (indices[i]); MSG_FolderInfo *srcParent = GetMaster()->GetFolderTree()->FindParentOf (src); if (0 != PreflightMoveFolder (src, srcParent, destFolder)) return MSG_Drag_Not_Allowed; } return MSG_Require_Move; } MsgERR MSG_FolderPane::MoveFolders (const MSG_ViewIndex *indices, int32 numIndices, MSG_FolderInfo *dest, XP_Bool needExitFunc /*= FALSE*/) { MSG_FolderArray srcFolders; MsgERR status = MakeMoveFoldersArray (indices, numIndices, &srcFolders); if (eSUCCESS == status) status = MoveFolders (srcFolders, dest, needExitFunc); return status; } MsgERR MSG_FolderPane::MoveFolders (MSG_FolderArray &srcFolders, MSG_FolderInfo *destFolder, XP_Bool needExitFunc /* = FALSE*/) { MsgERR err = 0; for (int32 i = 0; i < srcFolders.GetSize(); i++) { MSG_FolderInfo *srcFolder = srcFolders.GetAt(i); MSG_FolderInfo *srcParent = GetMaster()->GetFolderTree()->FindParentOf (srcFolder); if (0 == PreflightMoveFolderWithUI (srcFolder, srcParent, destFolder)) { // Deleting a newsgroup and dragging it to the trash are synonymous if ((srcFolder->IsNews() || srcFolder->GetType() == FOLDER_CONTAINERONLY) && destFolder->GetFlags() & MSG_FOLDER_FLAG_TRASH) { MSG_ViewIndex srcIdx = m_folderView.FindIndex (0, srcFolder); if (srcIdx != MSG_VIEWINDEXNONE) err = TrashFolders (&srcIdx, 1); } else { // If we're moving into an IMAP folder, start the URL here if ( ((destFolder->GetType() == FOLDER_IMAPMAIL) || (destFolder == GetMaster()->GetImapMailFolderTree())) && (srcFolder->GetType() == FOLDER_IMAPMAIL) ) { const char *destinationName = ""; // covers promote to root char destinationHierarchySeparator = 0; // covers promote to root if (destFolder->GetType() == FOLDER_IMAPMAIL) { destinationName = ((MSG_IMAPFolderInfoMail *)destFolder)->GetOnlineName(); destinationHierarchySeparator = ((MSG_IMAPFolderInfoMail *)destFolder)->GetOnlineHierarchySeparator(); } // the rename on the server has to happen first imap.h char *renameMailboxURL = CreateImapMailboxMoveFolderHierarchyUrl (((MSG_IMAPFolderInfoMail *) srcFolder)->GetHostName(), ((MSG_IMAPFolderInfoMail *) srcFolder)->GetOnlineName(), ((MSG_IMAPFolderInfoMail *) srcFolder)->GetOnlineHierarchySeparator(), destinationName,destinationHierarchySeparator); if (renameMailboxURL) { MSG_UrlQueue *q = MSG_UrlQueue::AddUrlToPane(renameMailboxURL, NULL, this, NET_SUPER_RELOAD); XP_ASSERT(q); if (q) q->AddInterruptCallback (SafeToSelectInterruptFunction); XP_FREE(renameMailboxURL); } } else if (srcFolder->GetType() != destFolder->GetType() && srcFolder->IsMail() && (destFolder->IsMail() || destFolder->GetType() == FOLDER_IMAPSERVERCONTAINER)) { // this must be a move on/offline of a mail folder m_RemoteCopyState = new MSG_RemoteFolderCopyState(srcFolder, destFolder, this); if (m_RemoteCopyState) { err = m_RemoteCopyState->DoNextAction(); if (err) { delete m_RemoteCopyState; m_RemoteCopyState = NULL; } } } else { // moving local folders around err = PerformLegalFolderMove(srcFolder, destFolder); // stop on error? } } } } if (needExitFunc) { // Must add this URL after all the IMAP delete URLs (if any) to tell the FE when // it's safe to select a folder (which runs a URL which might interrupt the delete) MSG_UrlQueue::AddUrlToPane ("mailbox:?null", SafeToSelectExitFunction, this); } return err; } MsgERR MSG_FolderPane::PerformLegalFolderMove(MSG_FolderInfo *srcFolder, MSG_FolderInfo *newParent) { MsgERR err = 0; int32 movedPos = 0; MSG_FolderInfo *originalParent = GetMaster()->GetFolderTree()->FindParentOf (srcFolder); XP_ASSERT(originalParent); // Rule changes get written when 'tracker' gets destructed MSG_RuleTracker tracker (GetMaster(), RuleTrackCB, this); tracker.WatchMoveFolders(&srcFolder, 1); if (originalParent) { originalParent->RemoveSubFolder (srcFolder); // If the srcFolder is an IMAP folder then reset its stored online name // This has to happen after MSG_RuleTracker decides if srcFolder was // a filter target and before MSG_RuleTracker gets destructed and writes // the new filter move destination. // Special case newParent being the imap container if (srcFolder->GetType() == FOLDER_IMAPMAIL) { MSG_IMAPFolderInfoMail *srcImapFolder = (MSG_IMAPFolderInfoMail *) srcFolder; MSG_IMAPHost *srcHost = srcImapFolder->GetIMAPHost(); XP_ASSERT(srcHost); if (newParent->GetType() == FOLDER_IMAPMAIL) srcImapFolder->ParentRenamed( ((MSG_IMAPFolderInfoMail *) newParent)->GetOnlineName()); else if (newParent->GetType() == FOLDER_IMAPSERVERCONTAINER) srcImapFolder->ParentRenamed(srcHost->GetRootNamespacePrefix()); else XP_ASSERT(FALSE); // should be impossible } // Move items around in the folder pane to reflect the new ancestry // since either the source or the destination might be expanded. // MSG_FolderPane *eachPane; // for (eachPane = (MSG_FolderPane *) m_master->FindFirstPaneOfType(MSG_FOLDERPANE); eachPane; // eachPane = (MSG_FolderPane *) m_master->FindNextPaneOfType(eachPane->GetNextPane(), MSG_FOLDERPANE)) // { // eachPane->UpdateFolderPaneAfterFolderMove(srcFolder, originalParent, newParent, movedPos); // } // Begin Undo hook // **** use StartBatch, EndBatch when moving multiple folders enabled UndoManager *undoManager = GetUndoManager(); if (undoManager && undoManager->GetState () == UndoIdle) { MoveFolderUndoAction *undoAction = new MoveFolderUndoAction(this, originalParent, srcFolder, newParent); if (undoAction) undoManager->AddUndoAction(undoAction); } // End Undo hook } if (err == eSUCCESS) { // Mail folder and summary get moved here, unless moving imap server m_master->BroadcastFolderDeleted(srcFolder); if (srcFolder->GetType() != FOLDER_IMAPSERVERCONTAINER) { err = newParent->Adopt (srcFolder, &movedPos); } else { movedPos = originalParent->GetSubFolders()->FindIndex (0, newParent); if (newParent->GetIMAPFolderInfoContainer()) { MSG_IMAPHostTable *imapHostTable = m_master->GetIMAPHostTable(); if (imapHostTable) imapHostTable->ReorderIMAPHost(srcFolder->GetIMAPFolderInfoContainer()->GetIMAPHost(), newParent->GetIMAPFolderInfoContainer()->GetIMAPHost()); // put the src folder after the drop target. originalParent->GetSubFolders()->InsertAt(movedPos + 1, srcFolder); } else XP_ASSERT(FALSE); } m_master->BroadcastFolderAdded(srcFolder, NULL); } else { char *errorString = XP_GetString (err); if (errorString && *errorString) FE_Alert (GetContext(), errorString); } return err; } // Undo helper function MsgERR MSG_FolderPane::UndoMoveFolder (MSG_FolderInfo *srcParent, MSG_FolderInfo *srcFolder, MSG_FolderInfo *destFolder) { MSG_ViewIndex viewIndex; MsgERR err = eUNKNOWN; int32 movedPos = 0; { // Don't remove the enclosing bracket.... MSG_RuleTracker tracker (GetMaster(), RuleTrackCB, this); tracker.WatchMoveFolders(&srcFolder, 1); // Mail folder and summary get moved here err = destFolder->Adopt (srcFolder, &movedPos); // Rule changes get written when 'tracker' gets destructed } if (eSUCCESS == err) { viewIndex = GetFolderIndex (srcFolder); // Move items around in the folder pane to reflect the new ancestry // since either the source or the destination might be expanded. MSG_FolderArray itemsToMove; GetExpansionArray(srcFolder, itemsToMove); // add the expanded children itemsToMove.InsertAt(0, srcFolder); // we will want to move the item itself. // Remove srcFolder and any open children of srcFolder int32 countVector = 0 - itemsToMove.GetSize(); if (MSG_VIEWINDEXNONE != viewIndex) { StartingUpdate (MSG_NotifyInsertOrDelete, viewIndex, countVector); m_folderView.RemoveAt (viewIndex, &itemsToMove); m_flagsArray.RemoveAt(viewIndex, itemsToMove.GetSize()); } srcParent->RemoveSubFolder (srcFolder); if (MSG_VIEWINDEXNONE != viewIndex) EndingUpdate (MSG_NotifyInsertOrDelete, viewIndex, countVector); // If we've just moved out the only child of this parent, tell the FE that // the state of the parent has changed so they redraw the expand/collapse widget if (srcParent && srcParent->GetNumSubFolders() == 0) { int srcParentIdx = m_folderView.FindIndex (0, srcParent); if (srcParentIdx != -1) { StartingUpdate (MSG_NotifyChanged, srcParentIdx, 1); EndingUpdate (MSG_NotifyChanged, srcParentIdx, 1); } } // Add srcFolder and its children to the new parent if the parent is expanded uint32 destFlags = destFolder->GetFlags(); int32 destFolderIdx = m_folderView.FindIndex (0, destFolder); XP_Bool destElided = m_flagsArray.GetAt(destFolderIdx) & MSG_FOLDER_FLAG_ELIDED; if ((destFlags & MSG_FOLDER_FLAG_DIRECTORY) && !destElided) { XP_ASSERT (destFolderIdx >= 0); AddFoldersToView (itemsToMove, destFolder, movedPos); } // If we've just added the first child of the destination, tell the FE to // redraw the dest folder line to get a new expand/collapse widget if (destFolder->GetNumSubFolders() == 1 && destFolderIdx != -1) { StartingUpdate (MSG_NotifyChanged, destFolderIdx, 1); EndingUpdate (MSG_NotifyChanged, destFolderIdx, 1); } } return err; } MsgERR MSG_FolderPane::RenameOnlineFolder (MSG_FolderInfo *folder, const char *newName) { char *renameMailboxURL = CreateImapMailboxRenameLeafUrl(((MSG_IMAPFolderInfoMail *)folder)->GetHostName(), ((MSG_IMAPFolderInfoMail *)folder)->GetOnlineName(), ((MSG_IMAPFolderInfoMail *)folder)->GetOnlineHierarchySeparator(), newName); // name is assumed to be leafname // and doesn't affect hierarchy if (renameMailboxURL) { MSG_UrlQueue::AddUrlToPane (renameMailboxURL, NULL, this, NET_SUPER_RELOAD); XP_FREE(renameMailboxURL); } return eSUCCESS; } MsgERR MSG_FolderPane::RenameOfflineFolder (MSG_FolderInfo *folder, const char *newName) { // Begin Undo hook UndoManager *undoManager = GetUndoManager(); if (undoManager && undoManager->GetState() == UndoIdle) { UndoAction *undoAction = NULL; if (folder->GetType() == FOLDER_MAIL) { undoAction = new RenameFolderUndoAction(this, folder, folder->GetName(), newName); } else if (folder->GetType() == FOLDER_IMAPMAIL) { undoAction = new IMAPRenameFolderUndoAction(this, folder, folder->GetName(), newName); // name is assumed to be leafname } if (undoAction) undoManager->AddUndoAction (undoAction); } // End Undo hook MsgERR returnErr = eFAILURE; MSG_RuleTracker tracker (GetMaster(), RuleTrackCB, this); tracker.WatchMoveFolders(&folder, 1); // I moved the StartingUpdate/EndingUpdate code here // because I call this function when I am notified that // the imap netlib module has renamed a folder // (i.e. the end of an asynch operation - km returnErr = folder->Rename (newName); if (returnErr == 0) // Force UI to update and for various lists to resort. RedisplayAll(); return returnErr; } char *MSG_FolderPane::GetValidNewFolderName (MSG_FolderInfo *parent, int captionId, MSG_FolderInfo *folder) { // Loop until we get a unique name, or else the user clicks cancel char *name = NULL; XP_Bool validName = FALSE; while (!validName) { name = FE_PromptWithCaption (GetContext(), XP_GetString(captionId), XP_GetString(MK_MSG_ENTER_FOLDERNAME), folder->GetPrettiestName()); if (name) { if (parent->ContainsChildNamed(name)) { FE_Alert (GetContext(), XP_GetString(MK_MSG_FOLDER_ALREADY_EXISTS)); XP_FREE(name); } else validName = TRUE; } else validName = TRUE; } return name; } MsgERR MSG_FolderPane::PreflightRename(MSG_FolderInfo *folder, int depth) { MSG_FolderArray *subFolders = folder->GetSubFolders(); for (int i = 0; i < subFolders->GetSize(); i++) { MsgERR err = PreflightRename (subFolders->GetAt(i), depth + 1); if (err) return err; } if (depth > 0) // only need to check children - we handle the top-level case { // Prevent the user from really deleting any folder which has a thread pane open XPPtrArray panes; GetMaster()->FindPanesReferringToFolder (folder, &panes); if (panes.GetSize() > 0) { char *prompt = PR_smprintf (XP_GetString (MK_MSG_PANES_OPEN_ON_FOLDER), folder->GetName()); if (prompt) { FE_Alert (GetContext(), prompt); XP_FREE(prompt); } return (MsgERR) -1; } } return eSUCCESS; } MsgERR MSG_FolderPane::RenameFolder(MSG_FolderInfo *folder, const char *newName) { MsgERR status = 0; char *name = NULL; XP_Bool mustFreeName = FALSE; XP_ASSERT(folder); if (!folder) return eUNKNOWN; // if this folder has sub-folders, preflight the rename XPPtrArray *subFolders = folder->GetSubFolders(); if (subFolders && subFolders->GetSize() > 0) { MsgERR err = PreflightRename(folder, 0); if (err != eSUCCESS) return err; } // If we didn't get a new name on input, ask for one from the user if (!newName) { name = GetValidNewFolderName (GetMaster()->GetFolderTree()->FindParentOf(folder), MK_MSG_RENAME_FOLDER_CAPTION, folder); mustFreeName = TRUE; } else { name = (char*) newName; MSG_FolderInfo *parent = GetMaster()->GetFolderTree()->FindParentOf(folder); if (parent && parent->ContainsChildNamed(name)) { // we don't want to free it, since we didn't dup it. name = NULL; status = eFAILURE; } else if (XP_FileNameContainsBadChars(name)) { name = NULL; status = eFAILURE; } } if (name && (XP_STRLEN(name) > 0) && XP_STRCMP(folder->GetName(), name)) // don't do anything unless names are different { if (folder->GetType() == FOLDER_MAIL) status = RenameOfflineFolder (folder, name); else if (folder->GetType() == FOLDER_IMAPMAIL) RenameOnlineFolder (folder, name); else { XP_ASSERT(FALSE); // should have been caught by command status status = eUNKNOWN; } if (mustFreeName) XP_FREE(name); } return status; } void MSG_FolderPane::GetExpansionArray(MSG_FolderInfo *folder, MSG_FolderArray &array) { MSG_FolderArray *subFolders = folder->GetSubFolders(); for (int i = 0; i < subFolders->GetSize(); i++) { MSG_FolderInfo *subFolder = subFolders->GetAt(i); if (subFolder->CanBeInFolderPane()) { array.InsertAt(array.GetSize(), subFolder); // now, this is not right, unless we're expanding...We can look for this in // the view. MSG_ViewIndex subFolderIndex = GetFolderIndex(subFolder); int32 flags = (subFolderIndex == MSG_VIEWINDEXNONE) ? subFolder->GetFlags() : m_flagsArray.GetAt(subFolderIndex); if (!(flags & MSG_FOLDER_FLAG_ELIDED)) GetExpansionArray(subFolder, array); } } } void MSG_FolderPane::OnToggleExpansion(MSG_FolderInfo* toggledinfo, MSG_ViewIndex line, int32 countVector) { if (countVector == 0) return; // Can this actually happen? // this is pretty horrible, but somewhere we need to update counts on // expanded folder. if (countVector > 0) { ScanFolder(); MSG_FolderInfoNews* newsinfo = (MSG_FolderInfoNews*) GetFolderInfo(line); if (newsinfo && newsinfo->IsNews()) { // OK, we just opened up a newshost. Go ask that host // for all the latest counts. MSG_NewsHost* host = newsinfo->GetHost(); host->SetEverExpanded(TRUE); host->SetWantNewTotals(TRUE); UpdateNewsCounts(host); } else if (toggledinfo->GetType() == FOLDER_IMAPMAIL) { MSG_IMAPFolderInfoMail *imapInfo = toggledinfo->GetIMAPFolderInfoMail(); if (imapInfo) { XP_Bool usingSubscription = TRUE; MSG_IMAPHost *imapHost = imapInfo->GetIMAPHost(); if (imapHost) { usingSubscription = imapHost->GetIsHostUsingSubscription(); } if (!usingSubscription) { // we need to run a child discovery URL, if we're doing LIST instead of LSUB char *url = CreateImapChildDiscoveryUrl(imapInfo->GetHostName(), imapInfo->GetOnlineName(), imapInfo->GetOnlineHierarchySeparator()); if (url) { URL_Struct *url_s = NET_CreateURLStruct(url, NET_SUPER_RELOAD); if (url_s) { MSG_UrlQueue::AddUrlToPane(url_s, NULL, this); } XP_FREE(url); } } } } else if (toggledinfo->GetType() == FOLDER_IMAPSERVERCONTAINER) { RefreshIMAPHostFolders(toggledinfo, TRUE); } } else { if ((toggledinfo->GetType() == FOLDER_CONTAINERONLY) || (toggledinfo->GetType() == FOLDER_IMAPMAIL) || (toggledinfo->GetType() == FOLDER_IMAPSERVERCONTAINER)) { // OK, we just collapsed a newshost or an imap folder. Stop any background process // we may have had going (which probably was us getting counts // or new newsgroups on that newshost). InterruptContext(FALSE); } } } char *MSG_FolderPane::RefreshIMAPHostFolders(MSG_FolderInfo* hostinfo, XP_Bool runURL) { // Expanding an imap server, update folder list. // First, clear the verified bits so that we can discover // folders going away, also. XPPtrArray *subFolders = hostinfo->GetSubFolders(); for (int i = 0; i < subFolders->GetSize(); i++) { MSG_IMAPFolderInfoMail *childinfo = ((MSG_FolderInfo*)subFolders->GetAt(i))->GetIMAPFolderInfoMail(); if (childinfo) { childinfo->SetHierarchyIsOnlineVerified(FALSE); } } // Now re-discover the folders char * url = CreateImapAllMailboxDiscoveryUrl(hostinfo->GetName()); if (!runURL) return url; if (url) { URL_Struct *url_struct = NET_CreateURLStruct(url, NET_SUPER_RELOAD); if (url_struct) { MSG_UrlQueue::AddUrlToPane(url_struct, NULL, this); } XP_FREE(url); } return 0; } void MSG_FolderPane::RefreshUpdatedIMAPHosts() { for (MSG_ViewIndex currentIndex = 0; currentIndex < (MSG_ViewIndex) GetNumLines(); currentIndex++) { MSG_FolderInfo *finfo = GetFolderInfo(currentIndex); if (finfo && finfo->GetType() == FOLDER_IMAPSERVERCONTAINER) { MSG_IMAPFolderInfoContainer *imapinfo = (MSG_IMAPFolderInfoContainer *)finfo; if (imapinfo->GetHostNeedsFolderUpdate()) { imapinfo->SetHostNeedsFolderUpdate(FALSE); char *url = RefreshIMAPHostFolders(imapinfo, FALSE); if (url) { MSG_UrlQueue::AddUrlToPane(url, NULL, this); XP_FREE(url); } } } } } void MSG_FolderPane::ToggleExpansion(MSG_ViewIndex line, int32* numChanged) { // Build the array of what will change MSG_FolderInfo *folder = GetFolderInfo (line); MSG_FolderArray array; GetExpansionArray(folder, array); // Tell the FE to redraw the expand/collapse widget of the parent folder StartingUpdate (MSG_NotifyChanged, line, 1); uint8 flag = m_flagsArray.GetAt(line); if (flag & MSG_FOLDER_FLAG_ELIDED) flag &= ~MSG_FOLDER_FLAG_ELIDED; else flag |= MSG_FOLDER_FLAG_ELIDED; m_flagsArray.SetAt(line, flag); folder->ToggleFlag (MSG_FOLDER_FLAG_ELIDED); EndingUpdate (MSG_NotifyChanged, line, 1); // Update the folder pane's array of what's visible line++; // all the interesting stuff happens *after* the sel line // Compute how many lines to tell the FE about, and in which direction. // Negative direction if the folder was just collapsed. int32 countVector = array.GetSize(); if (flag & MSG_FOLDER_FLAG_ELIDED) countVector = 0 - countVector; StartingUpdate (MSG_NotifyInsertOrDelete, line, countVector); if (flag & MSG_FOLDER_FLAG_ELIDED) { m_folderView.RemoveAt (line, &array); m_flagsArray.RemoveAt(line, array.GetSize()); } else { m_folderView.InsertAt (line, &array); InsertFlagsArrayAt(line, array); } EndingUpdate (MSG_NotifyInsertOrDelete, line, countVector); OnToggleExpansion(folder, line, countVector); if (numChanged) *numChanged = countVector; } int32 MSG_FolderPane::ExpansionDelta(MSG_ViewIndex line) { // Build the array of what will change MSG_FolderInfo *folder = GetFolderInfo (line); MSG_FolderArray array; GetExpansionArray(folder, array); // If the selected item is collapsed, we will be adding to the view, so // the delta is a positive number. If the sel item is expanded, we will // be shrinking the view, so the expansionDelta should be negative int32 vector = array.GetSize(); return m_flagsArray.GetAt(line) & MSG_FOLDER_FLAG_ELIDED ? vector : -vector; } int32 MSG_FolderPane::GetNumLines() { return m_folderView.GetSize(); } MsgERR MSG_FolderPane::UpdateMessageCounts(MSG_ViewIndex *indices, int32 numIndices) { MSG_NewsHost *host = NULL; for (MSG_ViewIndex index = 0; index < numIndices; index++) { MSG_FolderInfo* folderInfo = GetFolderInfo(indices[index]); if (folderInfo) { if (folderInfo->GetType() == FOLDER_CONTAINERONLY) { MSG_FolderInfoContainer *newsContainer = (MSG_FolderInfoContainer *) folderInfo; host = newsContainer->GetHost(); if (host) { host->SetWantNewTotals(TRUE); break; } } MSG_FolderInfoNews *newsFolder = folderInfo->GetNewsFolderInfo(); if (newsFolder) { newsFolder->SetWantNewTotals(TRUE); host = newsFolder->GetHost(); } // queue up an imap status url to update the counts. Could make this a method on // the imapfolderinfomail, but we'd need to pass in a pane. MSG_IMAPFolderInfoMail *imapFolder = folderInfo->GetIMAPFolderInfoMail(); if (imapFolder) { char *url = CreateIMAPStatusFolderURL(imapFolder->GetHostName(), imapFolder->GetOnlineName(), imapFolder->GetOnlineHierarchySeparator()); if (url) { MSG_UrlQueue::AddUrlToPane(url, NULL, this); XP_FREE(url); } } } } if (host) UpdateNewsCounts(host); return eSUCCESS; } /*static*/ void MSG_FolderPane::SafeToSelectInterruptFunction (MSG_UrlQueue * /*queue*/, URL_Struct *URL_s, int status, MWContext *context) { // This is the interrupt function for the URL queue we use for IMAP move/delete folder URLs // If the user interrupts such a URL, we need to tell the FE it's ok to select the folder // so we don't get caught holding the "not ok to select folder" state forever. SafeToSelectExitFunction (URL_s, status, context); } /*static*/ void MSG_FolderPane::SafeToSelectExitFunction (URL_Struct *URL_s, int /*status*/, MWContext* /*context*/) { XP_ASSERT(URL_s->msg_pane); if (URL_s->msg_pane) { // Tell the FE that we're done, and they can run another URL if they want. // // NB: If you add IMAP code which uses this callback to avoid getting interrupted, make // sure that the local folder code also sends the same notification, so the operation looks // the same to the FE whether it's IMAP or local. FE_PaneChanged (URL_s->msg_pane, FALSE, MSG_PaneNotifySafeToSelectFolder, 0); } } MsgERR MSG_FolderPane::CompressFolder(MSG_ViewIndex *indices, int32 numIndices) { MsgERR status = eSUCCESS; for (int i = 0; i < numIndices; i++) { MSG_FolderInfoMail *folder = NULL; folder = (MSG_FolderInfoMail*) GetFolderInfo (indices[i]); XP_ASSERT (folder); status = CompressOneMailFolder(folder); } return status; } MsgERR MSG_FolderPane::AddNewsGroup() { // ###tw Write me! // This is just a place holder for alpha news... ###tw char *groupURL = FE_Prompt(GetContext(), XP_GetString(XP_NEWS_PROMPT_ADD_NEWSGROUP), ""); if (!groupURL) return 0; /* User cancelled. */ MSG_FolderInfo *newsFolder = m_master->AddNewsGroup(groupURL); return newsFolder ? 0 : eUNKNOWN; } MsgERR MSG_FolderPane::Undo() { UndoManager *undoManager = GetUndoManager(); if (undoManager) { UndoStatus status; status = undoManager->Undo(); switch (status) { case UndoComplete: case UndoInProgress: return eSUCCESS; default: return eUNKNOWN; } } return 0; } MsgERR MSG_FolderPane::Redo() { UndoManager *undoManager = GetUndoManager(); if (undoManager) { UndoStatus status; status = undoManager->Redo(); switch (status) { case UndoComplete: case UndoInProgress: return eSUCCESS; default: return eUNKNOWN; } } return 0; } MsgERR MSG_FolderPane::MarkAllMessagesRead(MSG_ViewIndex* indices, int32 numIndices) { MSG_FolderInfo *folder; for (MSG_ViewIndex i = 0; i < numIndices; i++) { folder = GetFolderInfo(indices[i]); if (folder) // do a deep mark read for category containers. folder->MarkAllRead(GetContext(), folder->GetType() == FOLDER_CATEGORYCONTAINER); } return 0; } XP_Bool MSG_FolderPane::CanUndo() { UndoManager *undoManager = GetUndoManager(); if (undoManager) return undoManager->CanUndo(); return FALSE; } XP_Bool MSG_FolderPane::CanRedo() { UndoManager *undoManager = GetUndoManager(); if (undoManager) return undoManager->CanRedo(); return FALSE; } MsgERR MSG_FolderPane::DoCommand(MSG_CommandType command, MSG_ViewIndex* indices, int32 numIndices) { MsgERR status = 0; # define MSG_ASSERT_MAIL() \ XP_ASSERT(IndicesAreMail(indices,numIndices)); \ if (!IndicesAreMail(indices,numIndices)) break # define MSG_ASSERT_NEWS() \ XP_ASSERT(IndicesAreNews(indices,numIndices)); \ if (!IndicesAreNews(indices,numIndices)) break # define MSG_ASSERT_NOT_NEWS() \ XP_ASSERT(!IndicesAreNews(indices,numIndices)); \ if (IndicesAreNews(indices,numIndices)) break # define MSG_ASSERT_MAIL_OR_NEWS() ; // No other possibilities right now, // so nothing to assert! InterruptContext(FALSE); //###tw DisableUpdates(); switch (command) { /* FILE MENU ========= */ case MSG_GetNewMail: status = GetNewMail(this, (numIndices) ? GetFolderInfo(indices[0]) : 0); break; case MSG_OpenFolder: MSG_ASSERT_MAIL(); status = OpenFolder(); break; case MSG_NewFolder: MSG_ASSERT_NOT_NEWS(); // IMAP container ok status = NewFolderWithUI(indices, numIndices); break; case MSG_CompressFolder: MSG_ASSERT_MAIL(); status = CompressFolder(indices, numIndices); break; case MSG_AddNewsGroup: // MSG_ASSERT_NEWS(); should just use default news server status = AddNewsGroup(); break; case MSG_UpdateMessageCount: status = UpdateMessageCounts(indices, numIndices); break; case MSG_Print: XP_ASSERT(0); status = eUNKNOWN; break; case MSG_NewNewsgroup: { MSG_FolderInfo *folder = GetFolderInfo(indices[0]); XP_ASSERT(NewNewsgroupStatus(folder)); status = NewNewsgroup (folder, FOLDER_CATEGORYCONTAINER == folder->GetType()); } break; /* EDIT MENU ========= */ case MSG_Undo: MSG_ASSERT_MAIL_OR_NEWS(); status = Undo(); break; case MSG_Redo: MSG_ASSERT_MAIL_OR_NEWS(); status = Redo(); break; case MSG_Unsubscribe: // should be MSG_Delete but winfe is defeating me at the moment case MSG_DeleteFolder: case MSG_DeleteNoTrash: MSG_ASSERT_MAIL_OR_NEWS(); status = TrashFolders(indices, numIndices, (command == MSG_DeleteNoTrash)); break; case MSG_ManageMailAccount: MSG_ASSERT_MAIL(); status = ManageMailAccount((numIndices) ? GetFolderInfo(indices[0]) : 0); break; case MSG_ModerateNewsgroup: status = ModerateNewsgroup(GetFolderInfo(indices[0])); break; /* VIEW/SORT MENUS =============== */ /* MESSAGE MENU ============ */ case MSG_PostNew: MSG_ASSERT_NEWS(); status = ComposeNewsMessage(GetFolderInfo (indices[0])); break; /* GO MENU ======= */ case MSG_MarkAllRead: MSG_ASSERT_MAIL_OR_NEWS(); status = MarkAllMessagesRead(indices, numIndices); break; /* FOLDER MENU =========== */ case MSG_DoRenameFolder: MSG_ASSERT_MAIL(); status = RenameFolder (GetFolderInfo (indices[0])); break; /* OPTIONS MENU ============ */ case MSG_EmptyTrash: status = EmptyTrash((numIndices) ? GetFolderInfo(indices[0]) : 0); break; default: status = MSG_LinedPane::DoCommand(command, indices, numIndices); break; } # undef MSG_ASSERT_MAIL # undef MSG_ASSERT_NEWS # undef MSG_ASSERT_MAIL_OR_NEWS //###tw EnableUpdates(); return status; } MsgERR MSG_FolderPane::GetCommandStatus(MSG_CommandType command, const MSG_ViewIndex* indices, int32 numindices, XP_Bool *selectable_pP, MSG_COMMAND_CHECK_STATE *selected_pP, const char **display_stringP, XP_Bool *plural_pP) { const char *display_string = 0; XP_Bool plural_p = FALSE; XP_Bool selectable_p = TRUE; XP_Bool selected_p = FALSE; XP_Bool selected_used_p = FALSE; XP_Bool selected_newsgroups_p = FALSE; XP_Bool folder_selected_p; XP_Bool news_p = IndicesAreNews (indices, numindices); XP_Bool mail_p = IndicesAreMail (indices, numindices); //###phil not if it's a container XP_ASSERT(news_p || mail_p && !(news_p && mail_p)); // Amazing paranoia. folder_selected_p = (numindices > 0); if (news_p && folder_selected_p) { selected_newsgroups_p = TRUE; // ###tw Should figure out if anything // selected is actually a newsgroup. } switch (command) { /* FILE MENU ========= */ case MSG_OpenFolder: display_string = XP_GetString(MK_MSG_OPEN_FOLDER2); selectable_p = mail_p; break; case MSG_NewFolder: display_string = XP_GetString(MK_MSG_NEW_FOLDER); selectable_p = numindices == 1 && GetFolderInfo(indices[0])->CanCreateChildren(); break; case MSG_CompressFolder: display_string = XP_GetString(MK_MSG_COMPRESS_FOLDER); // multiple compresses are done with a url queue selectable_p = mail_p && folder_selected_p; break; case MSG_AddNewsGroup: display_string = XP_GetString(MK_MSG_ADD_NEWS_GROUP); // selectable_p = TRUE; /*news_p needs to work for news container.*/; /* ###tw && context->msgframe->data.news.current_host != NULL; */ selectable_p = GetMaster()->GetHostTable() != NULL; break; case MSG_Unsubscribe: selectable_p = news_p && numindices >= 1; // is last check redundant? display_string = XP_GetString(MK_MSG_UNSUBSCRIBE); break; case MSG_NewNewsgroup: display_string = XP_GetString(MK_MSG_NEW_NEWSGROUP); selectable_p = numindices > 0 && NewNewsgroupStatus (GetFolderInfo(indices[0])); break; /* EDIT MENU ========= */ case MSG_Undo: display_string = XP_GetString(MK_MSG_UNDO); selectable_p = CanUndo(); break; case MSG_Redo: display_string = XP_GetString(MK_MSG_REDO); selectable_p = CanRedo(); break; case MSG_DeleteFolder: // should be MSG_Delete { XP_Bool allSelectionsAreDeletable = TRUE; for (int i = 0; i < numindices; i++) { MSG_FolderInfo *f = m_folderView.GetAt(indices[i]); if (!f->IsDeletable() || ((f->GetFlags() & MSG_FOLDER_FLAG_NEWS_HOST) && numindices>1)) { allSelectionsAreDeletable = FALSE; break; } } display_string = XP_GetString(MK_MSG_DELETE_FOLDER); selectable_p = folder_selected_p && allSelectionsAreDeletable; break; } case MSG_ManageMailAccount: display_string = XP_GetString(MK_MSG_MANAGE_MAIL_ACCOUNT); selectable_p = mail_p && numindices > 0 && GetFolderInfo(indices[0])->HaveAdminUrl(MSG_AdminServer); break; case MSG_ModerateNewsgroup: display_string = XP_GetString (MK_MSG_MODERATE_NEWSGROUP); selectable_p = (numindices > 0 && ModerateNewsgroupStatus(GetFolderInfo(indices[0]))); break; case MSG_UpdateMessageCount: display_string = XP_GetString(MK_MSG_UPDATE_MSG_COUNTS); selectable_p = news_p || AnyIndicesAreIMAPMail (indices, numindices); break; /* VIEW/SORT MENUS =============== */ /* MESSAGE MENU ============ */ case MSG_PostNew: display_string = XP_GetString(MK_MSG_NEW_NEWS_MESSAGE); // we're currently only supporting posting to one newsgroup at a time. selectable_p = news_p && numindices == 1 && GetFolderInfo(indices[0])->AllowsPosting(); break; /* GO MENU ======= */ case MSG_MarkAllRead: display_string = XP_GetString(MK_MSG_MARK_ALL_READ); selectable_p = news_p && folder_selected_p && selected_newsgroups_p; break; /* FOLDER MENU =========== */ case MSG_DoRenameFolder: display_string = XP_GetString (MK_MSG_RENAME_FOLDER); selectable_p = mail_p && folder_selected_p && numindices == 1; if (selectable_p && !GetFolderInfo(indices[0])->CanBeRenamed ()) selectable_p = FALSE; break; /* OPTIONS MENU ============ */ default: return MSG_Pane::GetCommandStatus(command, indices, numindices, selectable_pP, selected_pP, display_stringP, plural_pP); } if (selectable_pP) *selectable_pP = selectable_p; if (selected_pP) { if (selected_used_p) { if (selected_p) *selected_pP = MSG_Checked; else *selected_pP = MSG_Unchecked; } else { *selected_pP = MSG_NotUsed; } } if (display_stringP) *display_stringP = display_string; if (plural_pP) *plural_pP = plural_p; return 0; } XP_Bool MSG_FolderPane::IndicesAreNews (const MSG_ViewIndex *indices, int32 numIndices) { for (int i = 0; i < numIndices; i++) { MSG_ViewIndex index = indices[i]; if (!IsValidIndex(index)) return FALSE; MSG_FolderInfo *folder = m_folderView.GetAt (index); if (!folder->IsNews()) return FALSE; } return TRUE; } XP_Bool MSG_FolderPane::IndicesAreMail (const MSG_ViewIndex *indices, int32 numIndices) { for (int i = 0; i < numIndices; i++) { MSG_ViewIndex index = indices[i]; if (!IsValidIndex(index)) return FALSE; MSG_FolderInfo *folder = m_folderView.GetAt(index); if (!folder->IsMail()) return FALSE; } return TRUE; } XP_Bool MSG_FolderPane::AnyIndicesAreIMAPMail (const MSG_ViewIndex *indices, int32 numIndices) { for (int i = 0; i < numIndices; i++) { MSG_ViewIndex index = indices[i]; if (!IsValidIndex(index)) continue; MSG_FolderInfo *folder = m_folderView.GetAt(index); if (folder->IsMail() && folder->GetType() == FOLDER_IMAPMAIL) return TRUE; } return FALSE; } MsgERR MSG_FolderPane::CheckForNew(MSG_NewsHost* host) { XP_ASSERT(host); if (!host) return eUNKNOWN; m_numNewGroups = 0; return MSG_Pane::CheckForNew(host); } void MSG_FolderPane::CheckForNewDone(URL_Struct* url_struct, int status, MWContext* context) { MSG_NewsHost* host = m_hostCheckingForNew; XP_ASSERT(host); if (!host) return; MSG_Pane::CheckForNewDone(url_struct, status, context); } void MSG_FolderPane::UpdateNewsCountsDone(int status) { if (status < 0) return; // See if we have a newshost opened that we haven't checked for new groups // on yet. If so, do it. msg_HostTable* table = m_master->GetHostTable(); XP_ASSERT(table); for (int32 i=0 ; igetNumHosts() ; i++) { MSG_NewsHost* host = table->getHost(i); XP_ASSERT(host); if (!host) continue; if (host->GetEverExpanded() && !host->GetCheckedForNew() && host->getLastUpdate() > 0) { host->SetCheckedForNew(TRUE); CheckForNew(host); return; } } } MSG_RemoteFolderCopyState::MSG_RemoteFolderCopyState(MSG_FolderInfo *sourceTree, MSG_FolderInfo *destinationTree, MSG_FolderPane *urlPane) { m_sourceTree = sourceTree; m_destinationTree = destinationTree; m_currentSourceNode = m_currentDestinationParent = NULL; m_urlPane = urlPane; m_currentTask = kMakingMailbox; m_copySourceDB = NULL; } MSG_RemoteFolderCopyState::~MSG_RemoteFolderCopyState() { if (m_copySourceDB) m_copySourceDB->Close(); } void MSG_RemoteFolderCopyState::SetNextSourceFromParent(MSG_FolderInfo *parent, MSG_FolderInfo *child) { if (parent && !parent->IsParentOf(m_sourceTree)) // prevent looking at the parent of m_sourceTree { XPPtrArray *children = parent->GetSubFolders(); int childIndex = children->FindIndex(0, child); if (++childIndex < parent->GetNumSubFolders()) { m_currentSourceNode = parent->GetSubFolder(childIndex); } else if (parent != m_sourceTree) { m_currentDestinationParent = m_destinationTree->FindParentOf(m_currentDestinationParent); SetNextSourceFromParent(m_sourceTree->FindParentOf(parent), parent); } else m_currentSourceNode = NULL; // we are finished } else m_currentSourceNode = NULL; // we are finished } void MSG_RemoteFolderCopyState::SetNextSourceNode() { if (m_currentSourceNode->HasSubFolders()) { // imap mailbox names are stored unescaped char *unescapedName = XP_STRDUP(m_currentSourceNode->GetName()); if (unescapedName) { m_currentDestinationParent = m_currentDestinationParent->FindChildNamed(NET_UnEscape(unescapedName)); if (m_currentDestinationParent) m_currentSourceNode = m_currentSourceNode->GetSubFolder(0); else m_currentSourceNode = NULL; // will stop this copy operation XP_FREE(unescapedName); } else m_currentSourceNode = NULL; // will stop this copy operation } else { // we have no children, kick up to my parent //m_currentDestinationParent = m_currentDestinationParent->FindParentOf(m_currentDestinationParent); SetNextSourceFromParent(m_sourceTree->FindParentOf(m_currentSourceNode), m_currentSourceNode); } } MsgERR MSG_RemoteFolderCopyState::MakeCurrentMailbox() { MsgERR returnCode = m_urlPane->GetMaster()->CreateMailFolder(m_urlPane, m_currentDestinationParent, m_currentSourceNode->GetName()); if ( (returnCode == 0) && (m_sourceTree->GetType() == FOLDER_IMAPMAIL)) returnCode = DoNextAction(); return returnCode; } /* static */ void MSG_FolderPane::RemoteFolderCopySelectCompleteExitFunction(URL_Struct *URL_s, int /*status*/, MWContext* /*window_id*/) { if (URL_s->msg_pane) { MSG_Pane *urlPane = (URL_s->msg_pane->GetParentPane() ? URL_s->msg_pane->GetParentPane() : URL_s->msg_pane); if (urlPane->GetPaneType() == MSG_FOLDERPANE) { MSG_FolderPane *folderPane = (MSG_FolderPane *) urlPane; if (folderPane->m_RemoteCopyState) folderPane->m_RemoteCopyState->DoNextAction(); } } } MsgERR MSG_RemoteFolderCopyState::DoUpdateCurrentMailbox() { MsgERR returnCode = eSUCCESS; XP_ASSERT(m_currentSourceNode); MSG_IMAPFolderInfoMail *srcMailFolder = m_currentSourceNode->GetIMAPFolderInfoMail(); if (srcMailFolder) { srcMailFolder->StartUpdateOfNewlySelectedFolder(m_urlPane, FALSE, NULL, NULL, FALSE, FALSE, MSG_FolderPane::RemoteFolderCopySelectCompleteExitFunction); } else { // must be local folder, no need to select DoNextAction(); } return returnCode; } MsgERR MSG_RemoteFolderCopyState::DoCurrentCopy() { MsgERR returnCode = eFAILURE; XP_ASSERT(m_currentSourceNode); // imap mailbox names are stored unescaped char *unescapedName = XP_STRDUP(m_currentSourceNode->GetName()); if (unescapedName) { MSG_FolderInfo *copyDestination = m_currentDestinationParent->FindChildNamed(NET_UnEscape(unescapedName)); if (copyDestination) { if (m_currentSourceNode->GetTotalMessages()) { MSG_FolderInfoMail *srcMailFolder = m_currentSourceNode->GetMailFolderInfo(); if (srcMailFolder) { DBFolderInfo *grpInfo = NULL; if (srcMailFolder->GetDBFolderInfoAndDB(&grpInfo, &m_copySourceDB) == 0) { IDArray *allMsgs = new IDArray; if (allMsgs) { m_copySourceDB->ListAllIds(*allMsgs); srcMailFolder->StartAsyncCopyMessagesInto(copyDestination, m_urlPane, m_copySourceDB, allMsgs, allMsgs->GetSize(), m_urlPane->GetContext(), NULL, FALSE); returnCode = 0; } } } } else returnCode = DoNextAction(); // nothing to copy here } XP_FREE(unescapedName); } return returnCode; } MsgERR MSG_RemoteFolderCopyState::DoNextAction() { MsgERR returnCode = 0; if (!m_currentSourceNode) { // this is the first call for this copy! m_currentTask = kMakingMailbox; m_currentSourceNode = m_sourceTree; m_currentDestinationParent = m_destinationTree; } else { if (m_currentTask == kCopyingMessages) { if (m_copySourceDB) { // we finished a copy, close its source DB m_copySourceDB->Close(); m_copySourceDB = NULL; } m_currentTask = kMakingMailbox; SetNextSourceNode(); } else if (m_currentTask == kMakingMailbox) m_currentTask = kUpdatingMailbox; else m_currentTask = kCopyingMessages; } if (m_currentSourceNode) { if (m_currentTask == kMakingMailbox) returnCode = MakeCurrentMailbox(); else if (m_currentTask == kUpdatingMailbox) returnCode = DoUpdateCurrentMailbox(); else returnCode = DoCurrentCopy(); } else returnCode = eFAILURE; // finito return returnCode; }