Torque3D/Engine/source/util/undo.cpp

594 lines
16 KiB
C++
Raw Permalink Normal View History

2012-09-19 15:15:01 +00:00
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "util/undo.h"
#include "console/console.h"
#include "console/consoleTypes.h"
#include "console/engineAPI.h"
2012-09-19 15:15:01 +00:00
//-----------------------------------------------------------------------------
// UndoAction
//-----------------------------------------------------------------------------
IMPLEMENT_CONOBJECT(UndoAction);
IMPLEMENT_CONOBJECT(UndoScriptAction);
ConsoleDocClass( UndoAction,
"@brief An event which signals the editors to undo the last action\n\n"
"Not intended for game development, for editors or internal use only.\n\n "
"@internal");
ConsoleDocClass( UndoScriptAction,
"@brief Undo actions which can be created as script objects.\n\n"
"Not intended for game development, for editors or internal use only.\n\n "
"@internal");
UndoAction::UndoAction(const UTF8 *actionName)
{
mActionName = actionName;
mUndoManager = NULL;
}
UndoAction::~UndoAction()
{
// If we are registered to an undo manager, make sure
// we get off its lists.
if( mUndoManager )
mUndoManager->removeAction( this, true );
clearAllNotifications();
}
//-----------------------------------------------------------------------------
void UndoAction::initPersistFields()
{
addField("actionName", TypeRealString, Offset(mActionName, UndoAction),
"A brief description of the action, for UI representation of this undo/redo action.");
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
void UndoAction::addToManager(UndoManager* theMan)
{
if(theMan)
{
mUndoManager = theMan;
(*theMan).addAction(this);
}
else
{
mUndoManager = &UndoManager::getDefaultManager();
mUndoManager->addAction(this);
}
}
//-----------------------------------------------------------------------------
// CompoundUndoAction
//-----------------------------------------------------------------------------
IMPLEMENT_CONOBJECT( CompoundUndoAction );
ConsoleDocClass( CompoundUndoAction,
"@brief An undo action that is comprised of other undo actions.\n\n"
"Not intended for game development, for editors or internal use only.\n\n "
"@internal");
CompoundUndoAction::CompoundUndoAction( const UTF8 *actionName )
: Parent( actionName )
{
}
CompoundUndoAction::~CompoundUndoAction()
{
while( !mChildren.empty() )
{
UndoAction* action = mChildren.last();
if( action->isProperlyAdded() )
action->deleteObject();
else
{
clearNotify( action ); // need to clear the delete notification manually in this case
delete action;
}
mChildren.pop_back();
}
}
void CompoundUndoAction::addAction( UndoAction *action )
{
//AssertFatal( action->mUndoManager == NULL, "CompoundUndoAction::addAction, action already had an UndoManager." );
mChildren.push_back( action );
deleteNotify( action );
}
void CompoundUndoAction::undo()
{
Vector<UndoAction*>::iterator itr = mChildren.end() - 1;
for ( ; itr != mChildren.begin() - 1; itr-- )
(*itr)->undo();
}
void CompoundUndoAction::redo()
{
Vector<UndoAction*>::iterator itr = mChildren.begin();
for ( ; itr != mChildren.end(); itr++ )
(*itr)->redo();
}
void CompoundUndoAction::onDeleteNotify( SimObject* object )
{
for( U32 i = 0; i < mChildren.size(); ++ i )
if( mChildren[ i ] == object )
mChildren.erase( i );
Parent::onDeleteNotify( object );
}
2018-04-17 19:01:50 +00:00
DefineEngineMethod( CompoundUndoAction, addAction, void, (const char * objName), , "addAction( UndoAction )" )
2012-09-19 15:15:01 +00:00
{
UndoAction *action;
if ( Sim::findObject( objName, action ) )
2012-09-19 15:15:01 +00:00
object->addAction( action );
}
//-----------------------------------------------------------------------------
// UndoManager
//-----------------------------------------------------------------------------
IMPLEMENT_CONOBJECT(UndoManager);
ConsoleDocClass( UndoManager,
"@brief SimObject which adds, tracks, and deletes UndoAction objects.\n\n"
"Not intended for game development, for editors or internal use only.\n\n "
"@internal")
UndoManager::UndoManager(U32 levels)
{
VECTOR_SET_ASSOCIATION( mUndoStack );
VECTOR_SET_ASSOCIATION( mRedoStack );
VECTOR_SET_ASSOCIATION( mCompoundStack );
mNumLevels = levels;
// levels can be arbitrarily high, so we don't really want to reserve(levels).
mUndoStack.reserve(10);
mRedoStack.reserve(10);
}
//-----------------------------------------------------------------------------
UndoManager::~UndoManager()
{
clearStack(mUndoStack);
clearStack(mRedoStack);
clearStack( *( ( Vector< UndoAction* >* ) &mCompoundStack ) );
}
//-----------------------------------------------------------------------------
void UndoManager::initPersistFields()
{
addField("numLevels", TypeS32, Offset(mNumLevels, UndoManager), "Number of undo & redo levels.");
// arrange for the default undo manager to exist.
// UndoManager &def = getDefaultManager();
// Con::printf("def = %s undo manager created", def.getName());
}
//-----------------------------------------------------------------------------
UndoManager& UndoManager::getDefaultManager()
{
// the default manager is created the first time it is asked for.
static UndoManager *defaultMan = NULL;
if(!defaultMan)
{
defaultMan = new UndoManager();
defaultMan->assignName("DefaultUndoManager");
defaultMan->registerObject();
}
return *defaultMan;
}
2018-04-17 19:01:50 +00:00
DefineEngineMethod(UndoManager, clearAll, void, (),, "Clears the undo manager.")
2012-09-19 15:15:01 +00:00
{
object->clearAll();
}
void UndoManager::clearAll()
{
clearStack(mUndoStack);
clearStack(mRedoStack);
Con::executef(this, "onClear");
}
//-----------------------------------------------------------------------------
void UndoManager::clearStack(Vector<UndoAction*> &stack)
{
Vector<UndoAction*>::iterator itr = stack.begin();
while (itr != stack.end())
{
UndoAction* undo = stack.first();
stack.pop_front();
// Call deleteObject() if the action was registered.
if ( undo->isProperlyAdded() )
undo->deleteObject();
else
delete undo;
}
stack.clear();
}
//-----------------------------------------------------------------------------
void UndoManager::clampStack(Vector<UndoAction*> &stack)
{
while(stack.size() > mNumLevels)
{
UndoAction *act = stack.front();
stack.pop_front();
// Call deleteObject() if the action was registered.
if ( act->isProperlyAdded() )
act->deleteObject();
else
delete act;
}
}
void UndoManager::removeAction(UndoAction *action, bool noDelete)
{
Vector<UndoAction*>::iterator itr = mUndoStack.begin();
while (itr != mUndoStack.end())
{
if ((*itr) == action)
{
UndoAction* deleteAction = *itr;
mUndoStack.erase(itr);
doRemove( deleteAction, noDelete );
return;
}
itr++;
}
itr = mRedoStack.begin();
while (itr != mRedoStack.end())
{
if ((*itr) == action)
{
UndoAction* deleteAction = *itr;
mRedoStack.erase(itr);
doRemove( deleteAction, noDelete );
return;
}
itr++;
}
}
void UndoManager::doRemove( UndoAction* action, bool noDelete )
{
if( action->mUndoManager == this )
action->mUndoManager = NULL;
if( !noDelete )
{
// Call deleteObject() if the action was registered.
if ( action->isProperlyAdded() )
action->deleteObject();
else
delete action;
}
if( isProperlyAdded() )
Con::executef(this, "onRemoveUndo");
}
//-----------------------------------------------------------------------------
void UndoManager::undo()
{
// make sure we have an action available
if(mUndoStack.size() < 1)
return;
// pop the action off the undo stack
UndoAction *act = mUndoStack.last();
mUndoStack.pop_back();
// add it to the redo stack
mRedoStack.push_back(act);
if(mRedoStack.size() > mNumLevels)
mRedoStack.pop_front();
Con::executef(this, "onUndo");
// perform the undo, whatever it may be.
(*act).undo();
}
//-----------------------------------------------------------------------------
void UndoManager::redo()
{
// make sure we have an action available
if(mRedoStack.size() < 1)
return;
// pop the action off the redo stack
UndoAction *react = mRedoStack.last();
mRedoStack.pop_back();
// add it to the undo stack
mUndoStack.push_back(react);
if(mUndoStack.size() > mNumLevels)
mUndoStack.pop_front();
Con::executef(this, "onRedo");
// perform the redo, whatever it may be.
(*react).redo();
}
2018-04-17 19:01:50 +00:00
DefineEngineMethod(UndoManager, getUndoCount, S32, (),, "")
2012-09-19 15:15:01 +00:00
{
return object->getUndoCount();
}
S32 UndoManager::getUndoCount()
{
return mUndoStack.size();
}
2018-04-17 19:01:50 +00:00
DefineEngineMethod(UndoManager, getUndoName, const char*, (S32 index), , "(index)")
2012-09-19 15:15:01 +00:00
{
return object->getUndoName(index);
2012-09-19 15:15:01 +00:00
}
const char* UndoManager::getUndoName(S32 index)
{
if ((index < getUndoCount()) && (index >= 0))
return mUndoStack[index]->mActionName;
return NULL;
}
2018-04-17 19:01:50 +00:00
DefineEngineMethod(UndoManager, getUndoAction, S32, (S32 index), , "(index)")
2012-09-19 15:15:01 +00:00
{
UndoAction * action = object->getUndoAction(index);
2012-09-19 15:15:01 +00:00
if ( !action )
return -1;
if ( !action->isProperlyAdded() )
action->registerObject();
return action->getId();
}
UndoAction* UndoManager::getUndoAction(S32 index)
{
if ((index < getUndoCount()) && (index >= 0))
return mUndoStack[index];
return NULL;
}
2018-04-17 19:01:50 +00:00
DefineEngineMethod(UndoManager, getRedoCount, S32, (),, "")
2012-09-19 15:15:01 +00:00
{
return object->getRedoCount();
}
S32 UndoManager::getRedoCount()
{
return mRedoStack.size();
}
2018-04-17 19:01:50 +00:00
DefineEngineMethod(UndoManager, getRedoName, const char*, (S32 index), , "(index)")
2012-09-19 15:15:01 +00:00
{
return object->getRedoName(index);
2012-09-19 15:15:01 +00:00
}
const char* UndoManager::getRedoName(S32 index)
{
if ((index < getRedoCount()) && (index >= 0))
return mRedoStack[getRedoCount() - index - 1]->mActionName;
return NULL;
}
2018-04-17 19:01:50 +00:00
DefineEngineMethod(UndoManager, getRedoAction, S32, (S32 index), , "(index)")
2012-09-19 15:15:01 +00:00
{
UndoAction * action = object->getRedoAction(index);
2012-09-19 15:15:01 +00:00
if ( !action )
return -1;
if ( !action->isProperlyAdded() )
action->registerObject();
return action->getId();
}
UndoAction* UndoManager::getRedoAction(S32 index)
{
if ((index < getRedoCount()) && (index >= 0))
return mRedoStack[index];
return NULL;
}
//-----------------------------------------------------------------------------
const char* UndoManager::getNextUndoName()
{
if(mUndoStack.size() < 1)
return NULL;
UndoAction *act = mUndoStack.last();
return (*act).mActionName;
}
//-----------------------------------------------------------------------------
const char* UndoManager::getNextRedoName()
{
if(mRedoStack.size() < 1)
return NULL;
UndoAction *act = mRedoStack.last();
return (*act).mActionName;
}
//-----------------------------------------------------------------------------
void UndoManager::addAction(UndoAction* action)
{
// If we are assembling a compound, redirect the action to it
// and don't modify our current undo/redo state.
if( mCompoundStack.size() )
{
mCompoundStack.last()->addAction( action );
return;
}
// clear the redo stack
clearStack(mRedoStack);
// push the incoming action onto the stack, move old data off the end if necessary.
mUndoStack.push_back(action);
if(mUndoStack.size() > mNumLevels)
mUndoStack.pop_front();
Con::executef(this, "onAddUndo");
}
//-----------------------------------------------------------------------------
CompoundUndoAction* UndoManager::pushCompound( const String& name )
{
mCompoundStack.push_back( new CompoundUndoAction( name ) );
return mCompoundStack.last();
}
//-----------------------------------------------------------------------------
void UndoManager::popCompound( bool discard )
{
AssertFatal( getCompoundStackDepth() > 0, "UndoManager::popCompound - no compound on stack!" );
CompoundUndoAction* undo = mCompoundStack.last();
mCompoundStack.pop_back();
if( discard || undo->getNumChildren() == 0 )
{
if( undo->isProperlyAdded() )
undo->deleteObject();
else
delete undo;
}
else
addAction( undo );
}
//-----------------------------------------------------------------------------
2018-04-17 19:01:50 +00:00
DefineEngineMethod(UndoAction, addToManager, void, (const char * undoManager), (""), "action.addToManager([undoManager])")
2012-09-19 15:15:01 +00:00
{
UndoManager *theMan = NULL;
if (!String::isEmpty(undoManager))
2012-09-19 15:15:01 +00:00
{
SimObject *obj = Sim::findObject(undoManager);
2012-09-19 15:15:01 +00:00
if(obj)
theMan = dynamic_cast<UndoManager*> (obj);
}
object->addToManager(theMan);
}
//-----------------------------------------------------------------------------
2018-04-17 19:01:50 +00:00
DefineEngineMethod( UndoAction, undo, void, (),, "() - Undo action contained in undo." )
2012-09-19 15:15:01 +00:00
{
object->undo();
}
//-----------------------------------------------------------------------------
2018-04-17 19:01:50 +00:00
DefineEngineMethod( UndoAction, redo, void, (),, "() - Reo action contained in undo." )
2012-09-19 15:15:01 +00:00
{
object->redo();
}
//-----------------------------------------------------------------------------
2018-04-17 19:01:50 +00:00
DefineEngineMethod(UndoManager, undo, void, (),, "UndoManager.undo();")
2012-09-19 15:15:01 +00:00
{
object->undo();
}
//-----------------------------------------------------------------------------
2018-04-17 19:01:50 +00:00
DefineEngineMethod(UndoManager, redo, void, (),, "UndoManager.redo();")
2012-09-19 15:15:01 +00:00
{
object->redo();
}
//-----------------------------------------------------------------------------
2018-04-17 19:01:50 +00:00
DefineEngineMethod(UndoManager, getNextUndoName, const char *, (),, "UndoManager.getNextUndoName();")
2012-09-19 15:15:01 +00:00
{
const char *name = object->getNextUndoName();
if(!name)
return NULL;
dsize_t retLen = dStrlen(name) + 1;
char *ret = Con::getReturnBuffer(retLen);
dStrcpy(ret, name, retLen);
2012-09-19 15:15:01 +00:00
return ret;
}
//-----------------------------------------------------------------------------
2018-04-17 19:01:50 +00:00
DefineEngineMethod(UndoManager, getNextRedoName, const char *, (),, "UndoManager.getNextRedoName();")
2012-09-19 15:15:01 +00:00
{
const char *name = object->getNextRedoName();
if(!name)
return NULL;
dsize_t retLen = dStrlen(name) + 1;
char *ret = Con::getReturnBuffer(retLen);
dStrcpy(ret, name, retLen);
2012-09-19 15:15:01 +00:00
return ret;
}
//-----------------------------------------------------------------------------
2018-04-17 19:01:50 +00:00
DefineEngineMethod( UndoManager, pushCompound, const char*, ( String name ), ("\"\""), "( string name=\"\" ) - Push a CompoundUndoAction onto the compound stack for assembly." )
2012-09-19 15:15:01 +00:00
{
CompoundUndoAction* action = object->pushCompound( name );
if( !action )
return "";
if( !action->isProperlyAdded() )
action->registerObject();
return action->getIdString();
}
//-----------------------------------------------------------------------------
2018-04-17 19:01:50 +00:00
DefineEngineMethod( UndoManager, popCompound, void, ( bool discard ), (false), "( bool discard=false ) - Pop the current CompoundUndoAction off the stack." )
2012-09-19 15:15:01 +00:00
{
if( !object->getCompoundStackDepth() )
{
Con::errorf( "UndoManager::popCompound - no compound on stack (%s) ",object->getName() );
2012-09-19 15:15:01 +00:00
return;
}
object->popCompound( discard );
}