//----------------------------------------------------------------------------- // 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" //----------------------------------------------------------------------------- // 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() { docsURL; 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::iterator itr = mChildren.end() - 1; for ( ; itr != mChildren.begin() - 1; itr-- ) (*itr)->undo(); } void CompoundUndoAction::redo() { Vector::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 ); } DefineEngineMethod( CompoundUndoAction, addAction, void, (const char * objName), , "addAction( UndoAction )" ) { UndoAction *action; if ( Sim::findObject( objName, action ) ) 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() { docsURL; 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; } DefineEngineMethod(UndoManager, clearAll, void, (),, "Clears the undo manager.") { object->clearAll(); } void UndoManager::clearAll() { clearStack(mUndoStack); clearStack(mRedoStack); Con::executef(this, "onClear"); } //----------------------------------------------------------------------------- void UndoManager::clearStack(Vector &stack) { Vector::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 &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::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(); } DefineEngineMethod(UndoManager, getUndoCount, S32, (),, "") { return object->getUndoCount(); } S32 UndoManager::getUndoCount() { return mUndoStack.size(); } DefineEngineMethod(UndoManager, getUndoName, const char*, (S32 index), , "(index)") { return object->getUndoName(index); } const char* UndoManager::getUndoName(S32 index) { if ((index < getUndoCount()) && (index >= 0)) return mUndoStack[index]->mActionName; return NULL; } DefineEngineMethod(UndoManager, getUndoAction, S32, (S32 index), , "(index)") { UndoAction * action = object->getUndoAction(index); 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; } DefineEngineMethod(UndoManager, getRedoCount, S32, (),, "") { return object->getRedoCount(); } S32 UndoManager::getRedoCount() { return mRedoStack.size(); } DefineEngineMethod(UndoManager, getRedoName, const char*, (S32 index), , "(index)") { return object->getRedoName(index); } const char* UndoManager::getRedoName(S32 index) { if ((index < getRedoCount()) && (index >= 0)) return mRedoStack[getRedoCount() - index - 1]->mActionName; return NULL; } DefineEngineMethod(UndoManager, getRedoAction, S32, (S32 index), , "(index)") { UndoAction * action = object->getRedoAction(index); 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 ); } //----------------------------------------------------------------------------- DefineEngineMethod(UndoAction, addToManager, void, (const char * undoManager), (""), "action.addToManager([undoManager])") { UndoManager *theMan = NULL; if (!String::isEmpty(undoManager)) { SimObject *obj = Sim::findObject(undoManager); if(obj) theMan = dynamic_cast (obj); } object->addToManager(theMan); } //----------------------------------------------------------------------------- DefineEngineMethod( UndoAction, undo, void, (),, "() - Undo action contained in undo." ) { object->undo(); } //----------------------------------------------------------------------------- DefineEngineMethod( UndoAction, redo, void, (),, "() - Reo action contained in undo." ) { object->redo(); } //----------------------------------------------------------------------------- DefineEngineMethod(UndoManager, undo, void, (),, "UndoManager.undo();") { object->undo(); } //----------------------------------------------------------------------------- DefineEngineMethod(UndoManager, redo, void, (),, "UndoManager.redo();") { object->redo(); } //----------------------------------------------------------------------------- DefineEngineMethod(UndoManager, getNextUndoName, const char *, (),, "UndoManager.getNextUndoName();") { const char *name = object->getNextUndoName(); if(!name) return NULL; dsize_t retLen = dStrlen(name) + 1; char *ret = Con::getReturnBuffer(retLen); dStrcpy(ret, name, retLen); return ret; } //----------------------------------------------------------------------------- DefineEngineMethod(UndoManager, getNextRedoName, const char *, (),, "UndoManager.getNextRedoName();") { const char *name = object->getNextRedoName(); if(!name) return NULL; dsize_t retLen = dStrlen(name) + 1; char *ret = Con::getReturnBuffer(retLen); dStrcpy(ret, name, retLen); return ret; } //----------------------------------------------------------------------------- DefineEngineMethod( UndoManager, pushCompound, const char*, ( String name ), (""), "( string name=\"\" ) - Push a CompoundUndoAction onto the compound stack for assembly." ) { CompoundUndoAction* action = object->pushCompound( name ); if( !action ) return ""; if( !action->isProperlyAdded() ) action->registerObject(); return action->getIdString(); } //----------------------------------------------------------------------------- DefineEngineMethod( UndoManager, popCompound, void, ( bool discard ), (false), "( bool discard=false ) - Pop the current CompoundUndoAction off the stack." ) { if( !object->getCompoundStackDepth() ) { Con::errorf( "UndoManager::popCompound - no compound on stack (%s) ",object->getName() ); return; } object->popCompound( discard ); }