pjs/lib/libmsg/msgfpane.cpp

2689 строки
82 KiB
C++

/* -*- 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 ; i<m_folderView.GetSize() ; i++)
{
MSG_FolderInfo *folder = m_folderView.GetAt((MSG_ViewIndex) i);
if (folder->GetNumUnread() == -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 ; i<numlines ; i++)
{
MSG_FolderInfo *folder = m_folderView.GetAt(line + i);
XP_Bool result = m_master->GetFolderLineById(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 ; i<table->getNumHosts() ; 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;
}