//----------------------------------------------------------------------------- // Copyright (c) 2013 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 "undo.h" #include "console/console.h" #include "console/consoleTypes.h" #include "undo_ScriptBinding.h" //----------------------------------------------------------------------------- // UndoAction //----------------------------------------------------------------------------- IMPLEMENT_CONOBJECT(UndoAction); IMPLEMENT_CONOBJECT(UndoScriptAction); UndoAction::UndoAction( const UTF8* actionName) { mActionName = StringTable->insert(actionName); mUndoManager = NULL; } S32 UndoManager::getRedoCount() { return mRedoStack.size(); } // Modified to clean up quiet sub actions [KNM | 08/10/11 | ITGB-152] UndoAction::~UndoAction() { clearAllNotifications(); for (U32 i = 0; i < (U32)mQuietSubActions.size(); i++) delete mQuietSubActions[i]; mQuietSubActions.clear(); } //----------------------------------------------------------------------------- void UndoAction::initPersistFields() { Parent::initPersistFields(); addField("actionName", TypeString, Offset(mActionName, UndoAction), "A brief description of the action, for UI representation of this undo/redo action."); } //----------------------------------------------------------------------------- // Implemented to trickle down into quiet sub actions [KNM | 08/10/11 | ITGB-152] void UndoAction::undo(void) { for (U32 i = 0; i < (U32)mQuietSubActions.size(); i++) mQuietSubActions[i]->undo(); } //----------------------------------------------------------------------------- // Implemented to trickle down into quiet sub actions [KNM | 08/10/11 | ITGB-152] void UndoAction::redo(void) { for (U32 i = 0; i < (U32)mQuietSubActions.size(); i++) mQuietSubActions[i]->redo(); } //----------------------------------------------------------------------------- // Adds a "quiet (hidden from user)" sub action [KNM | 08/10/11 | ITGB-152] void UndoAction::addQuietSubAction(UndoAction * quietSubAction) { if (!quietSubAction) return; mQuietSubActions.push_back(quietSubAction); } //----------------------------------------------------------------------------- void UndoAction::addToManager(UndoManager* theMan) { if(theMan) { mUndoManager = theMan; (*theMan).addAction(this); } else { mUndoManager = &UndoManager::getDefaultManager(); mUndoManager->addAction(this); } } //----------------------------------------------------------------------------- // UndoManager //----------------------------------------------------------------------------- IMPLEMENT_CONOBJECT(UndoManager); UndoManager::UndoManager(U32 levels) { 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); } //----------------------------------------------------------------------------- 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; } void UndoManager::clearAll() { clearStack(mUndoStack); clearStack(mRedoStack); Con::executef(this, 1, "onClear"); } //----------------------------------------------------------------------------- void UndoManager::clearStack(Vector &stack) { Vector::iterator itr = stack.begin(); while (itr != stack.end()) { UndoAction* undo = stack.first(); stack.pop_front(); // Don't delete script created undos. if (dynamic_cast(undo)) undo->deleteObject(); else delete undo; } stack.clear(); } //----------------------------------------------------------------------------- void UndoManager::clampStack(Vector &stack) { while((U32)stack.size() > mNumLevels) { UndoAction *act = stack.front(); stack.pop_front(); UndoScriptAction* scriptAction = dynamic_cast(act); if (scriptAction) scriptAction->deleteObject(); else delete act; } } void UndoManager::removeAction(UndoAction *action) { Vector::iterator itr = mUndoStack.begin(); while (itr != mUndoStack.end()) { if ((*itr) == action) { UndoAction* deleteAction = *itr; mUndoStack.erase(itr); if (!dynamic_cast(deleteAction)) delete deleteAction; Con::executef(this, 1, "onRemoveUndo"); return; } itr++; } itr = mRedoStack.begin(); while (itr != mRedoStack.end()) { if ((*itr) == action) { UndoAction* deleteAction = *itr; mRedoStack.erase(itr); if (!dynamic_cast(deleteAction)) delete deleteAction; Con::executef(this, 1, "onRemoveUndo"); return; } itr++; } } //----------------------------------------------------------------------------- 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((U32)mRedoStack.size() > mNumLevels) mRedoStack.pop_front(); Con::executef(this, 1, "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((U32)mUndoStack.size() > mNumLevels) mUndoStack.pop_front(); Con::executef(this, 1, "onRedo"); // perform the redo, whatever it may be. (*react).redo(); } S32 UndoManager::getUndoCount() { return mUndoStack.size(); } StringTableEntry UndoManager::getUndoName(S32 index) { if ((index < getUndoCount()) && (index >= 0)) return mUndoStack[index]->mActionName; return NULL; } StringTableEntry UndoManager::getRedoName(S32 index) { if ((index < getRedoCount()) && (index >= 0)) return mRedoStack[getRedoCount() - index - 1]->mActionName; return NULL; } //----------------------------------------------------------------------------- StringTableEntry UndoManager::getNextUndoName() { if(mUndoStack.size() < 1) return NULL; UndoAction *act = mUndoStack.last(); return (*act).mActionName; } //----------------------------------------------------------------------------- StringTableEntry UndoManager::getNextRedoName() { if(mRedoStack.size() < 1) return NULL; UndoAction *act = mRedoStack.last(); return (*act).mActionName; } //----------------------------------------------------------------------------- void UndoManager::addAction(UndoAction* action) { // push the incoming action onto the stack, move old data off the end if necessary. mUndoStack.push_back(action); if((U32)mUndoStack.size() > mNumLevels) mUndoStack.pop_front(); Con::executef(this, 1, "onAddUndo"); // clear the redo stack clearStack(mRedoStack); }