Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

444 lines
9.1 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "undomanager.h"
  7. #include "datamodel.h"
  8. extern CDataModel *g_pDataModelImp;
  9. CUtlSymbolTableMT CUndoManager::s_UndoSymbolTable;
  10. CUndoManager::CUndoManager( ) :
  11. m_bEnabled( true ),
  12. m_bDiscarded( false ),
  13. m_nMaxUndoDepth( 4096 ),
  14. m_nNesting( 0 ),
  15. m_nNotifyNesting( 0 ),
  16. m_bStreamStart( false ),
  17. m_bTrace( false ),
  18. m_bSuppressingNotify( false ),
  19. m_nItemsAddedSinceStartOfStream( 0 ),
  20. m_nNotifySource( 0 ),
  21. m_nNotifyFlags( 0 ),
  22. m_nChainingID( 0 ),
  23. m_PreviousChainingID( 0 )
  24. {
  25. }
  26. CUndoManager::~CUndoManager()
  27. {
  28. }
  29. void CUndoManager::Shutdown()
  30. {
  31. WipeUndo();
  32. WipeRedo();
  33. }
  34. bool CUndoManager::InstallNotificationCallback( IDmNotify *pNotify )
  35. {
  36. if ( m_Notifiers.Find( pNotify ) >= 0 )
  37. return false;
  38. m_Notifiers.AddToTail( pNotify );
  39. return true;
  40. }
  41. void CUndoManager::RemoveNotificationCallback( IDmNotify *pNotify )
  42. {
  43. m_Notifiers.FindAndRemove( pNotify );
  44. }
  45. bool CUndoManager::IsSuppressingNotify( ) const
  46. {
  47. return m_bSuppressingNotify;
  48. }
  49. void CUndoManager::SetSuppressingNotify( bool bSuppress )
  50. {
  51. m_bSuppressingNotify = bSuppress;
  52. }
  53. void CUndoManager::Trace( const char *fmt, ... )
  54. {
  55. if ( !m_bTrace )
  56. return;
  57. char str[ 2048 ];
  58. va_list argptr;
  59. va_start( argptr, fmt );
  60. _vsnprintf( str, sizeof( str ) - 1, fmt, argptr );
  61. va_end( argptr );
  62. str[ sizeof( str ) - 1 ] = 0;
  63. char spaces[ 128 ];
  64. Q_memset( spaces, 0, sizeof( spaces ) );
  65. for ( int i = 0; i < ( m_nNesting * 3 ); ++i )
  66. {
  67. if ( i >= 127 )
  68. break;
  69. spaces[ i ] = ' ';
  70. }
  71. Msg( "%s%s", spaces, str );
  72. }
  73. void CUndoManager::SetUndoDepth( int nMaxUndoDepth )
  74. {
  75. Assert( !HasUndoData() );
  76. m_nMaxUndoDepth = nMaxUndoDepth;
  77. }
  78. void CUndoManager::EnableUndo()
  79. {
  80. m_bEnabled = true;
  81. }
  82. void CUndoManager::DisableUndo()
  83. {
  84. m_bEnabled = false;
  85. }
  86. bool CUndoManager::HasUndoData() const
  87. {
  88. return m_UndoList.Count() != 0;
  89. }
  90. bool CUndoManager::UndoDataDiscarded() const
  91. {
  92. return m_bDiscarded;
  93. }
  94. bool CUndoManager::HasRedoData() const
  95. {
  96. return m_RedoStack.Count() > 0;
  97. }
  98. void CUndoManager::PushNotificationScope( const char *pReason, int nNotifySource, int nNotifyFlags )
  99. {
  100. if ( m_nNotifyNesting++ == 0 )
  101. {
  102. m_pNotifyReason = pReason;
  103. m_nNotifySource = nNotifySource;
  104. m_nNotifyFlags = nNotifyFlags;
  105. }
  106. }
  107. void CUndoManager::PopNotificationScope( bool bAbort )
  108. {
  109. --m_nNotifyNesting;
  110. Assert( m_nNotifyNesting >= 0 );
  111. if ( m_nNotifyNesting == 0 )
  112. {
  113. if ( !m_bSuppressingNotify && ( ( m_nNotifyFlags & NOTIFY_CHANGE_MASK ) != 0 ) )
  114. {
  115. int nNotifyCount = m_Notifiers.Count();
  116. for( int i = 0; i < nNotifyCount; ++i )
  117. {
  118. m_Notifiers[i]->NotifyDataChanged( m_pNotifyReason, m_nNotifySource, m_nNotifyFlags );
  119. }
  120. }
  121. m_nNotifySource = 0;
  122. m_nNotifyFlags = 0;
  123. }
  124. }
  125. void CUndoManager::PushUndo( const char *udesc, const char *rdesc, int nChainingID )
  126. {
  127. if ( !IsEnabled() )
  128. return;
  129. Trace( "[%d] Pushing undo '%s'\n", m_nNesting + 1, udesc );
  130. if ( m_nNesting++ == 0 )
  131. {
  132. m_PreviousChainingID = m_nChainingID;
  133. m_nChainingID = nChainingID;
  134. m_UndoDesc = s_UndoSymbolTable.AddString( udesc );
  135. m_RedoDesc = ( udesc == rdesc ) ? m_UndoDesc : s_UndoSymbolTable.AddString( rdesc );
  136. m_bStreamStart = true;
  137. m_nItemsAddedSinceStartOfStream = 0;
  138. }
  139. }
  140. void CUndoManager::PushRedo()
  141. {
  142. if ( !IsEnabled() )
  143. return;
  144. Trace( "[%d] Popping undo '%s'\n", m_nNesting, s_UndoSymbolTable.String( m_UndoDesc ) );
  145. --m_nNesting;
  146. Assert( m_nNesting >= 0 );
  147. if ( m_nNesting == 0 )
  148. {
  149. if ( m_nItemsAddedSinceStartOfStream > 0 )
  150. {
  151. WipeRedo();
  152. // Accumulate this operation into the previous "undo" operation if there is one
  153. if ( m_nChainingID != 0 &&
  154. m_PreviousChainingID == m_nChainingID )
  155. {
  156. // Walk undo list backward looking for previous end of stream and unmark that indicator
  157. int i = m_UndoList.Tail();
  158. while ( i != m_UndoList.InvalidIndex() )
  159. {
  160. IUndoElement *e = m_UndoList[ i ];
  161. if ( e && e->IsEndOfStream() )
  162. {
  163. e->SetEndOfStream( false );
  164. break;
  165. }
  166. i = m_UndoList.Previous( i );
  167. }
  168. }
  169. }
  170. m_nItemsAddedSinceStartOfStream = 0;
  171. }
  172. }
  173. void CUndoManager::AbortUndoableOperation()
  174. {
  175. if ( !IsEnabled() )
  176. return;
  177. bool hasItems = m_nItemsAddedSinceStartOfStream > 0 ? true : false;
  178. Trace( "[%d] Aborting undo '%s'\n", m_nNesting, s_UndoSymbolTable.String( m_UndoDesc ) );
  179. // Close off context
  180. PushRedo();
  181. if ( m_nNesting == 0 && hasItems )
  182. {
  183. Undo();
  184. WipeRedo();
  185. }
  186. }
  187. void CUndoManager::WipeUndo()
  188. {
  189. CDisableUndoScopeGuard sg;
  190. FOR_EACH_LL( m_UndoList, elem )
  191. {
  192. Trace( "WipeUndo '%s'\n", m_UndoList[ elem ]->GetDesc() );
  193. m_UndoList[ elem ]->Release();
  194. }
  195. m_UndoList.RemoveAll();
  196. m_PreviousChainingID = 0;
  197. }
  198. void CUndoManager::WipeRedo()
  199. {
  200. int c = m_RedoStack.Count();
  201. if ( c == 0 )
  202. return;
  203. CUtlVector< DmElementHandle_t > handles;
  204. g_pDataModelImp->GetInvalidHandles( handles );
  205. g_pDataModelImp->MarkHandlesValid( handles );
  206. CDisableUndoScopeGuard sg;
  207. for ( int i = 0; i < c ; ++i )
  208. {
  209. IUndoElement *elem;
  210. elem = m_RedoStack[ i ];
  211. Trace( "WipeRedo '%s'\n", elem->GetDesc() );
  212. elem->Release();
  213. }
  214. m_RedoStack.Clear();
  215. g_pDataModelImp->MarkHandlesInvalid( handles );
  216. }
  217. void CUndoManager::AddUndoElement( IUndoElement *pElement )
  218. {
  219. Assert( IsEnabled() );
  220. if ( !pElement )
  221. return;
  222. ++m_nItemsAddedSinceStartOfStream;
  223. WipeRedo();
  224. /*
  225. // For later
  226. if ( m_UndoList.Count() >= m_nMaxUndos )
  227. {
  228. m_bDiscarded = true;
  229. }
  230. */
  231. Trace( "AddUndoElement '%s'\n", pElement->GetDesc() );
  232. m_UndoList.AddToTail( pElement );
  233. if ( m_bStreamStart )
  234. {
  235. pElement->SetEndOfStream( true );
  236. m_bStreamStart = false;
  237. }
  238. }
  239. void CUndoManager::Undo()
  240. {
  241. CNotifyScopeGuard notify( "CUndoManager::Undo", NOTIFY_SOURCE_UNDO, NOTIFY_SETDIRTYFLAG );
  242. Trace( "Undo\n======\n" );
  243. bool saveEnabled = m_bEnabled;
  244. m_bEnabled = false;
  245. bool bEndOfStream = false;
  246. while ( !bEndOfStream && m_UndoList.Count() > 0 )
  247. {
  248. int i = m_UndoList.Tail();
  249. IUndoElement *action = m_UndoList[ i ];
  250. Assert( action );
  251. Trace( " %s\n", action->GetDesc() );
  252. action->Undo();
  253. m_RedoStack.Push( action );
  254. bEndOfStream = action->IsEndOfStream();
  255. m_UndoList.Remove( i );
  256. }
  257. Trace( "======\n\n" );
  258. m_bEnabled = saveEnabled;
  259. m_PreviousChainingID = 0;
  260. }
  261. void CUndoManager::Redo()
  262. {
  263. CNotifyScopeGuard notify( "CUndoManager::Redo", NOTIFY_SOURCE_UNDO, NOTIFY_SETDIRTYFLAG );
  264. Trace( "Redo\n======\n" );
  265. bool saveEnabled = m_bEnabled;
  266. m_bEnabled = false;
  267. bool bEndOfStream = false;
  268. while ( !bEndOfStream && m_RedoStack.Count() > 0 )
  269. {
  270. IUndoElement *action = NULL;
  271. m_RedoStack.Pop( action );
  272. Assert( action );
  273. Trace( " %s\n", action->GetDesc() );
  274. action->Redo();
  275. m_UndoList.AddToTail( action );
  276. if ( m_RedoStack.Count() > 0 )
  277. {
  278. action = m_RedoStack.Top();
  279. bEndOfStream = action->IsEndOfStream();
  280. }
  281. }
  282. Trace( "======\n\n" );
  283. m_bEnabled = saveEnabled;
  284. m_PreviousChainingID = 0;
  285. }
  286. const char *CUndoManager::UndoDesc() const
  287. {
  288. if ( m_UndoList.Count() <= 0 )
  289. return "";
  290. int i = m_UndoList.Tail();
  291. IUndoElement *action = m_UndoList[ i ];
  292. return action->UndoDesc();
  293. }
  294. const char *CUndoManager::RedoDesc() const
  295. {
  296. if ( m_RedoStack.Count() <= 0 )
  297. {
  298. return "";
  299. }
  300. IUndoElement *action = m_RedoStack.Top();
  301. return action->RedoDesc();
  302. }
  303. UtlSymId_t CUndoManager::GetUndoDescInternal( const char *context )
  304. {
  305. if ( m_nNesting <= 0 )
  306. {
  307. static CUtlSymbolTable s_DescErrorsTable;
  308. static CUtlVector< CUtlSymbol > s_DescErrors;
  309. CUtlSymbol sym = s_DescErrorsTable.AddString( context );
  310. if ( s_DescErrors.Find( sym ) == s_DescErrors.InvalidIndex() )
  311. {
  312. Warning( "CUndoManager::GetUndoDescInternal: undoable operation missing CUndoScopeGuard in application\nContext( %s )\n", context );
  313. s_DescErrors.AddToTail( sym );
  314. }
  315. return s_UndoSymbolTable.AddString( context );
  316. }
  317. return m_UndoDesc;
  318. }
  319. UtlSymId_t CUndoManager::GetRedoDescInternal( const char *context )
  320. {
  321. if ( m_nNesting <= 0 )
  322. {
  323. // Warning( "CUndoManager::GetRedoDescInternal: undoable operation missing CUndoScopeGuard in application\nContext( %s )", context );
  324. return s_UndoSymbolTable.AddString( context );
  325. }
  326. return m_RedoDesc;
  327. }
  328. void CUndoManager::GetUndoInfo( CUtlVector< UndoInfo_t >& list )
  329. {
  330. // Needs to persist after function returns...
  331. static CUtlSymbolTable table;
  332. int ops = 0;
  333. for ( int i = m_UndoList.Tail(); i != m_UndoList.InvalidIndex(); i = m_UndoList.Previous( i ) )
  334. {
  335. ++ops;
  336. IUndoElement *action = m_UndoList[ i ];
  337. Assert( action );
  338. bool bEndOfStream = action->IsEndOfStream();
  339. UndoInfo_t info;
  340. info.undo = action->UndoDesc();
  341. info.redo = action->RedoDesc();
  342. // This is a hack because GetDesc() returns a static char buf[] and so the last one will clobber them all
  343. // So we have the requester pass in a temporary string table so we can get a ptr to a CUtlSymbol in the table
  344. // and use that. Sigh.
  345. const char *desc = action->GetDesc();
  346. CUtlSymbol sym = table.AddString( desc );
  347. info.desc = table.String( sym );
  348. info.terminator = bEndOfStream;
  349. info.numoperations = bEndOfStream ? ops : 1;
  350. list.AddToTail( info );
  351. if ( bEndOfStream )
  352. {
  353. ops = 0;
  354. }
  355. }
  356. }
  357. void CUndoManager::TraceUndo( bool state )
  358. {
  359. m_bTrace = state;
  360. }