diff options
Diffstat (limited to 'datamodel/undomanager.cpp')
| -rw-r--r-- | datamodel/undomanager.cpp | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/datamodel/undomanager.cpp b/datamodel/undomanager.cpp new file mode 100644 index 0000000..8ae513b --- /dev/null +++ b/datamodel/undomanager.cpp @@ -0,0 +1,444 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "undomanager.h" +#include "datamodel.h" + +extern CDataModel *g_pDataModelImp; + +CUtlSymbolTableMT CUndoManager::s_UndoSymbolTable; + + +CUndoManager::CUndoManager( ) : + m_bEnabled( true ), + m_bDiscarded( false ), + m_nMaxUndoDepth( 4096 ), + m_nNesting( 0 ), + m_nNotifyNesting( 0 ), + m_bStreamStart( false ), + m_bTrace( false ), + m_bSuppressingNotify( false ), + m_nItemsAddedSinceStartOfStream( 0 ), + m_nNotifySource( 0 ), + m_nNotifyFlags( 0 ), + m_nChainingID( 0 ), + m_PreviousChainingID( 0 ) +{ +} + +CUndoManager::~CUndoManager() +{ +} + +void CUndoManager::Shutdown() +{ + WipeUndo(); + WipeRedo(); +} + +bool CUndoManager::InstallNotificationCallback( IDmNotify *pNotify ) +{ + if ( m_Notifiers.Find( pNotify ) >= 0 ) + return false; + m_Notifiers.AddToTail( pNotify ); + return true; +} + +void CUndoManager::RemoveNotificationCallback( IDmNotify *pNotify ) +{ + m_Notifiers.FindAndRemove( pNotify ); +} + +bool CUndoManager::IsSuppressingNotify( ) const +{ + return m_bSuppressingNotify; +} + +void CUndoManager::SetSuppressingNotify( bool bSuppress ) +{ + m_bSuppressingNotify = bSuppress; +} + +void CUndoManager::Trace( const char *fmt, ... ) +{ + if ( !m_bTrace ) + return; + + char str[ 2048 ]; + va_list argptr; + va_start( argptr, fmt ); + _vsnprintf( str, sizeof( str ) - 1, fmt, argptr ); + va_end( argptr ); + str[ sizeof( str ) - 1 ] = 0; + + char spaces[ 128 ]; + Q_memset( spaces, 0, sizeof( spaces ) ); + for ( int i = 0; i < ( m_nNesting * 3 ); ++i ) + { + if ( i >= 127 ) + break; + spaces[ i ] = ' '; + } + + Msg( "%s%s", spaces, str ); +} + +void CUndoManager::SetUndoDepth( int nMaxUndoDepth ) +{ + Assert( !HasUndoData() ); + m_nMaxUndoDepth = nMaxUndoDepth; +} + +void CUndoManager::EnableUndo() +{ + m_bEnabled = true; +} + +void CUndoManager::DisableUndo() +{ + m_bEnabled = false; +} + +bool CUndoManager::HasUndoData() const +{ + return m_UndoList.Count() != 0; +} + +bool CUndoManager::UndoDataDiscarded() const +{ + return m_bDiscarded; +} + +bool CUndoManager::HasRedoData() const +{ + return m_RedoStack.Count() > 0; +} + +void CUndoManager::PushNotificationScope( const char *pReason, int nNotifySource, int nNotifyFlags ) +{ + if ( m_nNotifyNesting++ == 0 ) + { + m_pNotifyReason = pReason; + m_nNotifySource = nNotifySource; + m_nNotifyFlags = nNotifyFlags; + } +} + +void CUndoManager::PopNotificationScope( bool bAbort ) +{ + --m_nNotifyNesting; + Assert( m_nNotifyNesting >= 0 ); + if ( m_nNotifyNesting == 0 ) + { + if ( !m_bSuppressingNotify && ( ( m_nNotifyFlags & NOTIFY_CHANGE_MASK ) != 0 ) ) + { + int nNotifyCount = m_Notifiers.Count(); + for( int i = 0; i < nNotifyCount; ++i ) + { + m_Notifiers[i]->NotifyDataChanged( m_pNotifyReason, m_nNotifySource, m_nNotifyFlags ); + } + } + m_nNotifySource = 0; + m_nNotifyFlags = 0; + } +} + + + +void CUndoManager::PushUndo( const char *udesc, const char *rdesc, int nChainingID ) +{ + if ( !IsEnabled() ) + return; + + Trace( "[%d] Pushing undo '%s'\n", m_nNesting + 1, udesc ); + + if ( m_nNesting++ == 0 ) + { + m_PreviousChainingID = m_nChainingID; + m_nChainingID = nChainingID; + m_UndoDesc = s_UndoSymbolTable.AddString( udesc ); + m_RedoDesc = ( udesc == rdesc ) ? m_UndoDesc : s_UndoSymbolTable.AddString( rdesc ); + m_bStreamStart = true; + m_nItemsAddedSinceStartOfStream = 0; + } +} + +void CUndoManager::PushRedo() +{ + if ( !IsEnabled() ) + return; + + Trace( "[%d] Popping undo '%s'\n", m_nNesting, s_UndoSymbolTable.String( m_UndoDesc ) ); + + --m_nNesting; + Assert( m_nNesting >= 0 ); + if ( m_nNesting == 0 ) + { + if ( m_nItemsAddedSinceStartOfStream > 0 ) + { + WipeRedo(); + + // Accumulate this operation into the previous "undo" operation if there is one + if ( m_nChainingID != 0 && + m_PreviousChainingID == m_nChainingID ) + { + // Walk undo list backward looking for previous end of stream and unmark that indicator + int i = m_UndoList.Tail(); + while ( i != m_UndoList.InvalidIndex() ) + { + IUndoElement *e = m_UndoList[ i ]; + if ( e && e->IsEndOfStream() ) + { + e->SetEndOfStream( false ); + break; + } + i = m_UndoList.Previous( i ); + } + } + } + + m_nItemsAddedSinceStartOfStream = 0; + } +} + +void CUndoManager::AbortUndoableOperation() +{ + if ( !IsEnabled() ) + return; + + bool hasItems = m_nItemsAddedSinceStartOfStream > 0 ? true : false; + + Trace( "[%d] Aborting undo '%s'\n", m_nNesting, s_UndoSymbolTable.String( m_UndoDesc ) ); + + // Close off context + PushRedo(); + + if ( m_nNesting == 0 && hasItems ) + { + Undo(); + WipeRedo(); + } +} + +void CUndoManager::WipeUndo() +{ + CDisableUndoScopeGuard sg; + + FOR_EACH_LL( m_UndoList, elem ) + { + Trace( "WipeUndo '%s'\n", m_UndoList[ elem ]->GetDesc() ); + + m_UndoList[ elem ]->Release(); + } + m_UndoList.RemoveAll(); + m_PreviousChainingID = 0; +} + +void CUndoManager::WipeRedo() +{ + int c = m_RedoStack.Count(); + if ( c == 0 ) + return; + + CUtlVector< DmElementHandle_t > handles; + g_pDataModelImp->GetInvalidHandles( handles ); + g_pDataModelImp->MarkHandlesValid( handles ); + + CDisableUndoScopeGuard sg; + + for ( int i = 0; i < c ; ++i ) + { + IUndoElement *elem; + elem = m_RedoStack[ i ]; + + Trace( "WipeRedo '%s'\n", elem->GetDesc() ); + + elem->Release(); + } + + m_RedoStack.Clear(); + + g_pDataModelImp->MarkHandlesInvalid( handles ); +} + +void CUndoManager::AddUndoElement( IUndoElement *pElement ) +{ + Assert( IsEnabled() ); + + if ( !pElement ) + return; + + ++m_nItemsAddedSinceStartOfStream; + + WipeRedo(); + + /* + // For later + if ( m_UndoList.Count() >= m_nMaxUndos ) + { + m_bDiscarded = true; + } + */ + + Trace( "AddUndoElement '%s'\n", pElement->GetDesc() ); + + m_UndoList.AddToTail( pElement ); + + if ( m_bStreamStart ) + { + pElement->SetEndOfStream( true ); + m_bStreamStart = false; + } +} + +void CUndoManager::Undo() +{ + CNotifyScopeGuard notify( "CUndoManager::Undo", NOTIFY_SOURCE_UNDO, NOTIFY_SETDIRTYFLAG ); + + Trace( "Undo\n======\n" ); + + bool saveEnabled = m_bEnabled; + m_bEnabled = false; + bool bEndOfStream = false; + while ( !bEndOfStream && m_UndoList.Count() > 0 ) + { + int i = m_UndoList.Tail(); + IUndoElement *action = m_UndoList[ i ]; + Assert( action ); + + Trace( " %s\n", action->GetDesc() ); + + action->Undo(); + m_RedoStack.Push( action ); + bEndOfStream = action->IsEndOfStream(); + m_UndoList.Remove( i ); + } + + Trace( "======\n\n" ); + + m_bEnabled = saveEnabled; + m_PreviousChainingID = 0; +} + +void CUndoManager::Redo() +{ + CNotifyScopeGuard notify( "CUndoManager::Redo", NOTIFY_SOURCE_UNDO, NOTIFY_SETDIRTYFLAG ); + + Trace( "Redo\n======\n" ); + + bool saveEnabled = m_bEnabled; + m_bEnabled = false; + bool bEndOfStream = false; + while ( !bEndOfStream && m_RedoStack.Count() > 0 ) + { + IUndoElement *action = NULL; + m_RedoStack.Pop( action ); + Assert( action ); + + Trace( " %s\n", action->GetDesc() ); + + action->Redo(); + m_UndoList.AddToTail( action ); + if ( m_RedoStack.Count() > 0 ) + { + action = m_RedoStack.Top(); + bEndOfStream = action->IsEndOfStream(); + } + } + + Trace( "======\n\n" ); + + m_bEnabled = saveEnabled; + m_PreviousChainingID = 0; +} + +const char *CUndoManager::UndoDesc() const +{ + if ( m_UndoList.Count() <= 0 ) + return ""; + + int i = m_UndoList.Tail(); + IUndoElement *action = m_UndoList[ i ]; + return action->UndoDesc(); +} + +const char *CUndoManager::RedoDesc() const +{ + if ( m_RedoStack.Count() <= 0 ) + { + return ""; + } + + IUndoElement *action = m_RedoStack.Top(); + return action->RedoDesc(); +} + +UtlSymId_t CUndoManager::GetUndoDescInternal( const char *context ) +{ + if ( m_nNesting <= 0 ) + { + static CUtlSymbolTable s_DescErrorsTable; + static CUtlVector< CUtlSymbol > s_DescErrors; + CUtlSymbol sym = s_DescErrorsTable.AddString( context ); + if ( s_DescErrors.Find( sym ) == s_DescErrors.InvalidIndex() ) + { + Warning( "CUndoManager::GetUndoDescInternal: undoable operation missing CUndoScopeGuard in application\nContext( %s )\n", context ); + s_DescErrors.AddToTail( sym ); + } + return s_UndoSymbolTable.AddString( context ); + } + return m_UndoDesc; +} + +UtlSymId_t CUndoManager::GetRedoDescInternal( const char *context ) +{ + if ( m_nNesting <= 0 ) + { + // Warning( "CUndoManager::GetRedoDescInternal: undoable operation missing CUndoScopeGuard in application\nContext( %s )", context ); + return s_UndoSymbolTable.AddString( context ); + } + return m_RedoDesc; +} + +void CUndoManager::GetUndoInfo( CUtlVector< UndoInfo_t >& list ) +{ + // Needs to persist after function returns... + static CUtlSymbolTable table; + + int ops = 0; + for ( int i = m_UndoList.Tail(); i != m_UndoList.InvalidIndex(); i = m_UndoList.Previous( i ) ) + { + ++ops; + IUndoElement *action = m_UndoList[ i ]; + Assert( action ); + bool bEndOfStream = action->IsEndOfStream(); + + UndoInfo_t info; + info.undo = action->UndoDesc(); + info.redo = action->RedoDesc(); + + // This is a hack because GetDesc() returns a static char buf[] and so the last one will clobber them all + // So we have the requester pass in a temporary string table so we can get a ptr to a CUtlSymbol in the table + // and use that. Sigh. + const char *desc = action->GetDesc(); + CUtlSymbol sym = table.AddString( desc ); + info.desc = table.String( sym ); + info.terminator = bEndOfStream; + info.numoperations = bEndOfStream ? ops : 1; + + list.AddToTail( info ); + + if ( bEndOfStream ) + { + ops = 0; + } + } +} + +void CUndoManager::TraceUndo( bool state ) +{ + m_bTrace = state; +} |