You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

355 lines
8.4 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

//////////////////////////////////////////////////////////////////////////////
//主要功能: 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