////////////////////////////////////////////////////////////////////////////// //主要功能: Undo and Redo的全局操作函数,为了区分符号修改与文件修改的Redo/Undo // //程序编写: 2006-12-07 ///////////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "UndoManager.h" #include "Action.h" #include "Util.h" #include #include #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif namespace NAction { CActionManager::CActionManager(void *aTarget) : actionTarget(aTarget) , cleanMarker(0) , capacity(30) , m_pSavedAction(nullptr) { } CActionManager::~CActionManager() { DeleteActions(undoStack, (long)undoStack.size()); DeleteActions(redoStack, (long)redoStack.size()); } void CActionManager::SetStackCapacity(long nActions) { assert(nActions > 0); // the new size may be smaller than the previous stack // size, so we may have to delete some initial actions if ((long)undoStack.size() > nActions) DeleteActions(undoStack, (long)undoStack.size() - nActions); if ((long)redoStack.size() > nActions) DeleteActions(redoStack, (long)redoStack.size() - nActions); capacity = nActions; // and the new size may also be greater than our previous // clean state; if so, we need to mark the document so that // it cannot be cleaned if (cleanMarker >= capacity) cleanMarker = -1; } void CActionManager::SavePostion() { deque ::iterator itEnd = undoStack.end(); if (itEnd == undoStack.begin()) { m_pSavedAction = nullptr; return; } itEnd--; m_pSavedAction = *itEnd; } bool CActionManager::IsModified() { deque ::iterator itEnd = undoStack.end(); if (itEnd == undoStack.begin()) { return FALSE; } itEnd--; return m_pSavedAction != *itEnd; } void CActionManager::SetLastAction(CAction *anAction) { // ensure last action knows its done if (undoStack.empty() == false) { CAction *previousAction = undoStack.back(); if (previousAction->IsFinished() == false) previousAction->Finish(); } // this may just be a notification that the current // action is done; if so, the action must finish // (above) but nothing else should change. if (anAction != 0) { // a new undoable action cancels any previously redoable ones DeleteActions(redoStack, (long)redoStack.size()); // remove bottom action if stack is full if (undoStack.size() == capacity) { CAction *firstAction = undoStack.front(); // we are removing an action which modified the // command target (e.g., a document); this effects // our ability to undo back into an unmodified state cleanMarker = -1; undoStack.pop_front(); delete firstAction; firstAction = nullptr; } // we use the standard reverse vector approach for our stacks, // even though we can efficiently push front with a deque; this // way however, indexes in the deque remain constant; that makes // building menus and tracking the clean marker much easier undoStack.push_back(anAction); //NotifyTarget(UM_Do, 1); } //else // NotifyTarget(UM_Do, 1); } const UMString &CActionManager::GetActionName(StackKind kind, long pos) const { const ActionStack &theStack = (kind == CActionManager::UNDO) ? undoStack : redoStack; assert(pos < (long)theStack.size()); return theStack[theStack.size() - (pos + 1)]->GetName(); } long CActionManager::GetStackSize(CActionManager::StackKind kind) const { const ActionStack &theStack = (kind == CActionManager::UNDO) ? undoStack : redoStack; return (long)theStack.size(); } bool CActionManager::CanUndo(long nActions) const { return nActions <= (long)undoStack.size(); } int CActionManager::Undo(long nActions) { //assert( CanUndo(nActions) ); if (!CanUndo(nActions)) return 0; int nActionType = 0; for (long i = 0; i < nActions; ++i) { CAction *lastAction = undoStack.back(); undoStack.pop_back(); // to undo it, the action must finish // whatever its currently doing if (lastAction->IsFinished() == false) lastAction->Finish(); lastAction->Undo(); // redo size is limited by the undo stack and its // limitations are enforced by SetLastAction() redoStack.push_back(lastAction); nActionType = lastAction->GetActionType(); } return nActionType; } bool CActionManager::CanRedo(long nActions) const { return nActions <= (long)redoStack.size(); } int CActionManager::Redo(long nActions) { //assert( CanRedo(nActions) ); if (!CanRedo(nActions)) return 0; int nActionType = 0; for (long i = 0; i < nActions; ++i) { CAction *lastAction = redoStack.back(); redoStack.pop_back(); lastAction->Redo(); // undo size is limited by the redo stack, which is // limited in turn by the original undo stack, which // is limited by SetLastAction() (whew!) undoStack.push_back(lastAction); nActionType = lastAction->GetActionType(); } return nActionType; } void CActionManager::TargetCleaned() { if (undoStack.size() > 0) { // finish the last action; once we've // saved a document, actions like typing // must create a new action CAction *lastAction = undoStack.back(); if (lastAction->IsFinished() == false) lastAction->Finish(); } // set the marker to the size of the stack // (we must be at that point for the target // to be clean) cleanMarker = (long)undoStack.size(); } const CActionManager::ActionStack& CActionManager::GetUndoStack() const { return undoStack; } const CActionManager::ActionStack& CActionManager::GetRedoStack() const { return redoStack; } std::vector CActionManager::ListActionDetails() const { std::vector details; // undo 栈里,越靠近栈底的,越是先插入的 for (auto* pAction : undoStack) { details.push_back({ pAction->GetUUID(), pAction->GetActionName(), FormatTime(pAction->GetTimePoint()) }); } // redo 栈里的 Action 一定比 undo 栈里后执行,并且越是靠近栈顶越是先插入的 Action for (auto it = redoStack.rbegin(); it != redoStack.rend(); it++) { CAction* pAction = *it; details.push_back({ pAction->GetUUID(), pAction->GetActionName(), FormatTime(pAction->GetTimePoint()) }); } return details; } void CActionManager::RestoreToTargetAction(const std::string& targetUUID) { // 如果为空,我们退回到原始图件 if (targetUUID.empty()) { while (CanUndo()) { Undo(); } return; } /* * 我们每添加一个 Action,它都会进行 undoStack,假定我们已经恢复到指定 Action 了,这时会发生什么? * 答案是:该 Action 在 undoStack 栈的栈顶 * 所以如果 目标 Action 在 undoStack 栈中,我们就需要一直 Undo 直到它到 undoStack 栈顶为止, * 如果目标 Action 在 redoStack 中,则需要一直 Redo 直接它到 undoStack 栈顶 * * | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * /\ * 我们不是采用列表加游标来实现操作和回退的,而是采用两个栈,但是思路是一样的,在上面的例子中,当前操作 * 停留在操作 4 上,往左移是 undo,往右移是 redo,如果想恢复到 2,显然就是 undo,想恢复到 6 则是 redo */ if (InUndoStack(targetUUID)) { std::string currentUUID; while (!undoStack.empty()) { currentUUID = undoStack.back()->GetUUID(); if (currentUUID == targetUUID) { break; } Undo(); } } else if (InRedoStack(targetUUID)) { std::string currentUUID; while (!redoStack.empty()) { currentUUID = redoStack.back()->GetUUID(); Redo(); if (currentUUID == targetUUID) { break; } } } } bool CActionManager::InUndoStack(const std::string& uuid) const { return AnyOf(undoStack, [&uuid](const CAction* pAction) { return pAction->GetUUID() == uuid; }); } bool CActionManager::InRedoStack(const std::string& uuid) const { return AnyOf(redoStack, [&uuid](const CAction* pAction) { return pAction->GetUUID() == uuid; }); } void CActionManager::DeleteActions(ActionStack &aStack, long nActions) { // delete from the bottom up; because // we're using a reverse storage strategy, // we walk from front to back for (long i = 0; i < nActions; ++i) { CAction *anAction = aStack.front(); aStack.pop_front(); delete anAction; anAction = nullptr; } } void CActionManager::RemoveLastAction() { assert(CanUndo(1)); undoStack.pop_back(); // may have undone very first action since last // save, or undone something that was saved //NotifyTarget(UM_Undo, nActions); } long CActionManager::GetStackCapacity() const { return capacity; } void* CActionManager::GetTarget() const { return actionTarget; } void CActionManager::ClearTarget() //clear all actioin { DeleteActions(undoStack, (long)undoStack.size()); DeleteActions(redoStack, (long)redoStack.size()); } }//end namespace