|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
//主要功能: Undo and Redo的全局操作函数,为了区分符号修改与文件修改的Redo/Undo
|
|
|
//
|
|
|
//程序编写: 2006-12-07
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
#include "stdafx.h"
|
|
|
#include "UndoManager.h"
|
|
|
#include "Action.h"
|
|
|
#include "Util.h"
|
|
|
|
|
|
#include <assert.h>
|
|
|
#include <algorithm>
|
|
|
|
|
|
#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 <CAction *>::iterator itEnd = undoStack.end();
|
|
|
if (itEnd == undoStack.begin())
|
|
|
{
|
|
|
m_pSavedAction = nullptr;
|
|
|
return;
|
|
|
}
|
|
|
itEnd--;
|
|
|
m_pSavedAction = *itEnd;
|
|
|
}
|
|
|
bool CActionManager::IsModified()
|
|
|
{
|
|
|
deque <CAction *>::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<ActionDetail> CActionManager::ListActionDetails() const
|
|
|
{
|
|
|
std::vector<ActionDetail> 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
|