Files
FC1/Editor/Undo/Undo.cpp
romkazvo 34d6c5d489 123
2023-08-07 19:29:24 +08:00

583 lines
14 KiB
C++

////////////////////////////////////////////////////////////////////////////
//
// Crytek Engine Source File.
// Copyright (C), Crytek Studios, 2001.
// -------------------------------------------------------------------------
// File name: Undo.cpp
// Version: v1.00
// Created: 9/1/2002 by Timur.
// Compilers: Visual C++ 6.0
// Description:
// -------------------------------------------------------------------------
// History:
//
////////////////////////////////////////////////////////////////////////////
#include "StdAfx.h"
#include "Undo.h"
#include "IUndoObject.h"
#include "Objects\ObjectManager.h"
#include "Settings.h"
//! CUndo is a collection of IUndoObjects instances that forms single undo step.
class CUndoStep
{
public:
CUndoStep() {};
virtual ~CUndoStep() { ClearObjects(); }
//! Set undo object name.
void SetName( const CString &name ) { m_name = name; };
//! Get undo object name.
const CString& GetName() { return m_name; };
//! Add new undo object to undo step.
void AddUndoObject( IUndoObject *o )
{
m_undoObjects.push_back(o);
}
void ClearObjects()
{
int i;
// Release all undo objects.
for (i = 0; i < m_undoObjects.size(); i++)
{
m_undoObjects[i]->Release();
}
m_undoObjects.clear();
};
virtual int GetSize() const
{
int size = 0;
for (int i = 0; i < m_undoObjects.size(); i++)
{
size += m_undoObjects[i]->GetSize();
}
return size;
}
virtual bool IsEmpty() const { return m_undoObjects.empty(); };
virtual void Undo( bool bUndo )
{
for (int i = m_undoObjects.size()-1; i >= 0; i--)
{
m_undoObjects[i]->Undo( bUndo );
}
}
virtual void Redo()
{
for (int i = 0; i < m_undoObjects.size(); i++)
{
m_undoObjects[i]->Redo();
}
}
private:
friend class CUndoManager;
CString m_name;
// Undo objects registered for this step.
std::vector<IUndoObject*> m_undoObjects;
};
//! CSuperUndo objects groups together a block of UndoStepto to allow them to be Undo by single operation.
class CSuperUndoStep : public CUndoStep
{
public:
//! Add new undo object to undo step.
void AddUndoStep( CUndoStep *step )
{
m_undoSteps.push_back(step);
}
virtual int GetSize() const
{
int size = 0;
for (int i = 0; i < m_undoSteps.size(); i++)
{
size += m_undoSteps[i]->GetSize();
}
return size;
}
virtual bool IsEmpty() { return m_undoSteps.empty(); };
virtual void Undo( bool bUndo )
{
for (int i = m_undoSteps.size()-1; i >= 0; i--)
{
m_undoSteps[i]->Undo( bUndo );
}
}
virtual void Redo()
{
for (int i = 0; i < m_undoSteps.size(); i++)
{
m_undoSteps[i]->Redo();
}
}
private:
//! Undo steps included in this step.
std::vector<CUndoStep*> m_undoSteps;
};
//////////////////////////////////////////////////////////////////////////
CUndoManager::CUndoManager()
{
m_bRecording = false;
m_bSuperRecording = false;
m_currentUndo = 0;
m_superUndo = 0;
m_suspendCount = 0;
m_bUndoing = false;
m_bRedoing = false;
}
//////////////////////////////////////////////////////////////////////////
CUndoManager::~CUndoManager()
{
m_bRecording = false;
ClearRedoStack();
ClearUndoStack();
delete m_superUndo;
delete m_currentUndo;
}
//////////////////////////////////////////////////////////////////////////
void CUndoManager::Begin()
{
//CLogFile::FormatLine( "<Undo> Begin SuspendCount=%d",m_suspendCount );
//if (m_bSuperRecording)
//CLogFile::FormatLine( "<Undo> Begin (Inside SuperSuper)" );
if (m_bUndoing || m_bRedoing) // If Undoing or redoing now, ignore this calls.
return;
assert( m_bRecording == false );
if (m_bRecording)
{
//CLogFile::WriteLine( "<Undo> Begin (already recording)" );
// Not cancel, just combine.
return;
}
// Begin Creates a new undo object.
m_currentUndo = new CUndoStep;
m_bRecording = true;
//CLogFile::WriteLine( "<Undo> Begin OK" );
}
//////////////////////////////////////////////////////////////////////////
void CUndoManager::Restore( bool bUndo )
{
if (m_bUndoing || m_bRedoing) // If Undoing or redoing now, ignore this calls.
return;
if (m_currentUndo)
{
Suspend();
if (bUndo)
m_currentUndo->Undo(false); // Undo not by Undo command (no need to store Redo)
Resume();
m_currentUndo->ClearObjects();
}
//CLogFile::FormatLine( "Reset Undo" );
}
//////////////////////////////////////////////////////////////////////////
void CUndoManager::Accept( const CString &name )
{
//CLogFile::FormatLine( "<Undo> Accept, Suspend Count=%d",m_suspendCount );
if (m_bUndoing || m_bRedoing) // If Undoing or redoing now, ignore this calls.
return;
if (!m_bRecording)
{
//CLogFile::WriteLine( "<Undo> Accept (Not recording)" );
return;
}
if (!m_currentUndo->IsEmpty())
{
GetIEditor()->SetModifiedFlag();
// If accepting new undo object, must clear all redo stack.
ClearRedoStack();
m_currentUndo->SetName(name);
if (m_bSuperRecording)
{
m_superUndo->AddUndoStep( m_currentUndo );
}
else
{
// Normal recording.
// Keep max undo steps.
if (m_undoStack.size() > gSettings.undoLevels)
{
delete m_undoStack.front();
m_undoStack.pop_front();
}
m_undoStack.push_back( m_currentUndo );
}
CLogFile::FormatLine( "Undo Object Accepted (Undo:%d,Redo:%d, Size=%dKb)",m_undoStack.size(),m_redoStack.size(),GetDatabaseSize()/1024 );
// If undo accepted, document modified.
GetIEditor()->SetModifiedFlag();
}
else
{
// If no any object was recorded, Cancel undo operation.
Cancel();
}
m_bRecording = false;
m_currentUndo = 0;
//CLogFile::WriteLine( "<Undo> Accept OK" );
}
//////////////////////////////////////////////////////////////////////////
void CUndoManager::Cancel()
{
//CLogFile::WriteLine( "<Undo> Cancel" );
if (m_bUndoing || m_bRedoing) // If Undoing or redoing now, ignore this calls.
return;
if (!m_bRecording)
return;
assert( m_currentUndo != 0 );
m_bRecording = false;
if (!m_currentUndo->IsEmpty())
{
// Restore all objects to the state they was at Begin call and throws out all undo objects.
Restore( true );
CLogFile::FormatLine( "Undo Object Canceled (Undo:%d,Redo:%d)",m_undoStack.size(),m_redoStack.size() );
}
delete m_currentUndo;
m_currentUndo = 0;
//CLogFile::WriteLine( "<Undo> Cancel OK" );
}
//////////////////////////////////////////////////////////////////////////
void CUndoManager::Redo( int numSteps )
{
if (m_bUndoing || m_bRedoing) // If Undoing or redoing now, ignore this calls.
return;
if (m_bRecording || m_bSuperRecording)
{
CLogFile::FormatLine( "Cannot Redo while Recording" );
return;
}
if (!m_redoStack.empty())
{
Suspend();
while (numSteps-- > 0 && !m_redoStack.empty())
{
m_bRedoing = true;
CUndoStep *redo = m_redoStack.back();
redo->Redo();
m_redoStack.pop_back();
// Push undo object to redo stack.
m_undoStack.push_back( redo );
m_bRedoing = false;
}
Resume();
}
if (m_suspendCount == 0)
GetIEditor()->UpdateViews(eUpdateObjects);
CLogFile::FormatLine( "Redo (Undo:%d,Redo:%d)",m_undoStack.size(),m_redoStack.size() );
GetIEditor()->GetObjectManager()->InvalidateVisibleList();
}
//////////////////////////////////////////////////////////////////////////
void CUndoManager::Undo( int numSteps )
{
if (m_bUndoing || m_bRedoing) // If Undoing or redoing now, ignore this calls.
return;
if (m_bRecording || m_bSuperRecording)
{
CLogFile::FormatLine( "Cannot Undo while Recording" );
return;
}
if (!m_undoStack.empty())
{
Suspend();
while (numSteps-- > 0 && !m_undoStack.empty())
{
m_bUndoing = true;
CUndoStep *undo = m_undoStack.back();
undo->Undo(true);
m_undoStack.pop_back();
// Push undo object to redo stack.
m_redoStack.push_back( undo );
m_bUndoing = false;
}
Resume();
}
// Update viewports.
if (m_suspendCount == 0)
GetIEditor()->UpdateViews(eUpdateObjects);
CLogFile::FormatLine( "Undo (Undo:%d,Redo:%d)",m_undoStack.size(),m_redoStack.size() );
GetIEditor()->GetObjectManager()->InvalidateVisibleList();
}
//////////////////////////////////////////////////////////////////////////
void CUndoManager::RecordUndo( IUndoObject *obj )
{
//CLogFile::FormatLine( "<Undo> RecordUndo Name=%s",obj->GetDescription() );
if (m_bUndoing || m_bRedoing) // If Undoing or redoing now, ignore this calls.
{
//CLogFile::WriteLine( "<Undo> RecordUndo (Undoing or Redoing)" );
obj->Release();
return;
}
if (m_bRecording)
{
assert( m_currentUndo != 0 );
m_currentUndo->AddUndoObject( obj );
//CLogFile::FormatLine( "Undo Object Added: %s",obj->GetDescription() );
}
else
{
//CLogFile::WriteLine( "<Undo> RecordUndo (Not Recording)" );
// Ignore this object.
obj->Release();
}
}
//////////////////////////////////////////////////////////////////////////
void CUndoManager::ClearRedoStack()
{
for (std::list<CUndoStep*>::iterator it = m_redoStack.begin(); it != m_redoStack.end(); it++)
{
delete *it;
}
m_redoStack.clear();
}
//////////////////////////////////////////////////////////////////////////
void CUndoManager::ClearUndoStack()
{
for (std::list<CUndoStep*>::iterator it = m_undoStack.begin(); it != m_undoStack.end(); it++)
{
delete *it;
}
m_undoStack.clear();
}
//////////////////////////////////////////////////////////////////////////
void CUndoManager::ClearUndoStack( int num )
{
int i = num;
while (i > 0 && !m_undoStack.empty())
{
delete m_undoStack.front();
m_undoStack.pop_front();
i--;
}
}
//////////////////////////////////////////////////////////////////////////
void CUndoManager::ClearRedoStack( int num )
{
int i = num;
while (i > 0 && !m_redoStack.empty())
{
delete m_redoStack.back();
m_redoStack.pop_back();
i--;
}
}
//////////////////////////////////////////////////////////////////////////
bool CUndoManager::IsHaveRedo() const
{
return !m_redoStack.empty();
}
//////////////////////////////////////////////////////////////////////////
bool CUndoManager::IsHaveUndo() const
{
return !m_undoStack.empty();
}
//////////////////////////////////////////////////////////////////////////
void CUndoManager::Suspend()
{
m_suspendCount++;
//CLogFile::FormatLine( "<Undo> Suspend %d",m_suspendCount );
}
//////////////////////////////////////////////////////////////////////////
void CUndoManager::Resume()
{
assert( m_suspendCount >= 0 );
m_suspendCount--;
//CLogFile::FormatLine( "<Undo> Resume %d",m_suspendCount );
}
//////////////////////////////////////////////////////////////////////////
void CUndoManager::SuperBegin()
{
//CLogFile::FormatLine( "<Undo> SuperBegin (SuspendCount%d)",m_suspendCount );
if (m_bUndoing || m_bRedoing) // If Undoing or redoing now, ignore this calls.
return;
m_bSuperRecording = true;
m_superUndo = new CSuperUndoStep;
//CLogFile::WriteLine( "<Undo> SuperBegin OK" );
}
//////////////////////////////////////////////////////////////////////////
void CUndoManager::SuperAccept( const CString &name )
{
//CLogFile::WriteLine( "<Undo> SupperAccept" );
if (m_bUndoing || m_bRedoing) // If Undoing or redoing now, ignore this calls.
return;
if (!m_bSuperRecording)
return;
assert( m_superUndo != 0 );
if (m_bRecording)
Accept( name );
if (!m_superUndo->IsEmpty())
{
m_superUndo->SetName(name);
// Keep max undo steps.
if (m_undoStack.size() > gSettings.undoLevels)
{
delete m_undoStack.front();
m_undoStack.pop_front();
}
m_undoStack.push_back( m_superUndo );
}
else
{
// If no any object was recorded, Cancel undo operation.
SuperCancel();
}
CLogFile::FormatLine( "Undo Object Accepted (Undo:%d,Redo:%d)",m_undoStack.size(),m_redoStack.size() );
m_bSuperRecording = false;
m_superUndo = 0;
//CLogFile::WriteLine( "<Undo> SupperAccept OK" );
}
//////////////////////////////////////////////////////////////////////////
void CUndoManager::SuperCancel()
{
//CLogFile::WriteLine( "<Undo> SuperCancel" );
if (m_bUndoing || m_bRedoing) // If Undoing or redoing now, ignore this calls.
return;
if (!m_bSuperRecording)
return;
assert( m_superUndo != 0 );
if (m_bRecording)
Cancel();
Suspend();
//! Undo all changes already made.
m_superUndo->Undo(false); // Undo not by Undo command (no need to store Redo)
Resume();
m_bSuperRecording = false;
delete m_superUndo;
m_superUndo = 0;
//CLogFile::WriteLine( "<Undo> SuperCancel OK" );
}
//////////////////////////////////////////////////////////////////////////
int CUndoManager::GetUndoStackLen() const
{
return m_undoStack.size();
}
//////////////////////////////////////////////////////////////////////////
int CUndoManager::GetRedoStackLen() const
{
return m_redoStack.size();
}
//////////////////////////////////////////////////////////////////////////
void CUndoManager::GetUndoStackNames( std::vector<CString> &undos )
{
undos.resize( m_undoStack.size() );
int i = 0;
for (std::list<CUndoStep*>::iterator it = m_undoStack.begin(); it != m_undoStack.end(); it++)
{
undos[i++] = (*it)->GetName();
}
}
//////////////////////////////////////////////////////////////////////////
void CUndoManager::GetRedoStackNames( std::vector<CString> &redos )
{
redos.resize( m_redoStack.size() );
int i = 0;
for (std::list<CUndoStep*>::iterator it = m_redoStack.begin(); it != m_redoStack.end(); it++)
{
redos[i++] = (*it)->GetName();
}
}
//////////////////////////////////////////////////////////////////////////
int CUndoManager::GetDatabaseSize()
{
int size = 0;
{
for (std::list<CUndoStep*>::iterator it = m_undoStack.begin(); it != m_undoStack.end(); it++)
{
size += (*it)->GetSize();
}
}
{
for (std::list<CUndoStep*>::iterator it = m_redoStack.begin(); it != m_redoStack.end(); it++)
{
size += (*it)->GetSize();
}
}
return size;
}
//////////////////////////////////////////////////////////////////////////
void CUndoManager::Flush()
{
m_bRecording = false;
ClearRedoStack();
ClearUndoStack();
delete m_superUndo;
delete m_currentUndo;
m_superUndo = 0;
m_currentUndo = 0;
}
//////////////////////////////////////////////////////////////////////////
void CUndoManager::SetMaxUndoStep( int steps )
{
gSettings.undoLevels = steps;
};
//////////////////////////////////////////////////////////////////////////
int CUndoManager::GetMaxUndoStep() const
{
return gSettings.undoLevels;
}