//////////////////////////////////////////////////////////////////////////// // // 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 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 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( " Begin SuspendCount=%d",m_suspendCount ); //if (m_bSuperRecording) //CLogFile::FormatLine( " 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( " Begin (already recording)" ); // Not cancel, just combine. return; } // Begin Creates a new undo object. m_currentUndo = new CUndoStep; m_bRecording = true; //CLogFile::WriteLine( " 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( " 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( " 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( " Accept OK" ); } ////////////////////////////////////////////////////////////////////////// void CUndoManager::Cancel() { //CLogFile::WriteLine( " 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( " 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( " RecordUndo Name=%s",obj->GetDescription() ); if (m_bUndoing || m_bRedoing) // If Undoing or redoing now, ignore this calls. { //CLogFile::WriteLine( " 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( " RecordUndo (Not Recording)" ); // Ignore this object. obj->Release(); } } ////////////////////////////////////////////////////////////////////////// void CUndoManager::ClearRedoStack() { for (std::list::iterator it = m_redoStack.begin(); it != m_redoStack.end(); it++) { delete *it; } m_redoStack.clear(); } ////////////////////////////////////////////////////////////////////////// void CUndoManager::ClearUndoStack() { for (std::list::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( " Suspend %d",m_suspendCount ); } ////////////////////////////////////////////////////////////////////////// void CUndoManager::Resume() { assert( m_suspendCount >= 0 ); m_suspendCount--; //CLogFile::FormatLine( " Resume %d",m_suspendCount ); } ////////////////////////////////////////////////////////////////////////// void CUndoManager::SuperBegin() { //CLogFile::FormatLine( " 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( " SuperBegin OK" ); } ////////////////////////////////////////////////////////////////////////// void CUndoManager::SuperAccept( const CString &name ) { //CLogFile::WriteLine( " 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( " SupperAccept OK" ); } ////////////////////////////////////////////////////////////////////////// void CUndoManager::SuperCancel() { //CLogFile::WriteLine( " 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( " SuperCancel OK" ); } ////////////////////////////////////////////////////////////////////////// int CUndoManager::GetUndoStackLen() const { return m_undoStack.size(); } ////////////////////////////////////////////////////////////////////////// int CUndoManager::GetRedoStackLen() const { return m_redoStack.size(); } ////////////////////////////////////////////////////////////////////////// void CUndoManager::GetUndoStackNames( std::vector &undos ) { undos.resize( m_undoStack.size() ); int i = 0; for (std::list::iterator it = m_undoStack.begin(); it != m_undoStack.end(); it++) { undos[i++] = (*it)->GetName(); } } ////////////////////////////////////////////////////////////////////////// void CUndoManager::GetRedoStackNames( std::vector &redos ) { redos.resize( m_redoStack.size() ); int i = 0; for (std::list::iterator it = m_redoStack.begin(); it != m_redoStack.end(); it++) { redos[i++] = (*it)->GetName(); } } ////////////////////////////////////////////////////////////////////////// int CUndoManager::GetDatabaseSize() { int size = 0; { for (std::list::iterator it = m_undoStack.begin(); it != m_undoStack.end(); it++) { size += (*it)->GetSize(); } } { for (std::list::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; }