|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
//<2F><>Ҫ<EFBFBD><D2AA><EFBFBD><EFBFBD>: Undo and Redo<64><6F>ȫ<EFBFBD>ֲ<EFBFBD><D6B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><CEAA><EFBFBD><EFBFBD><EFBFBD>ַ<EFBFBD><D6B7><EFBFBD><EFBFBD><EFBFBD><DEB8><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC>ĵ<DEB8>Redo/Undo
|
|
|
|
|
|
//
|
|
|
|
|
|
//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>д: 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 ջ<>Խ<EFA3AC><D4BD><EFBFBD><EFBFBD>ջ<EFBFBD>ģ<D7B5>Խ<EFBFBD><D4BD><EFBFBD>Ȳ<EFBFBD><C8B2><EFBFBD><EFBFBD><EFBFBD>
|
|
|
|
|
|
for (auto* pAction : undoStack)
|
|
|
|
|
|
{
|
|
|
|
|
|
details.push_back({ pAction->GetUUID(), pAction->GetActionName(), FormatTime(pAction->GetTimePoint()) });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// redo ջ<><D5BB><EFBFBD><EFBFBD> Action һ<><D2BB><EFBFBD><EFBFBD> undo ջ<><D5BB><EFBFBD><EFBFBD>ִ<EFBFBD>У<EFBFBD><D0A3><EFBFBD><EFBFBD><EFBFBD>Խ<EFBFBD>ǿ<EFBFBD><C7BF><EFBFBD>ջ<EFBFBD><D5BB>Խ<EFBFBD><D4BD><EFBFBD>Ȳ<EFBFBD><C8B2><EFBFBD><EFBFBD><EFBFBD> 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)
|
|
|
|
|
|
{
|
|
|
|
|
|
// <20><><EFBFBD><EFBFBD>Ϊ<EFBFBD>գ<EFBFBD><D5A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˻ص<CBBB>ԭʼͼ<CABC><CDBC>
|
|
|
|
|
|
if (targetUUID.empty())
|
|
|
|
|
|
{
|
|
|
|
|
|
while (CanUndo())
|
|
|
|
|
|
{
|
|
|
|
|
|
Undo();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ÿ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><EFBFBD> Action<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> undoStack<EFBFBD><EFBFBD><EFBFBD>ٶ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ѿ<EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD> Action <EFBFBD>ˣ<EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD>ᷢ<EFBFBD><EFBFBD>ʲô<EFBFBD><EFBFBD>
|
|
|
|
|
|
* <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǣ<EFBFBD><EFBFBD><EFBFBD> Action <EFBFBD><EFBFBD> undoStack ջ<EFBFBD><EFBFBD>ջ<EFBFBD><EFBFBD>
|
|
|
|
|
|
* <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Ŀ<EFBFBD><EFBFBD> Action <EFBFBD><EFBFBD> undoStack ջ<EFBFBD>У<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ǿ<EFBFBD><EFBFBD><EFBFBD>Ҫһֱ Undo ֱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> undoStack ջ<EFBFBD><EFBFBD>Ϊֹ<EFBFBD><EFBFBD>
|
|
|
|
|
|
* <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><EFBFBD> Action <EFBFBD><EFBFBD> redoStack <EFBFBD>У<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫһֱ Redo ֱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> undoStack ջ<EFBFBD><EFBFBD>
|
|
|
|
|
|
*
|
|
|
|
|
|
* | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|
|
|
|
|
|
* /\
|
|
|
|
|
|
* <EFBFBD><EFBFBD><EFBFBD>Dz<EFBFBD><EFBFBD>Dz<EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD><EFBFBD><EFBFBD><EFBFBD>α<EFBFBD><EFBFBD><EFBFBD>ʵ<EFBFBD>ֲ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͻ<EFBFBD><EFBFBD>˵ģ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Dz<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ջ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˼·<EFBFBD><EFBFBD>һ<EFBFBD><EFBFBD><EFBFBD>ģ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>У<EFBFBD><EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
|
|
|
|
* ͣ<EFBFBD><EFBFBD><EFBFBD>ڲ<EFBFBD><EFBFBD><EFBFBD> 4 <EFBFBD>ϣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> undo<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> redo<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD><EFBFBD> 2<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ȼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> undo<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD><EFBFBD> 6 <EFBFBD><EFBFBD><EFBFBD><EFBFBD> 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
|