Counter Strike : Global Offensive Source Code
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.

443 lines
9.1 KiB

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