Public Functions
ConsoleDocClass(CompoundUndoAction , "@brief An undo action that is comprised of other undo <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">actions.\n\n</a>" "Not intended <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> game development, <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> editors or internal use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n\n</a> " " @internal" )
ConsoleDocClass(UndoAction , "@brief An event which signals the editors <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> undo the last <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">action\n\n</a>" "Not intended <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> game development, <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> editors or internal use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n\n</a> " " @internal" )
ConsoleDocClass(UndoManager , "@brief <a href="/coding/class/classsimobject/">SimObject</a> which adds, tracks , and deletes <a href="/coding/class/classundoaction/">UndoAction</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">objects.\n\n</a>" "Not intended <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> game development, <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> editors or internal use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n\n</a> " " @internal" )
ConsoleDocClass(UndoScriptAction , "@brief Undo actions which can be created as script <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">objects.\n\n</a>" "Not intended <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> game development, <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> editors or internal use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n\n</a> " " @internal" )
DefineEngineMethod(CompoundUndoAction , addAction , void , (const char *objName) , "addAction( <a href="/coding/class/classundoaction/">UndoAction</a> )" )
DefineEngineMethod(UndoAction , addToManager , void , (const char *undoManager) , ("") , "action.addToManager([undoManager])" )
DefineEngineMethod(UndoAction , redo , void , () , "() - Reo action contained in undo." )
DefineEngineMethod(UndoAction , undo , void , () , "() - Undo action contained in undo." )
DefineEngineMethod(UndoManager , clearAll , void , () , "Clears the undo manager." )
DefineEngineMethod(UndoManager , getNextRedoName , const char * , () , "UndoManager.getNextRedoName();" )
DefineEngineMethod(UndoManager , getNextUndoName , const char * , () , "UndoManager.getNextUndoName();" )
DefineEngineMethod(UndoManager , getRedoAction , S32 , (S32 index) , "(index)" )
DefineEngineMethod(UndoManager , getRedoCount , S32 , () , "" )
DefineEngineMethod(UndoManager , getRedoName , const char * , (S32 index) , "(index)" )
DefineEngineMethod(UndoManager , getUndoAction , S32 , (S32 index) , "(index)" )
DefineEngineMethod(UndoManager , getUndoCount , S32 , () , "" )
DefineEngineMethod(UndoManager , getUndoName , const char * , (S32 index) , "(index)" )
DefineEngineMethod(UndoManager , popCompound , void , (bool discard) , (false) , "( bool discard=false ) - Pop the current <a href="/coding/class/classcompoundundoaction/">CompoundUndoAction</a> off the stack." )
DefineEngineMethod(UndoManager , pushCompound , const char * , (String name) , ("") , "( string name=\"\" ) - Push <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/class/classcompoundundoaction/">CompoundUndoAction</a> onto the compound stack <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> assembly." )
DefineEngineMethod(UndoManager , redo , void , () , "UndoManager.redo();" )
DefineEngineMethod(UndoManager , undo , void , () , "UndoManager.undo();" )
IMPLEMENT_CONOBJECT(CompoundUndoAction )
IMPLEMENT_CONOBJECT(UndoAction )
IMPLEMENT_CONOBJECT(UndoManager )
IMPLEMENT_CONOBJECT(UndoScriptAction )
1
2//-----------------------------------------------------------------------------
3// Copyright (c) 2012 GarageGames, LLC
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to
7// deal in the Software without restriction, including without limitation the
8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9// sell copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in
13// all copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21// IN THE SOFTWARE.
22//-----------------------------------------------------------------------------
23
24#include "platform/platform.h"
25#include "util/undo.h"
26
27#include "console/console.h"
28#include "console/consoleTypes.h"
29#include "console/engineAPI.h"
30
31//-----------------------------------------------------------------------------
32// UndoAction
33//-----------------------------------------------------------------------------
34IMPLEMENT_CONOBJECT(UndoAction);
35IMPLEMENT_CONOBJECT(UndoScriptAction);
36
37ConsoleDocClass( UndoAction,
38 "@brief An event which signals the editors to undo the last action\n\n"
39 "Not intended for game development, for editors or internal use only.\n\n "
40 "@internal");
41
42ConsoleDocClass( UndoScriptAction,
43 "@brief Undo actions which can be created as script objects.\n\n"
44 "Not intended for game development, for editors or internal use only.\n\n "
45 "@internal");
46
47UndoAction::UndoAction(const UTF8 *actionName)
48{
49 mActionName = actionName;
50 mUndoManager = NULL;
51}
52
53UndoAction::~UndoAction()
54{
55 // If we are registered to an undo manager, make sure
56 // we get off its lists.
57 if( mUndoManager )
58 mUndoManager->removeAction( this, true );
59
60 clearAllNotifications();
61}
62
63//-----------------------------------------------------------------------------
64void UndoAction::initPersistFields()
65{
66 addField("actionName", TypeRealString, Offset(mActionName, UndoAction),
67 "A brief description of the action, for UI representation of this undo/redo action.");
68
69 Parent::initPersistFields();
70}
71
72//-----------------------------------------------------------------------------
73void UndoAction::addToManager(UndoManager* theMan)
74{
75 if(theMan)
76 {
77 mUndoManager = theMan;
78 (*theMan).addAction(this);
79 }
80 else
81 {
82 mUndoManager = &UndoManager::getDefaultManager();
83 mUndoManager->addAction(this);
84 }
85}
86
87//-----------------------------------------------------------------------------
88// CompoundUndoAction
89//-----------------------------------------------------------------------------
90IMPLEMENT_CONOBJECT( CompoundUndoAction );
91
92ConsoleDocClass( CompoundUndoAction,
93 "@brief An undo action that is comprised of other undo actions.\n\n"
94 "Not intended for game development, for editors or internal use only.\n\n "
95 "@internal");
96
97CompoundUndoAction::CompoundUndoAction( const UTF8 *actionName )
98 : Parent( actionName )
99{
100}
101
102CompoundUndoAction::~CompoundUndoAction()
103{
104 while( !mChildren.empty() )
105 {
106 UndoAction* action = mChildren.last();
107 if( action->isProperlyAdded() )
108 action->deleteObject();
109 else
110 {
111 clearNotify( action ); // need to clear the delete notification manually in this case
112 delete action;
113 }
114
115 mChildren.pop_back();
116 }
117}
118
119void CompoundUndoAction::addAction( UndoAction *action )
120{
121 //AssertFatal( action->mUndoManager == NULL, "CompoundUndoAction::addAction, action already had an UndoManager." );
122 mChildren.push_back( action );
123 deleteNotify( action );
124}
125
126void CompoundUndoAction::undo()
127{
128 Vector<UndoAction*>::iterator itr = mChildren.end() - 1;
129 for ( ; itr != mChildren.begin() - 1; itr-- )
130 (*itr)->undo();
131}
132
133void CompoundUndoAction::redo()
134{
135 Vector<UndoAction*>::iterator itr = mChildren.begin();
136 for ( ; itr != mChildren.end(); itr++ )
137 (*itr)->redo();
138}
139
140void CompoundUndoAction::onDeleteNotify( SimObject* object )
141{
142 for( U32 i = 0; i < mChildren.size(); ++ i )
143 if( mChildren[ i ] == object )
144 mChildren.erase( i );
145
146 Parent::onDeleteNotify( object );
147}
148
149DefineEngineMethod( CompoundUndoAction, addAction, void, (const char * objName), , "addAction( UndoAction )" )
150{
151 UndoAction *action;
152 if ( Sim::findObject( objName, action ) )
153 object->addAction( action );
154}
155
156//-----------------------------------------------------------------------------
157// UndoManager
158//-----------------------------------------------------------------------------
159IMPLEMENT_CONOBJECT(UndoManager);
160
161ConsoleDocClass( UndoManager,
162 "@brief SimObject which adds, tracks, and deletes UndoAction objects.\n\n"
163 "Not intended for game development, for editors or internal use only.\n\n "
164 "@internal")
165
166UndoManager::UndoManager(U32 levels)
167{
168 VECTOR_SET_ASSOCIATION( mUndoStack );
169 VECTOR_SET_ASSOCIATION( mRedoStack );
170 VECTOR_SET_ASSOCIATION( mCompoundStack );
171
172 mNumLevels = levels;
173 // levels can be arbitrarily high, so we don't really want to reserve(levels).
174 mUndoStack.reserve(10);
175 mRedoStack.reserve(10);
176}
177
178//-----------------------------------------------------------------------------
179UndoManager::~UndoManager()
180{
181 clearStack(mUndoStack);
182 clearStack(mRedoStack);
183 clearStack( *( ( Vector< UndoAction*>* ) &mCompoundStack ) );
184}
185
186//-----------------------------------------------------------------------------
187void UndoManager::initPersistFields()
188{
189 addField("numLevels", TypeS32, Offset(mNumLevels, UndoManager), "Number of undo & redo levels.");
190 // arrange for the default undo manager to exist.
191// UndoManager &def = getDefaultManager();
192// Con::printf("def = %s undo manager created", def.getName());
193
194}
195
196//-----------------------------------------------------------------------------
197UndoManager& UndoManager::getDefaultManager()
198{
199 // the default manager is created the first time it is asked for.
200 static UndoManager *defaultMan = NULL;
201 if(!defaultMan)
202 {
203 defaultMan = new UndoManager();
204 defaultMan->assignName("DefaultUndoManager");
205 defaultMan->registerObject();
206 }
207 return *defaultMan;
208}
209
210DefineEngineMethod(UndoManager, clearAll, void, (),, "Clears the undo manager.")
211{
212 object->clearAll();
213}
214
215void UndoManager::clearAll()
216{
217 clearStack(mUndoStack);
218 clearStack(mRedoStack);
219
220 Con::executef(this, "onClear");
221}
222
223//-----------------------------------------------------------------------------
224void UndoManager::clearStack(Vector<UndoAction*> &stack)
225{
226 Vector<UndoAction*>::iterator itr = stack.begin();
227 while (itr != stack.end())
228 {
229 UndoAction* undo = stack.first();
230 stack.pop_front();
231
232 // Call deleteObject() if the action was registered.
233 if ( undo->isProperlyAdded() )
234 undo->deleteObject();
235 else
236 delete undo;
237 }
238 stack.clear();
239}
240
241//-----------------------------------------------------------------------------
242void UndoManager::clampStack(Vector<UndoAction*> &stack)
243{
244 while(stack.size() > mNumLevels)
245 {
246 UndoAction *act = stack.front();
247 stack.pop_front();
248
249 // Call deleteObject() if the action was registered.
250 if ( act->isProperlyAdded() )
251 act->deleteObject();
252 else
253 delete act;
254 }
255}
256
257void UndoManager::removeAction(UndoAction *action, bool noDelete)
258{
259 Vector<UndoAction*>::iterator itr = mUndoStack.begin();
260 while (itr != mUndoStack.end())
261 {
262 if ((*itr) == action)
263 {
264 UndoAction* deleteAction = *itr;
265 mUndoStack.erase(itr);
266 doRemove( deleteAction, noDelete );
267 return;
268 }
269 itr++;
270 }
271
272 itr = mRedoStack.begin();
273 while (itr != mRedoStack.end())
274 {
275 if ((*itr) == action)
276 {
277 UndoAction* deleteAction = *itr;
278 mRedoStack.erase(itr);
279 doRemove( deleteAction, noDelete );
280 return;
281 }
282 itr++;
283 }
284}
285
286void UndoManager::doRemove( UndoAction* action, bool noDelete )
287{
288 if( action->mUndoManager == this )
289 action->mUndoManager = NULL;
290
291 if( !noDelete )
292 {
293 // Call deleteObject() if the action was registered.
294 if ( action->isProperlyAdded() )
295 action->deleteObject();
296 else
297 delete action;
298 }
299
300 if( isProperlyAdded() )
301 Con::executef(this, "onRemoveUndo");
302}
303
304//-----------------------------------------------------------------------------
305void UndoManager::undo()
306{
307 // make sure we have an action available
308 if(mUndoStack.size() < 1)
309 return;
310
311 // pop the action off the undo stack
312 UndoAction *act = mUndoStack.last();
313 mUndoStack.pop_back();
314
315 // add it to the redo stack
316 mRedoStack.push_back(act);
317 if(mRedoStack.size() > mNumLevels)
318 mRedoStack.pop_front();
319
320 Con::executef(this, "onUndo");
321
322 // perform the undo, whatever it may be.
323 (*act).undo();
324}
325
326//-----------------------------------------------------------------------------
327void UndoManager::redo()
328{
329 // make sure we have an action available
330 if(mRedoStack.size() < 1)
331 return;
332
333 // pop the action off the redo stack
334 UndoAction *react = mRedoStack.last();
335 mRedoStack.pop_back();
336
337 // add it to the undo stack
338 mUndoStack.push_back(react);
339 if(mUndoStack.size() > mNumLevels)
340 mUndoStack.pop_front();
341
342 Con::executef(this, "onRedo");
343
344 // perform the redo, whatever it may be.
345 (*react).redo();
346}
347
348DefineEngineMethod(UndoManager, getUndoCount, S32, (),, "")
349{
350 return object->getUndoCount();
351}
352
353S32 UndoManager::getUndoCount()
354{
355 return mUndoStack.size();
356}
357
358DefineEngineMethod(UndoManager, getUndoName, const char*, (S32 index), , "(index)")
359{
360 return object->getUndoName(index);
361}
362
363const char* UndoManager::getUndoName(S32 index)
364{
365 if ((index < getUndoCount()) && (index >= 0))
366 return mUndoStack[index]->mActionName;
367
368 return NULL;
369}
370
371DefineEngineMethod(UndoManager, getUndoAction, S32, (S32 index), , "(index)")
372{
373 UndoAction * action = object->getUndoAction(index);
374 if ( !action )
375 return -1;
376
377 if ( !action->isProperlyAdded() )
378 action->registerObject();
379
380 return action->getId();
381}
382
383UndoAction* UndoManager::getUndoAction(S32 index)
384{
385 if ((index < getUndoCount()) && (index >= 0))
386 return mUndoStack[index];
387 return NULL;
388}
389
390DefineEngineMethod(UndoManager, getRedoCount, S32, (),, "")
391{
392 return object->getRedoCount();
393}
394
395S32 UndoManager::getRedoCount()
396{
397 return mRedoStack.size();
398}
399
400DefineEngineMethod(UndoManager, getRedoName, const char*, (S32 index), , "(index)")
401{
402 return object->getRedoName(index);
403}
404
405const char* UndoManager::getRedoName(S32 index)
406{
407 if ((index < getRedoCount()) && (index >= 0))
408 return mRedoStack[getRedoCount() - index - 1]->mActionName;
409
410 return NULL;
411}
412
413DefineEngineMethod(UndoManager, getRedoAction, S32, (S32 index), , "(index)")
414{
415 UndoAction * action = object->getRedoAction(index);
416
417 if ( !action )
418 return -1;
419
420 if ( !action->isProperlyAdded() )
421 action->registerObject();
422
423 return action->getId();
424}
425
426UndoAction* UndoManager::getRedoAction(S32 index)
427{
428 if ((index < getRedoCount()) && (index >= 0))
429 return mRedoStack[index];
430 return NULL;
431}
432
433//-----------------------------------------------------------------------------
434const char* UndoManager::getNextUndoName()
435{
436 if(mUndoStack.size() < 1)
437 return NULL;
438
439 UndoAction *act = mUndoStack.last();
440 return (*act).mActionName;
441}
442
443//-----------------------------------------------------------------------------
444const char* UndoManager::getNextRedoName()
445{
446 if(mRedoStack.size() < 1)
447 return NULL;
448
449 UndoAction *act = mRedoStack.last();
450 return (*act).mActionName;
451}
452
453//-----------------------------------------------------------------------------
454void UndoManager::addAction(UndoAction* action)
455{
456 // If we are assembling a compound, redirect the action to it
457 // and don't modify our current undo/redo state.
458
459 if( mCompoundStack.size() )
460 {
461 mCompoundStack.last()->addAction( action );
462 return;
463 }
464
465 // clear the redo stack
466 clearStack(mRedoStack);
467
468 // push the incoming action onto the stack, move old data off the end if necessary.
469 mUndoStack.push_back(action);
470 if(mUndoStack.size() > mNumLevels)
471 mUndoStack.pop_front();
472
473 Con::executef(this, "onAddUndo");
474}
475
476//-----------------------------------------------------------------------------
477
478CompoundUndoAction* UndoManager::pushCompound( const String& name )
479{
480 mCompoundStack.push_back( new CompoundUndoAction( name ) );
481 return mCompoundStack.last();
482}
483
484//-----------------------------------------------------------------------------
485
486void UndoManager::popCompound( bool discard )
487{
488 AssertFatal( getCompoundStackDepth() > 0, "UndoManager::popCompound - no compound on stack!" );
489
490 CompoundUndoAction* undo = mCompoundStack.last();
491 mCompoundStack.pop_back();
492
493 if( discard || undo->getNumChildren() == 0 )
494 {
495 if( undo->isProperlyAdded() )
496 undo->deleteObject();
497 else
498 delete undo;
499 }
500 else
501 addAction( undo );
502}
503
504//-----------------------------------------------------------------------------
505DefineEngineMethod(UndoAction, addToManager, void, (const char * undoManager), (""), "action.addToManager([undoManager])")
506{
507 UndoManager *theMan = NULL;
508 if (!String::isEmpty(undoManager))
509 {
510 SimObject *obj = Sim::findObject(undoManager);
511 if(obj)
512 theMan = dynamic_cast<UndoManager*> (obj);
513 }
514 object->addToManager(theMan);
515}
516
517//-----------------------------------------------------------------------------
518
519DefineEngineMethod( UndoAction, undo, void, (),, "() - Undo action contained in undo." )
520{
521 object->undo();
522}
523
524//-----------------------------------------------------------------------------
525
526DefineEngineMethod( UndoAction, redo, void, (),, "() - Reo action contained in undo." )
527{
528 object->redo();
529}
530
531//-----------------------------------------------------------------------------
532DefineEngineMethod(UndoManager, undo, void, (),, "UndoManager.undo();")
533{
534 object->undo();
535}
536
537//-----------------------------------------------------------------------------
538DefineEngineMethod(UndoManager, redo, void, (),, "UndoManager.redo();")
539{
540 object->redo();
541}
542
543//-----------------------------------------------------------------------------
544DefineEngineMethod(UndoManager, getNextUndoName, const char *, (),, "UndoManager.getNextUndoName();")
545{
546 const char *name = object->getNextUndoName();
547 if(!name)
548 return NULL;
549 dsize_t retLen = dStrlen(name) + 1;
550 char *ret = Con::getReturnBuffer(retLen);
551 dStrcpy(ret, name, retLen);
552 return ret;
553}
554
555//-----------------------------------------------------------------------------
556DefineEngineMethod(UndoManager, getNextRedoName, const char *, (),, "UndoManager.getNextRedoName();")
557{
558 const char *name = object->getNextRedoName();
559 if(!name)
560 return NULL;
561 dsize_t retLen = dStrlen(name) + 1;
562 char *ret = Con::getReturnBuffer(retLen);
563 dStrcpy(ret, name, retLen);
564 return ret;
565}
566
567//-----------------------------------------------------------------------------
568
569DefineEngineMethod( UndoManager, pushCompound, const char*, ( String name ), (""), "( string name=\"\" ) - Push a CompoundUndoAction onto the compound stack for assembly." )
570{
571
572 CompoundUndoAction* action = object->pushCompound( name );
573 if( !action )
574 return "";
575
576 if( !action->isProperlyAdded() )
577 action->registerObject();
578
579 return action->getIdString();
580}
581
582//-----------------------------------------------------------------------------
583
584DefineEngineMethod( UndoManager, popCompound, void, ( bool discard ), (false), "( bool discard=false ) - Pop the current CompoundUndoAction off the stack." )
585{
586 if( !object->getCompoundStackDepth() )
587 {
588 Con::errorf( "UndoManager::popCompound - no compound on stack (%s) ",object->getName() );
589 return;
590 }
591
592
593 object->popCompound( discard );
594}
595