/* -*- 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.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): */ /* msgundmg.cpp --- Undo Manager implementation */ #include "msg.h" #include "msgundmg.h" #include "maildb.h" #include "msgdbvw.h" #include "msgpane.h" #include "msgmpane.h" #include "msgtpane.h" #include "mailhdr.h" #include "msgfinfo.h" extern "C" { extern int MK_OUT_OF_MEMORY; } /////////////////////////////////////////////////////////////////////////// // Implementation of UndoObject /////////////////////////////////////////////////////////////////////////// UndoObject::UndoObject() { m_owner = NULL; m_preExitFunc_hook = NULL; m_refCount = 1; } UndoObject::~UndoObject() { } int UndoObject::Release() { --m_refCount; if (!m_refCount) { delete this; return 0; } return m_refCount; } UndoStatus UndoObject::DoUndo() { return UndoComplete; } UndoStatus UndoObject::StopUndo( int /*status*/ ) { return UndoFailed; } // This is a default implementation. You should overwrite this // method. void UndoObject::FinalizeUndo() { // This is a default implementation. Client should // overwrite. XP_ASSERT(m_owner); AddRefCount(); m_owner->AddUndoAction (this); // Inform UndoManager that we have done with // current undo object. m_owner->DoUndoComplete(this); } void UndoObject::UndoURLHook(URL_Struct *url) { XP_ASSERT (url); if ( !url ) return; MSG_Pane *pane = url->msg_pane; XP_ASSERT (pane); if (!pane) return; pane->GetUndoManager()->AddInProgress(this); if (url->pre_exit_fn) m_preExitFunc_hook = url->pre_exit_fn; url->pre_exit_fn = UndoObject::UndoURLPreExitFunc; } void UndoObject::UndoURLPreExitFunc (URL_Struct *url, int status, MWContext *context) { XP_ASSERT (url); if ( !url ) return; if (url->msg_pane) { UndoManager *undoManager = url->msg_pane->GetUndoManager(); XP_ASSERT (undoManager); if (!undoManager) return; UndoObject *undoObject = undoManager->RemoveInProgress(); XP_ASSERT (undoObject); if (!undoObject) return; if (undoObject->m_preExitFunc_hook) undoObject->m_preExitFunc_hook (url, status, context); if (status < 0) { undoObject->StopUndo (status); undoManager->Reset(); return; } UndoStatus undoStatus; undoStatus = undoObject->UndoPreExit(); if (undoStatus == UndoComplete) undoObject->FinalizeUndo(); else { undoObject->StopUndo (status); undoManager->Reset(); } } } ///////////////////////////////////////////////////////////////////// // Implementation of UndoMarker ///////////////////////////////////////////////////////////////////// void UndoMarker::FinalizeUndo() { XP_ASSERT (IsMarker()); if (IsStart()) m_owner->StartBatch(); else m_owner->EndBatch(); m_owner->DoUndoComplete(this); } ///////////////////////////////////////////////////////////////// // Implementation of UndoManager ///////////////////////////////////////////////////////////////// UndoManager::UndoManager (MSG_Pane *pane, int maxActions) { m_maxUndoActions = maxActions; m_depth = 0; m_state = UndoIdle; m_count = 0; m_status = UndoComplete; m_pane = pane; m_refCount = 1; } UndoManager::~UndoManager () { FlushActions(); } int UndoManager::Init() { return 0; } int UndoManager::Release() { --m_refCount; if (!m_refCount) { delete this; return 0; } return m_refCount; } int UndoManager::StartBatch() { int status = 0; UndoMarker *marker = new UndoMarker (FALSE); if ( !marker ) return MK_OUT_OF_MEMORY; ++m_depth; if (m_state == UndoIdle || m_state == UndoRedoing) AddAtomicUndo(marker); else AddAtomicRedo(marker); return status; } int UndoManager::EndBatch() { int status = 0; UndoMarker *marker = new UndoMarker (TRUE); if (!marker) { Reset(); return MK_OUT_OF_MEMORY; } --m_depth; if (m_state == UndoIdle || m_state == UndoRedoing) AddAtomicUndo(marker); else AddAtomicRedo(marker); if (m_state == UndoIdle) FinalizeAtomicActions(); return status; } int UndoManager::AbortBatch() { XP_ASSERT (m_state == UndoIdle); int size; int status = 0; // **** There definitely much more we can do. For now // let's just free some memory resource. Revisit later!!!!!! while (1) { size = m_atomicUndoList.GetSize(); if (size == 0) break; ((UndoObject *) m_atomicUndoList.GetAt(--size))->Release(); m_atomicUndoList.RemoveAt(size); } return status; } int UndoManager::AddUndoAction( UndoObject *undoObject ) { int status = 0; XP_ASSERT (undoObject); if (!undoObject) return -1; undoObject->SetOwner(this); if (m_state == UndoIdle || m_state == UndoRedoing) { if (m_depth) { AddAtomicUndo(undoObject); } else { if (m_undoList.GetSize() >= m_maxUndoActions) FlushAtomic (&m_undoList); m_undoList.Add(undoObject); } } else { if (m_depth) { AddAtomicRedo (undoObject); } else { if (m_redoList.GetSize() >= m_maxUndoActions) FlushAtomic (&m_redoList); m_redoList.Add(undoObject); } } return status; } XP_Bool UndoManager::CanUndo() { return (m_undoList.GetSize() > 0); } XP_Bool UndoManager::CanRedo() { return (m_redoList.GetSize() > 0); } UndoStatus UndoManager::Undo() { UndoStatus status = UndoError; if (CanUndo()) { m_status = UndoComplete; m_state = UndoUndoing; status = FireAtomic( &m_undoList ); } if (status != UndoInProgress) m_state = UndoIdle; return status; } UndoStatus UndoManager::Redo() { UndoStatus status = UndoError; if (CanRedo()) { m_status = UndoComplete; m_state = UndoRedoing; status = FireAtomic( &m_redoList ); } if (status != UndoInProgress) m_state = UndoIdle; return status; } void UndoManager::FlushActions() { int size = 0; while (1) { size = m_undoList.GetSize(); if (size == 0) break; ((UndoObject*)m_undoList.GetAt(--size))->Release(); m_undoList.RemoveAt(size); } while (1) { size = m_redoList.GetSize(); if (size == 0) break; ((UndoObject*)m_redoList.GetAt(--size))->Release(); m_redoList.RemoveAt(size); } m_inProgressList.RemoveAll(); m_atomicUndoList.RemoveAll(); m_atomicRedoList.RemoveAll(); m_depth = 0; m_count = 0; m_state = UndoIdle; m_status = UndoComplete; } void UndoManager::RemoveActions(MSG_FolderInfo *folder) { if (!folder) return; int size; size = m_undoList.GetSize(); while (size) { if (((UndoObject*)m_undoList.GetAt(--size))->HasFolder(folder)) m_undoList.RemoveAt(size); } size = m_redoList.GetSize(); while(size) { if (((UndoObject*)m_undoList.GetAt(--size))->HasFolder(folder)) m_redoList.RemoveAt(size); } } void UndoManager::Reset() { int size = 0; if (m_state == UndoUndoing) { while (1) { size = m_atomicRedoList.GetSize(); if (size == 0) break; ((UndoObject*)m_atomicRedoList.GetAt(--size))->Release(); m_atomicRedoList.RemoveAt(size); } } else if (m_state == UndoRedoing) { while (1) { size = m_atomicUndoList.GetSize(); if (size == 0) break; ((UndoObject*)m_atomicUndoList.GetAt(--size))->Release(); m_atomicUndoList.RemoveAt(size); } } m_state = UndoIdle; m_depth = 0; m_count = 0; m_status = UndoComplete; } int UndoManager::AddInProgress (UndoObject *undoObject) { XP_ASSERT (undoObject); if (!undoObject) return -1; m_inProgressList.Add((void *)undoObject); return 0; } UndoObject* UndoManager::GetInProgress() { int size = m_inProgressList.GetSize(); XP_ASSERT(size > 0); if (size <= 0) return NULL; UndoObject *undoObject = (UndoObject *) m_inProgressList.GetAt(size-1); return undoObject; } UndoObject* UndoManager::RemoveInProgress() { UndoObject *undoObject = GetInProgress(); m_inProgressList.RemoveAt(m_inProgressList.GetSize()-1); return (undoObject); } void UndoManager::FlushAtomic (XPPtrArray *list) { int size = list->GetSize(); int count = 0; --size; // real index; UndoObject *undoObject = (UndoObject*) list->GetAt(size); XP_ASSERT (undoObject); if (!undoObject) return; if (m_depth) { // we are flush while we are adding a batch operation int depth = m_depth; while (depth && size >= 0) { if (undoObject->IsMarker() && undoObject->IsEnd()) depth--; undoObject = (UndoObject*) list->GetAt(--size); } } if (undoObject->IsMarker()) { int depth = 0; XP_ASSERT(undoObject->IsStart()); while (undoObject && size >= 0) { if (undoObject->IsStart()) depth++; else if (undoObject->IsEnd()) depth--; undoObject->Release(); ++count; list->RemoveAt(size--); if (!depth) return; undoObject = (UndoObject*) list->GetAt(size); } } else { ++count; undoObject->Release(); list->RemoveAt(size); } } // This is an extremely overloaded routine. I am not sure what else // I can do. Sigh..... UndoStatus UndoManager::FireAtomic (XPPtrArray *list) { if (m_status!= UndoComplete) return m_status; int size = list->GetSize(); if (!size) return UndoComplete; UndoObject *undoObject; if (!m_depth) m_count = 0; undoObject = (UndoObject*) list->GetAt(size-m_count-1); XP_ASSERT(undoObject); if (!undoObject) return UndoError; if (undoObject->IsMarker() || m_depth) { while (undoObject) { m_status = undoObject->DoUndo(); if (m_status == UndoInProgress) { return m_status; } else if (m_status == UndoComplete) { // What happen if something went wrong in the middle of an // atomic operation undoObject->FinalizeUndo(); } else { // Don't know what to do yet XP_ASSERT(0); } if (!m_depth) { // We are done with this batch. Reset undo state // to undo idle. m_state = UndoIdle; return m_status; } else if (m_status != UndoComplete) { return m_status; } undoObject = (UndoObject*)list->GetAt(size-m_count-1); } } else { m_status = undoObject->DoUndo(); if ( m_status == UndoComplete ) { undoObject->FinalizeUndo(); // FinalizeUndo() calls DoUndoComplete() if this is a single // atomic undo operation m_state is reset to UndoIdle in // DoUndoComplete(). return UndoComplete; } else if (m_status == UndoInProgress) { return UndoInProgress; } else { // don't know what to do yet return m_status; } } return m_status; } void UndoManager::DoUndoComplete(UndoObject* undoObject) { m_status = UndoComplete; ++m_count; if (m_depth) { if (m_state == UndoUndoing) FireAtomic(&m_undoList); else if (m_state == UndoRedoing) FireAtomic(&m_redoList); else XP_ASSERT(0); // **** Post Error message if error occured } else { if (undoObject->IsMarker() && undoObject->IsEnd()) { FinalizeAtomicActions(); } else { if (m_state == UndoUndoing) m_undoList.Remove((void *) undoObject); else if (m_state == UndoRedoing) m_redoList.Remove((void *) undoObject); undoObject->Release(); } m_state = UndoIdle; } } void UndoManager::AddAtomicUndo(UndoObject *undoObject) { m_atomicUndoList.Add(undoObject); } void UndoManager::AddAtomicRedo(UndoObject *undoObject) { m_atomicRedoList.Add(undoObject); } void UndoManager::FinalizeAtomicActions() { XPPtrArray *list = (m_state == UndoUndoing ? &m_atomicRedoList : &m_atomicUndoList ); if (!m_depth) { UndoObject *undoObject; int size = list->GetSize(); int count = m_count; for (int i=0; iGetAt(i); AddUndoAction(undoObject); } list->RemoveAll(); if (m_state == UndoIdle) return; list = (m_state == UndoUndoing ? &m_undoList : &m_redoList ); while (count) { undoObject = (UndoObject *) list->GetAt(list->GetSize()-1); undoObject->Release(); list->RemoveAt(list->GetSize()-1); count--; } } } int UndoManager::AddMsgKey(MessageKey key) { int status = 0; m_msgKeyArray.Add(key); return status; } MessageKey UndoManager::GetAndRemoveMsgKey() { if (!m_msgKeyArray.GetSize()) return MSG_MESSAGEKEYNONE; MessageKey key = m_msgKeyArray.GetAt(0); m_msgKeyArray.RemoveAt(0); return key; } void UndoManager::SetUndoMsgFolder( MSG_FolderInfo *folder ) { m_undoMsgFolder = folder; } MSG_FolderInfo * UndoManager::GetUndoMsgFolder() { return m_undoMsgFolder; } ////////////////////////////////////////////////////////////////////////// // BacktrackManager ////////////////////////////////////////////////////////////////////////// BacktrackManager::BacktrackManager(MSG_Pane *pane) { m_pane = pane; m_cursor = -1; m_state = MSG_BacktrackIdle; m_curKey = MSG_MESSAGEKEYNONE; m_curFolder = NULL; } BacktrackManager::~BacktrackManager() { Reset(); } int BacktrackManager::AddEntry(MSG_FolderInfo *folder, MessageKey key) { int status = 0; BacktrackEntry *entry = NULL; if (m_state != MSG_BacktrackIdle) { return status; } if (m_cursor >= 0) { entry = (BacktrackEntry *) m_backtrackArray.GetAt(m_cursor); if (entry->folder == folder && entry->key == key) goto done; } if (m_cursor-1 >= 0) { entry = (BacktrackEntry *) m_backtrackArray.GetAt(m_cursor-1); if (entry->folder == folder && entry->key == key) goto done; } if ((m_cursor+1) < m_backtrackArray.GetSize()) { entry = (BacktrackEntry *) m_backtrackArray.GetAt(m_cursor+1); if (entry->folder == folder && entry->key == key) goto done; } entry = new BacktrackEntry; if (entry) { m_cursor++; entry->folder = folder; entry->key = key; m_backtrackArray.InsertAt(m_cursor, (void*)entry, 1); goto done; } else return MK_OUT_OF_MEMORY; done: if (entry) SetCurMessageKey (entry->folder, entry->key); return status; } XP_Bool BacktrackManager::CanGoBack() { if (m_cursor <=0) return FALSE; BacktrackEntry *entry = NULL; for (int i=m_cursor-1; i >= 0; i--) { entry = (BacktrackEntry *) m_backtrackArray.GetAt(i); XP_ASSERT (entry->key != MSG_MESSAGEKEYNONE); if (entry->key != MSG_MESSAGEKEYNONE) return TRUE; } return FALSE; } XP_Bool BacktrackManager::CanGoForward() { int size = m_backtrackArray.GetSize(); BacktrackEntry *entry = NULL; if (m_cursor >= size-1) return FALSE; for (int i=m_cursor+1; i < size; i++) { entry = (BacktrackEntry*) m_backtrackArray.GetAt(i); XP_ASSERT (entry->key != MSG_MESSAGEKEYNONE); if (entry->key != MSG_MESSAGEKEYNONE) return TRUE; } return FALSE; } MessageKey BacktrackManager::GoBack(MSG_FolderInfo **pFolderInfo) { XP_ASSERT (m_cursor > 0); int i; DBMessageHdr *msgHdr = NULL; BacktrackEntry *entry = NULL; MailDB *mailDb; m_state = MSG_BacktrackBackward; for (i=m_cursor-1; i >= 0; i--) { entry = (BacktrackEntry*) m_backtrackArray.GetAt(i); if (entry->folder != m_curFolder) { // *** if the folder is a newsgroup folder we done. if (entry->folder->IsNews()) break; // if this is a mail folder does this message get deleted? MsgERR err = eUNKNOWN; mailDb= NULL; err = MailDB::Open(entry->folder->GetMailFolderInfo()->GetPathname(), FALSE, &mailDb); if (err == eSUCCESS) { msgHdr = mailDb->GetDBHdrForKey(entry->key); if (msgHdr) { mailDb->Close(); break; } } if (mailDb) mailDb->Close(); } else if (entry->key != m_curKey) { msgHdr = m_pane->GetMsgView()->GetDB()->GetDBHdrForKey(entry->key); if (msgHdr) break; } } delete msgHdr; if (entry) { if (pFolderInfo && m_curFolder != entry->folder) *pFolderInfo = entry->folder; m_cursor = i; SetCurMessageKey(entry->folder, entry->key); return entry->key; } return MSG_MESSAGEKEYNONE; } MessageKey BacktrackManager::GoForward(MSG_FolderInfo **pFolderInfo) { int size = m_backtrackArray.GetSize(); XP_ASSERT (m_cursor < size -1); int i; DBMessageHdr *msgHdr = NULL; BacktrackEntry *entry = NULL; MailDB *mailDb; m_state = MSG_BacktrackForward; for (i=m_cursor+1; i < size; i++) { entry = (BacktrackEntry*) m_backtrackArray.GetAt(i); if (entry->folder != m_curFolder) { // *** if the folder is a newsgroup folder we done. if (entry->folder->IsNews()) break; // if this is a mail folder does this message get deleted? MsgERR err = eUNKNOWN; mailDb= NULL; err = MailDB::Open(entry->folder->GetMailFolderInfo()->GetPathname(), FALSE, &mailDb); if (err == eSUCCESS) { msgHdr = mailDb->GetDBHdrForKey(entry->key); if (msgHdr) { mailDb->Close(); break; } } if (mailDb) mailDb->Close(); } else if (entry->key != m_curKey) { msgHdr = m_pane->GetMsgView()->GetDB()->GetDBHdrForKey(entry->key); if (msgHdr) break; } } delete msgHdr; if (entry) { if (pFolderInfo && m_curFolder != entry->folder) *pFolderInfo = entry->folder; m_cursor = i; SetCurMessageKey(entry->folder, entry->key); return entry->key; } return MSG_MESSAGEKEYNONE; } void BacktrackManager::Reset() { BacktrackEntry *entry = NULL; int size = m_backtrackArray.GetSize(); for (int i=0; i < size; i++) { entry = (BacktrackEntry *) m_backtrackArray.GetAt(0); m_backtrackArray.RemoveAt(0); delete entry; } m_cursor = -1; m_curKey = MSG_MESSAGEKEYNONE; m_curFolder = NULL; } void BacktrackManager::SetCurMessageKey(MSG_FolderInfo *folder, MessageKey key) { m_curFolder = folder; m_curKey = key; } void BacktrackManager::RemoveEntries(MSG_FolderInfo *folder) { int size = m_backtrackArray.GetSize(); BacktrackEntry *entry = NULL; if (!folder || size <= 0) return; while (size) { entry = (BacktrackEntry *) m_backtrackArray.GetAt(--size); if (entry->folder == folder) { m_backtrackArray.RemoveAt(size); delete entry; if (m_cursor == size) { m_cursor = -1; m_curKey = MSG_MESSAGEKEYNONE; m_curFolder = NULL; } else if (m_cursor > size) { m_cursor--; } } } }