X-Git-Url: http://git.xonotic.org/?a=blobdiff_plain;f=radiant%2Fundo.cpp;h=fb741abdd9db93059102f34a1ced0a595a78d531;hb=32dc1da26645b97a5823ff22175000542225c6dd;hp=d576f82bf862ea8dbdd8926c5a7af2c0ee4797cb;hpb=830125fad042fad35dc029b6eb57c8156ad7e176;p=xonotic%2Fnetradiant.git diff --git a/radiant/undo.cpp b/radiant/undo.cpp index d576f82b..fb741abd 100644 --- a/radiant/undo.cpp +++ b/radiant/undo.cpp @@ -1,6 +1,6 @@ /* - Copyright (C) 1999-2007 id Software, Inc. and contributors. - For a list of contributors, see the accompanying CONTRIBUTORS file. + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. This file is part of GtkRadiant. @@ -19,918 +19,490 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "undo.h" -/* - - QERadiant Undo/Redo - +#include "debugging/debugging.h" +#include "warnings.h" - basic setup: +#include "iundo.h" +#include "preferencesystem.h" +#include "string/string.h" +#include "generic/callback.h" +#include "preferences.h" +#include "stringio.h" - <-g_undolist---------g_lastundo> <---map data---> <-g_lastredo---------g_redolist-> +#include +#include +#include +#include "timer.h" - undo/redo on the world_entity is special, only the epair changes are remembered - and the world entity never gets deleted. - - FIXME: maybe reset the Undo system at map load - maybe also reset the entityId at map load - */ - -#include "stdafx.h" - -typedef struct undo_s +class DebugScopeTimer { - double time; //time operation was performed - int id; //every undo has an unique id - int done; //true when undo is build - const char *operation; //name of the operation - brush_t brushlist; //deleted brushes - entity_t entitylist; //deleted entities - struct undo_s *prev, *next; //next and prev undo in list -} undo_t; - -undo_t *g_undolist; //first undo in the list -undo_t *g_lastundo; //last undo in the list -undo_t *g_redolist; //first redo in the list -undo_t *g_lastredo; //last undo in list -int g_undoMaxSize = 64; //maximum number of undos -int g_undoSize = 0; //number of undos in the list -int g_undoMaxMemorySize = 2 * 1024 * 1024; //maximum undo memory (default 2 MB) -int g_undoMemorySize = 0; //memory size of undo buffer -int g_undoId = 1; //current undo ID (zero is invalid id) -int g_redoId = 1; //current redo ID (zero is invalid id) - -/* - ============= - Undo_MemorySize - ============= - */ -int Undo_MemorySize( void ){ - return g_undoMemorySize; +Timer m_timer; +const char* m_operation; +public: +DebugScopeTimer( const char* operation ) + : m_operation( operation ){ + m_timer.start(); } - -/* - ============= - Undo_ClearRedo - ============= - */ -void Undo_ClearRedo( void ){ - undo_t *redo, *nextredo; - brush_t *pBrush, *pNextBrush; - entity_t *pEntity, *pNextEntity; - - for ( redo = g_redolist; redo; redo = nextredo ) - { - nextredo = redo->next; - for ( pBrush = redo->brushlist.next ; pBrush != NULL && pBrush != &redo->brushlist ; pBrush = pNextBrush ) - { - pNextBrush = pBrush->next; - Brush_Free( pBrush ); - } - for ( pEntity = redo->entitylist.next; pEntity != NULL && pEntity != &redo->entitylist; pEntity = pNextEntity ) - { - pNextEntity = pEntity->next; - Entity_Free( pEntity ); - } - free( redo ); +~DebugScopeTimer(){ + unsigned int elapsed = m_timer.elapsed_msec(); + if ( elapsed > 0 ) { + globalOutputStream() << m_operation << ": " << elapsed << " msec\n"; } - g_redolist = NULL; - g_lastredo = NULL; - g_redoId = 1; } +}; -/* - ============= - Undo_Clear - Clears the undo buffer. - ============= - */ -void Undo_Clear( void ){ - undo_t *undo, *nextundo; - brush_t *pBrush, *pNextBrush; - entity_t *pEntity, *pNextEntity; +class RadiantUndoSystem : public UndoSystem +{ +UINT_CONSTANT( MAX_UNDO_LEVELS, 1024 ); - Undo_ClearRedo(); - for ( undo = g_undolist; undo; undo = nextundo ) - { - nextundo = undo->next; - for ( pBrush = undo->brushlist.next ; pBrush != NULL && pBrush != &undo->brushlist ; pBrush = pNextBrush ) - { - pNextBrush = pBrush->next; - g_undoMemorySize -= Brush_MemorySize( pBrush ); - Brush_Free( pBrush ); - } - for ( pEntity = undo->entitylist.next; pEntity != NULL && pEntity != &undo->entitylist; pEntity = pNextEntity ) - { - pNextEntity = pEntity->next; - g_undoMemorySize -= Entity_MemorySize( pEntity ); - Entity_Free( pEntity ); - } - g_undoMemorySize -= sizeof( undo_t ); - free( undo ); - } - g_undolist = NULL; - g_lastundo = NULL; - g_undoSize = 0; - g_undoMemorySize = 0; - g_undoId = 1; +class Snapshot +{ +class StateApplicator +{ +public: +Undoable* m_undoable; +private: +UndoMemento* m_data; +public: + +StateApplicator( Undoable* undoable, UndoMemento* data ) + : m_undoable( undoable ), m_data( data ){ } - -/* - ============= - Undo_SetMaxSize - ============= - */ -void Undo_SetMaxSize( int size ){ - Undo_Clear(); - if ( size < 1 ) { - g_undoMaxSize = 1; - } - else{g_undoMaxSize = size; } +void restore(){ + m_undoable->importState( m_data ); } - -/* - ============= - Undo_GetMaxSize - ============= - */ -int Undo_GetMaxSize( void ){ - return g_undoMaxSize; +void release(){ + m_data->release(); } +}; -/* - ============= - Undo_SetMaxMemorySize - ============= - */ -void Undo_SetMaxMemorySize( int size ){ - Undo_Clear(); - if ( size < 1024 ) { - g_undoMaxMemorySize = 1024; - } - else{g_undoMaxMemorySize = size; } -} +typedef std::list states_t; +states_t m_states; -/* - ============= - Undo_GetMaxMemorySize - ============= - */ -int Undo_GetMaxMemorySize( void ){ - return g_undoMaxMemorySize; +public: +bool empty() const { + return m_states.empty(); } - -/* - ============= - Undo_FreeFirstUndo - ============= - */ -void Undo_FreeFirstUndo( void ){ - undo_t *undo; - brush_t *pBrush, *pNextBrush; - entity_t *pEntity, *pNextEntity; - - //remove the oldest undo from the undo buffer - undo = g_undolist; - g_undolist = g_undolist->next; - g_undolist->prev = NULL; - // - for ( pBrush = undo->brushlist.next ; pBrush != NULL && pBrush != &undo->brushlist ; pBrush = pNextBrush ) +std::size_t size() const { + return m_states.size(); +} +void save( Undoable* undoable ){ + m_states.push_front( StateApplicator( undoable, undoable->exportState() ) ); +} +void restore(){ + for ( states_t::iterator i = m_states.begin(); i != m_states.end(); ++i ) { - pNextBrush = pBrush->next; - g_undoMemorySize -= Brush_MemorySize( pBrush ); - Brush_Free( pBrush ); + ( *i ).restore(); } - for ( pEntity = undo->entitylist.next; pEntity != NULL && pEntity != &undo->entitylist; pEntity = pNextEntity ) +} +void release(){ + for ( states_t::iterator i = m_states.begin(); i != m_states.end(); ++i ) { - pNextEntity = pEntity->next; - g_undoMemorySize -= Entity_MemorySize( pEntity ); - Entity_Free( pEntity ); + ( *i ).release(); } - g_undoMemorySize -= sizeof( undo_t ); - free( undo ); - g_undoSize--; } +}; -/* - ============= - Undo_GeneralStart - ============= - */ -void Undo_GeneralStart( const char *operation ){ - undo_t *undo; - brush_t *pBrush; - entity_t *pEntity; - - - if ( g_lastundo ) { - if ( !g_lastundo->done ) { - Sys_Printf( "Undo_Start: WARNING last undo not finished.\n" ); - } - } +struct Operation +{ + Snapshot m_snapshot; + CopiedString m_command; - undo = (undo_t *) malloc( sizeof( undo_t ) ); - if ( !undo ) { - return; - } - memset( undo, 0, sizeof( undo_t ) ); - undo->brushlist.next = &undo->brushlist; - undo->brushlist.prev = &undo->brushlist; - undo->entitylist.next = &undo->entitylist; - undo->entitylist.prev = &undo->entitylist; - if ( g_lastundo ) { - g_lastundo->next = undo; + Operation( const char* command ) + : m_command( command ){ } - else{ - g_undolist = undo; + ~Operation(){ + m_snapshot.release(); } - undo->prev = g_lastundo; - undo->next = NULL; - g_lastundo = undo; - - undo->time = Sys_DoubleTime(); - // - if ( g_undoId > g_undoMaxSize * 2 ) { - g_undoId = 1; - } - if ( g_undoId <= 0 ) { - g_undoId = 1; - } - undo->id = g_undoId++; - undo->done = false; - undo->operation = operation; - //reset the undo IDs of all brushes using the new ID - for ( pBrush = active_brushes.next; pBrush != NULL && pBrush != &active_brushes; pBrush = pBrush->next ) - { - if ( pBrush->undoId == undo->id ) { - pBrush->undoId = 0; +}; + + +class UndoStack +{ +//! Note: using std::list instead of vector/deque, to avoid copying of undos +typedef std::list Operations; + +Operations m_stack; +Operation* m_pending; + +public: +UndoStack() : m_pending( 0 ){ +} +~UndoStack(){ + clear(); +} +bool empty() const { + return m_stack.empty(); +} +std::size_t size() const { + return m_stack.size(); +} +Operation* back(){ + return m_stack.back(); +} +const Operation* back() const { + return m_stack.back(); +} +Operation* front(){ + return m_stack.front(); +} +const Operation* front() const { + return m_stack.front(); +} +void pop_front(){ + delete m_stack.front(); + m_stack.pop_front(); +} +void pop_back(){ + delete m_stack.back(); + m_stack.pop_back(); +} +void clear(){ + if ( !m_stack.empty() ) { + for ( Operations::iterator i = m_stack.begin(); i != m_stack.end(); ++i ) + { + delete *i; } + m_stack.clear(); } - for ( pBrush = selected_brushes.next; pBrush != NULL && pBrush != &selected_brushes; pBrush = pBrush->next ) - { - if ( pBrush->undoId == undo->id ) { - pBrush->undoId = 0; - } +} +void start( const char* command ){ + if ( m_pending != 0 ) { + delete m_pending; + } + m_pending = new Operation( command ); +} +bool finish( const char* command ){ + if ( m_pending != 0 ) { + delete m_pending; + m_pending = 0; + return false; } - //reset the undo IDs of all entities using thew new ID - for ( pEntity = entities.next; pEntity != NULL && pEntity != &entities; pEntity = pEntity->next ) + else { - if ( pEntity->undoId == undo->id ) { - pEntity->undoId = 0; - } + ASSERT_MESSAGE( !m_stack.empty(), "undo stack empty" ); + m_stack.back()->m_command = command; + return true; } - g_undoMemorySize += sizeof( undo_t ); - g_undoSize++; - //undo buffer is bound to a max - if ( g_undoSize > g_undoMaxSize ) { - Undo_FreeFirstUndo(); +} +void save( Undoable* undoable ){ + if ( m_pending != 0 ) { + m_stack.push_back( m_pending ); + m_pending = 0; } + back()->m_snapshot.save( undoable ); } +}; -/* - ============= - Undo_BrushInUndo - ============= - */ -int Undo_BrushInUndo( undo_t *undo, brush_t *brush ){ -/* brush_t *b; +UndoStack m_undo_stack; +UndoStack m_redo_stack; + +class UndoStackFiller : public UndoObserver +{ +UndoStack* m_stack; +public: - for (b = undo->brushlist.next; b != &undo->brushlist; b = b->next) - { - // Arnout: NOTE - can't do a pointer compare as the brushes get cloned into the undo brushlist, and not just referenced from it - // For entities we have a unique ID, for brushes we have numberID - but brush full clone increases that anyway so it's useless right now. - if (b == brush) return true; - }*/ - // Arnout: function is pointless right now, see above explanation - return false; +UndoStackFiller() + : m_stack( 0 ){ } +void save( Undoable* undoable ){ + ASSERT_NOTNULL( undoable ); -/* - ============= - Undo_EntityInUndo - ============= - */ -int Undo_EntityInUndo( undo_t *undo, entity_t *ent ){ - entity_t *e; + if ( m_stack != 0 ) { + m_stack->save( undoable ); + m_stack = 0; + } +} +void setStack( UndoStack* stack ){ + m_stack = stack; +} +}; - for ( e = undo->entitylist.next; e != &undo->entitylist; e = e->next ) +typedef std::map undoables_t; +undoables_t m_undoables; + +void mark_undoables( UndoStack* stack ){ + for ( undoables_t::iterator i = m_undoables.begin(); i != m_undoables.end(); ++i ) { - // Arnout: NOTE - can't do a pointer compare as the entities get cloned into the undo entitylist, and not just referenced from it - //if (e == ent) return true; - if ( e->entityId == ent->entityId ) { - return true; - } + ( *i ).second.setStack( stack ); } - return false; } -/* - ============= - Undo_Start - ============= - */ -void Undo_Start( const char *operation ){ - // spog - disable undo if undo levels = 0 - if ( g_PrefsDlg.m_nUndoLevels == 0 ) { -#ifdef DBG_UNDO - Sys_Printf( "Undo_Start: undo is disabled.\n" ); -#endif - return; - } +std::size_t m_undo_levels; - Undo_ClearRedo(); - Undo_GeneralStart( operation ); +typedef std::set Trackers; +Trackers m_trackers; +public: +RadiantUndoSystem() + : m_undo_levels( 64 ){ } +~RadiantUndoSystem(){ + clear(); +} +UndoObserver* observer( Undoable* undoable ){ + ASSERT_NOTNULL( undoable ); -/* - ============= - Undo_AddBrush - ============= - */ -void Undo_AddBrush( brush_t *pBrush ){ - // spog - disable undo if undo levels = 0 - if ( g_PrefsDlg.m_nUndoLevels == 0 ) { -#ifdef DBG_UNDO - Sys_Printf( "Undo_AddBrush: undo is disabled.\n" ); -#endif - return; + return &m_undoables[undoable]; +} +void release( Undoable* undoable ){ + ASSERT_NOTNULL( undoable ); + + m_undoables.erase( undoable ); +} +void setLevels( std::size_t levels ){ + if ( levels > MAX_UNDO_LEVELS() ) { + levels = MAX_UNDO_LEVELS(); } - if ( !g_lastundo ) { - Sys_Printf( "Undo_AddBrushList: no last undo.\n" ); - return; + while ( m_undo_stack.size() > levels ) + { + m_undo_stack.pop_front(); } - if ( g_lastundo->entitylist.next != &g_lastundo->entitylist ) { - Sys_Printf( "Undo_AddBrushList: WARNING adding brushes after entity.\n" ); + m_undo_levels = levels; +} +std::size_t getLevels() const { + return m_undo_levels; +} +std::size_t size() const { + return m_undo_stack.size(); +} +void startUndo(){ + m_undo_stack.start( "unnamedCommand" ); + mark_undoables( &m_undo_stack ); +} +bool finishUndo( const char* command ){ + bool changed = m_undo_stack.finish( command ); + mark_undoables( 0 ); + return changed; +} +void startRedo(){ + m_redo_stack.start( "unnamedCommand" ); + mark_undoables( &m_redo_stack ); +} +bool finishRedo( const char* command ){ + bool changed = m_redo_stack.finish( command ); + mark_undoables( 0 ); + return changed; +} +void start(){ + m_redo_stack.clear(); + if ( m_undo_stack.size() == m_undo_levels ) { + m_undo_stack.pop_front(); } - //if the brush is already in the undo - if ( Undo_BrushInUndo( g_lastundo, pBrush ) ) { - return; + startUndo(); + trackersBegin(); +} +void finish( const char* command ){ + if ( finishUndo( command ) ) { + globalOutputStream() << command << '\n'; } - //clone the brush - brush_t* pClone = Brush_FullClone( pBrush ); - //save the ID of the owner entity - pClone->ownerId = pBrush->owner->entityId; - //save the old undo ID for previous undos - pClone->undoId = pBrush->undoId; - Brush_AddToList( pClone, &g_lastundo->brushlist ); - // - g_undoMemorySize += Brush_MemorySize( pClone ); } - -/* - ============= - Undo_AddBrushList - TTimo: some brushes are just there for UI, and the information is somewhere else - for patches it's in the patchMesh_t structure, so when we clone the brush we get that information (brush_t::pPatch) - but: models are stored in pBrush->owner->md3Class, and owner epairs and origin parameters are important - so, we detect models and push the entity in the undo session (as well as it's BBox brush) - same for other items like weapons and ammo etc. - ============= - */ -void Undo_AddBrushList( brush_t *brushlist ){ - // spog - disable undo if undo levels = 0 - if ( g_PrefsDlg.m_nUndoLevels == 0 ) { -#ifdef DBG_UNDO - Sys_Printf( "Undo_AddBrushList: undo is disabled.\n" ); -#endif - return; +void undo(){ + if ( m_undo_stack.empty() ) { + globalOutputStream() << "Undo: no undo available\n"; } + else + { + Operation* operation = m_undo_stack.back(); + globalOutputStream() << "Undo: " << operation->m_command.c_str() << "\n"; - brush_t *pBrush; - - if ( !g_lastundo ) { - Sys_Printf( "Undo_AddBrushList: no last undo.\n" ); - return; + startRedo(); + trackersUndo(); + operation->m_snapshot.restore(); + finishRedo( operation->m_command.c_str() ); + m_undo_stack.pop_back(); } - if ( g_lastundo->entitylist.next != &g_lastundo->entitylist ) { - Sys_Printf( "Undo_AddBrushList: WARNING adding brushes after entity.\n" ); +} +void redo(){ + if ( m_redo_stack.empty() ) { + globalOutputStream() << "Redo: no redo available\n"; } - //copy the brushes to the undo - for ( pBrush = brushlist->next ; pBrush != NULL && pBrush != brushlist; pBrush = pBrush->next ) + else { - //if the brush is already in the undo - //++timo FIXME: when does this happen? - if ( Undo_BrushInUndo( g_lastundo, pBrush ) ) { - continue; - } - // do we need to store this brush's entity in the undo? - // if it's a fixed size entity, the brush that reprents it is not really relevant, it's used for selecting and moving around - // what we want to store for undo is the owner entity, epairs and origin/angle stuff - //++timo FIXME: if the entity is not fixed size I don't know, so I don't do it yet - if ( pBrush->owner->eclass->fixedsize == 1 ) { - Undo_AddEntity( pBrush->owner ); - } - // clone the brush - brush_t* pClone = Brush_FullClone( pBrush ); - // save the ID of the owner entity - pClone->ownerId = pBrush->owner->entityId; - // save the old undo ID from previous undos - pClone->undoId = pBrush->undoId; - Brush_AddToList( pClone, &g_lastundo->brushlist ); - // track memory size used by undo - g_undoMemorySize += Brush_MemorySize( pClone ); + Operation* operation = m_redo_stack.back(); + globalOutputStream() << "Redo: " << operation->m_command.c_str() << "\n"; + + startUndo(); + trackersRedo(); + operation->m_snapshot.restore(); + finishUndo( operation->m_command.c_str() ); + m_redo_stack.pop_back(); } } - -/* - ============= - Undo_EndBrush - ============= - */ -void Undo_EndBrush( brush_t *pBrush ){ - // spog - disable undo if undo levels = 0 - if ( g_PrefsDlg.m_nUndoLevels == 0 ) { -#ifdef DBG_UNDO - Sys_Printf( "Undo_EndBrush: undo is disabled.\n" ); -#endif - return; +void clear(){ + mark_undoables( 0 ); + m_undo_stack.clear(); + m_redo_stack.clear(); + trackersClear(); +} +void trackerAttach( UndoTracker& tracker ){ + ASSERT_MESSAGE( m_trackers.find( &tracker ) == m_trackers.end(), "undo tracker already attached" ); + m_trackers.insert( &tracker ); +} +void trackerDetach( UndoTracker& tracker ){ + ASSERT_MESSAGE( m_trackers.find( &tracker ) != m_trackers.end(), "undo tracker cannot be detached" ); + m_trackers.erase( &tracker ); +} +void trackersClear() const { + for ( Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i ) + { + ( *i )->clear(); } - - - if ( !g_lastundo ) { - //Sys_Printf("Undo_End: no last undo.\n"); - return; +} +void trackersBegin() const { + for ( Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i ) + { + ( *i )->begin(); } - if ( g_lastundo->done ) { - //Sys_Printf("Undo_End: last undo already finished.\n"); - return; +} +void trackersUndo() const { + for ( Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i ) + { + ( *i )->undo(); } - pBrush->undoId = g_lastundo->id; } - -/* - ============= - Undo_EndBrushList - ============= - */ -void Undo_EndBrushList( brush_t *brushlist ){ - // spog - disable undo if undo levels = 0 - if ( g_PrefsDlg.m_nUndoLevels == 0 ) { -#ifdef DBG_UNDO - Sys_Printf( "Undo_EndBrushList: undo is disabled.\n" ); -#endif - return; +void trackersRedo() const { + for ( Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i ) + { + ( *i )->redo(); } +} +}; - if ( !g_lastundo ) { - //Sys_Printf("Undo_End: no last undo.\n"); - return; - } - if ( g_lastundo->done ) { - //Sys_Printf("Undo_End: last undo already finished.\n"); - return; - } - for ( brush_t* pBrush = brushlist->next; pBrush != NULL && pBrush != brushlist; pBrush = pBrush->next ) - { - pBrush->undoId = g_lastundo->id; - // http://github.com/mfn/GtkRadiant/commit/ee1ef98536470d5680bd9bfecc5b5c9a62ffe9ab - if ( pBrush->owner->eclass->fixedsize == 1 ) { - pBrush->owner->undoId = pBrush->undoId; - } - } + +void UndoLevels_importString( RadiantUndoSystem& undo, const char* value ){ + int levels; + PropertyImpl::Import( levels, value ); + undo.setLevels( levels ); +} +typedef ReferenceCaller UndoLevelsImportStringCaller; +void UndoLevels_exportString( const RadiantUndoSystem& undo, const Callback & importer ){ + PropertyImpl::Export( static_cast( undo.getLevels() ), importer ); } +typedef ConstReferenceCaller &), UndoLevels_exportString> UndoLevelsExportStringCaller; -/* - ============= - Undo_AddEntity - ============= - */ -void Undo_AddEntity( entity_t *entity ){ - // spog - disable undo if undo levels = 0 - if ( g_PrefsDlg.m_nUndoLevels == 0 ) { -#ifdef DBG_UNDO - Sys_Printf( "Undo_AddEntity: undo is disabled.\n" ); -#endif - return; - } +#include "generic/callback.h" +struct UndoLevels { + static void Export(const RadiantUndoSystem &self, const Callback &returnz) { + returnz(static_cast(self.getLevels())); + } - entity_t* pClone; + static void Import(RadiantUndoSystem &self, int value) { + self.setLevels(value); + } +}; - if ( !g_lastundo ) { - Sys_Printf( "Undo_AddEntity: no last undo.\n" ); - return; - } - //if the entity is already in the undo - if ( Undo_EntityInUndo( g_lastundo, entity ) ) { - return; - } - //clone the entity - pClone = Entity_Clone( entity ); - //save the old undo ID for previous undos - pClone->undoId = entity->undoId; - //save the entity ID (we need a full clone) - pClone->entityId = entity->entityId; - // - Entity_AddToList( pClone, &g_lastundo->entitylist ); - // - g_undoMemorySize += Entity_MemorySize( pClone ); +void Undo_constructPreferences( RadiantUndoSystem& undo, PreferencesPage& page ){ + page.appendSpinner("Undo Queue Size", 64, 0, 1024, make_property(undo)); +} +void Undo_constructPage( RadiantUndoSystem& undo, PreferenceGroup& group ){ + PreferencesPage page( group.createPage( "Undo", "Undo Queue Settings" ) ); + Undo_constructPreferences( undo, page ); +} +void Undo_registerPreferencesPage( RadiantUndoSystem& undo ){ + PreferencesDialog_addSettingsPage( ReferenceCaller( undo ) ); } -/* - ============= - Undo_EndEntity - ============= - */ -void Undo_EndEntity( entity_t *entity ){ - // spog - disable undo if undo levels = 0 - if ( g_PrefsDlg.m_nUndoLevels == 0 ) { -#ifdef DBG_UNDO - Sys_Printf( "Undo_EndEntity: undo is disabled.\n" ); -#endif - return; - } +class UndoSystemDependencies : public GlobalPreferenceSystemModuleRef +{ +}; +class UndoSystemAPI +{ +RadiantUndoSystem m_undosystem; +public: +typedef UndoSystem Type; +STRING_CONSTANT( Name, "*" ); - if ( !g_lastundo ) { -#ifdef _DEBUG - Sys_Printf( "Undo_End: no last undo.\n" ); -#endif - return; - } - if ( g_lastundo->done ) { -#ifdef _DEBUG - Sys_Printf( "Undo_End: last undo already finished.\n" ); -#endif - return; - } - if ( entity == world_entity ) { - //Sys_Printf("Undo_AddEntity: undo on world entity.\n"); - //NOTE: we never delete the world entity when undoing an operation - // we only transfer the epairs - return; - } - entity->undoId = g_lastundo->id; +UndoSystemAPI(){ + GlobalPreferenceSystem().registerPreference("UndoLevels", make_property_string(m_undosystem)); + + Undo_registerPreferencesPage( m_undosystem ); } +UndoSystem* getTable(){ + return &m_undosystem; +} +}; -/* - ============= - Undo_End - ============= - */ -void Undo_End( void ){ - // spog - disable undo if undo levels = 0 - if ( g_PrefsDlg.m_nUndoLevels == 0 ) { -#ifdef DBG_UNDO - Sys_Printf( "Undo_End: undo is disabled.\n" ); -#endif - return; - } +#include "modulesystem/singletonmodule.h" +#include "modulesystem/moduleregistry.h" +typedef SingletonModule UndoSystemModule; +typedef Static StaticUndoSystemModule; +StaticRegisterModule staticRegisterUndoSystem( StaticUndoSystemModule::instance() ); - if ( !g_lastundo ) { - //Sys_Printf("Undo_End: no last undo.\n"); - return; - } - if ( g_lastundo->done ) { - //Sys_Printf("Undo_End: last undo already finished.\n"); - return; - } - g_lastundo->done = true; - //undo memory size is bound to a max - while ( g_undoMemorySize > g_undoMaxMemorySize ) - { - //always keep one undo - if ( g_undolist == g_lastundo ) { - break; - } - Undo_FreeFirstUndo(); - } - // - //Sys_Printf("undo size = %d, undo memory = %d\n", g_undoSize, g_undoMemorySize); -} -/* - ============= - Undo_Undo - ============= - */ -void Undo_Undo( boolean bSilent ){ - // spog - disable undo if undo levels = 0 - if ( g_PrefsDlg.m_nUndoLevels == 0 ) { - Sys_Printf( "Undo_Undo: undo is disabled.\n" ); - return; - } - undo_t *undo, *redo; - brush_t *pBrush, *pNextBrush; - entity_t *pEntity, *pNextEntity, *pUndoEntity; - if ( !g_lastundo ) { - Sys_Printf( "Nothing left to undo.\n" ); - return; - } - if ( !g_lastundo->done ) { - Sys_Printf( "Undo_Undo: WARNING: last undo not yet finished!\n" ); - } - // get the last undo - undo = g_lastundo; - if ( g_lastundo->prev ) { - g_lastundo->prev->next = NULL; - } - else{g_undolist = NULL; } - g_lastundo = g_lastundo->prev; - //allocate a new redo - redo = (undo_t *) malloc( sizeof( undo_t ) ); - if ( !redo ) { - return; - } - memset( redo, 0, sizeof( undo_t ) ); - redo->brushlist.next = &redo->brushlist; - redo->brushlist.prev = &redo->brushlist; - redo->entitylist.next = &redo->entitylist; - redo->entitylist.prev = &redo->entitylist; - if ( g_lastredo ) { - g_lastredo->next = redo; - } - else{g_redolist = redo; } - redo->prev = g_lastredo; - redo->next = NULL; - g_lastredo = redo; - redo->time = Sys_DoubleTime(); - redo->id = g_redoId++; - redo->done = true; - redo->operation = undo->operation; - - //reset the redo IDs of all brushes using the new ID - for ( pBrush = active_brushes.next; pBrush != NULL && pBrush != &active_brushes; pBrush = pBrush->next ) - { - if ( pBrush->redoId == redo->id ) { - pBrush->redoId = 0; - } - } - for ( pBrush = selected_brushes.next; pBrush != NULL && pBrush != &selected_brushes; pBrush = pBrush->next ) - { - if ( pBrush->redoId == redo->id ) { - pBrush->redoId = 0; - } - } - //reset the redo IDs of all entities using thew new ID - for ( pEntity = entities.next; pEntity != NULL && pEntity != &entities; pEntity = pEntity->next ) - { - if ( pEntity->redoId == redo->id ) { - pEntity->redoId = 0; - } - } - // deselect current sutff - Select_Deselect(); - // move "created" brushes to the redo - for ( pBrush = active_brushes.next; pBrush != NULL && pBrush != &active_brushes; pBrush = pNextBrush ) - { - pNextBrush = pBrush->next; - if ( pBrush->undoId == undo->id ) { - //Brush_Free(pBrush); - //move the brush to the redo - Brush_RemoveFromList( pBrush ); - Brush_AddToList( pBrush, &redo->brushlist ); - //make sure the ID of the owner is stored - pBrush->ownerId = pBrush->owner->entityId; - //unlink the brush from the owner entity - Entity_UnlinkBrush( pBrush ); - } - } - // move "created" entities to the redo - for ( pEntity = entities.next; pEntity != NULL && pEntity != &entities; pEntity = pNextEntity ) - { - pNextEntity = pEntity->next; - if ( pEntity->undoId == undo->id ) { - // check if this entity is in the undo - for ( pUndoEntity = undo->entitylist.next; pUndoEntity != NULL && pUndoEntity != &undo->entitylist; pUndoEntity = pUndoEntity->next ) - { - // move brushes to the undo entity - if ( pUndoEntity->entityId == pEntity->entityId ) { - pUndoEntity->brushes.next = pEntity->brushes.next; - pUndoEntity->brushes.prev = pEntity->brushes.prev; - pEntity->brushes.next = &pEntity->brushes; - pEntity->brushes.prev = &pEntity->brushes; - } - } - // - //Entity_Free(pEntity); - //move the entity to the redo - Entity_RemoveFromList( pEntity ); - Entity_AddToList( pEntity, &redo->entitylist ); - } - } - // add the undo entities back into the entity list - for ( pEntity = undo->entitylist.next; pEntity != NULL && pEntity != &undo->entitylist; pEntity = undo->entitylist.next ) - { - g_undoMemorySize -= Entity_MemorySize( pEntity ); - //if this is the world entity - if ( pEntity->entityId == world_entity->entityId ) { - epair_t* tmp = world_entity->epairs; - world_entity->epairs = pEntity->epairs; - pEntity->epairs = tmp; - Entity_Free( pEntity ); - } - else - { - Entity_RemoveFromList( pEntity ); - Entity_AddToList( pEntity, &entities ); - pEntity->redoId = redo->id; - } - } - // add the undo brushes back into the selected brushes - for ( pBrush = undo->brushlist.next; pBrush != NULL && pBrush != &undo->brushlist; pBrush = undo->brushlist.next ) - { - //Sys_Printf("Owner ID: %i\n",pBrush->ownerId); - g_undoMemorySize -= Brush_MemorySize( pBrush ); - Brush_RemoveFromList( pBrush ); - Brush_AddToList( pBrush, &active_brushes ); - for ( pEntity = entities.next; pEntity != NULL && pEntity != &entities; pEntity = pEntity->next ) // fixes broken undo on entities - { - //Sys_Printf("Entity ID: %i\n",pEntity->entityId); - if ( pEntity->entityId == pBrush->ownerId ) { - Entity_LinkBrush( pEntity, pBrush ); - break; - } - } - //if the brush is not linked then it should be linked into the world entity - //++timo FIXME: maybe not, maybe we've lost this entity's owner! - if ( pEntity == NULL || pEntity == &entities ) { - Entity_LinkBrush( world_entity, pBrush ); - } - //build the brush - //Brush_Build(pBrush); - Select_Brush( pBrush ); - pBrush->redoId = redo->id; - } - if ( !bSilent ) { - Sys_Printf( "%s undone.\n", undo->operation ); - } - // free the undo - g_undoMemorySize -= sizeof( undo_t ); - free( undo ); - g_undoSize--; - g_undoId--; - if ( g_undoId <= 0 ) { - g_undoId = 2 * g_undoMaxSize; - } - // - g_bScreenUpdates = true; - UpdateSurfaceDialog(); - Sys_UpdateWindows( W_ALL ); -} -/* - ============= - Undo_Redo - ============= - */ -void Undo_Redo( void ){ - // spog - disable undo if undo levels = 0 - if ( g_PrefsDlg.m_nUndoLevels == 0 ) { - Sys_Printf( "Undo_Redo: undo is disabled.\n" ); - return; - } - undo_t *redo; - brush_t *pBrush, *pNextBrush; - entity_t *pEntity, *pNextEntity, *pRedoEntity; - if ( !g_lastredo ) { - Sys_Printf( "Nothing left to redo.\n" ); - return; - } - if ( g_lastundo ) { - if ( !g_lastundo->done ) { - Sys_Printf( "WARNING: last undo not finished.\n" ); - } - } - // get the last redo - redo = g_lastredo; - if ( g_lastredo->prev ) { - g_lastredo->prev->next = NULL; - } - else{g_redolist = NULL; } - g_lastredo = g_lastredo->prev; - // - Undo_GeneralStart( redo->operation ); - // remove current selection - Select_Deselect(); - // move "created" brushes back to the last undo - for ( pBrush = active_brushes.next; pBrush != NULL && pBrush != &active_brushes; pBrush = pNextBrush ) - { - pNextBrush = pBrush->next; - if ( pBrush->redoId == redo->id ) { - //move the brush to the undo - Brush_RemoveFromList( pBrush ); - Brush_AddToList( pBrush, &g_lastundo->brushlist ); - g_undoMemorySize += Brush_MemorySize( pBrush ); - pBrush->ownerId = pBrush->owner->entityId; - Entity_UnlinkBrush( pBrush ); - } - } - // move "created" entities back to the last undo - for ( pEntity = entities.next; pEntity != NULL && pEntity != &entities; pEntity = pNextEntity ) - { - pNextEntity = pEntity->next; - if ( pEntity->redoId == redo->id ) { - // check if this entity is in the redo - for ( pRedoEntity = redo->entitylist.next; pRedoEntity != NULL && pRedoEntity != &redo->entitylist; pRedoEntity = pRedoEntity->next ) - { - // move brushes to the redo entity - if ( pRedoEntity->entityId == pEntity->entityId ) { - pRedoEntity->brushes.next = pEntity->brushes.next; - pRedoEntity->brushes.prev = pEntity->brushes.prev; - pEntity->brushes.next = &pEntity->brushes; - pEntity->brushes.prev = &pEntity->brushes; - } - } - // - //Entity_Free(pEntity); - //move the entity to the redo - Entity_RemoveFromList( pEntity ); - Entity_AddToList( pEntity, &g_lastundo->entitylist ); - g_undoMemorySize += Entity_MemorySize( pEntity ); - } +class undoable_test : public Undoable +{ +struct state_type : public UndoMemento +{ + state_type() : test_data( 0 ){ } - // add the undo entities back into the entity list - for ( pEntity = redo->entitylist.next; pEntity != NULL && pEntity != &redo->entitylist; pEntity = redo->entitylist.next ) - { - //if this is the world entity - if ( pEntity->entityId == world_entity->entityId ) { - epair_t* tmp = world_entity->epairs; - world_entity->epairs = pEntity->epairs; - pEntity->epairs = tmp; - Entity_Free( pEntity ); - } - else - { - Entity_RemoveFromList( pEntity ); - Entity_AddToList( pEntity, &entities ); - } + state_type( const state_type& other ) : UndoMemento( other ), test_data( other.test_data ){ } - // add the redo brushes back into the selected brushes - for ( pBrush = redo->brushlist.next; pBrush != NULL && pBrush != &redo->brushlist; pBrush = redo->brushlist.next ) - { - Brush_RemoveFromList( pBrush ); - Brush_AddToList( pBrush, &active_brushes ); - for ( pEntity = entities.next; pEntity != NULL && pEntity != &entities; pEntity = pEntity->next ) // fixes broken undo on entities - { - if ( pEntity->entityId == pBrush->ownerId ) { - Entity_LinkBrush( pEntity, pBrush ); - break; - } - } - //if the brush is not linked then it should be linked into the world entity - if ( pEntity == NULL || pEntity == &entities ) { - Entity_LinkBrush( world_entity, pBrush ); - } - //build the brush - //Brush_Build(pBrush); - Select_Brush( pBrush ); + void release(){ + delete this; } - // - Undo_End(); - // - Sys_Printf( "%s redone.\n", redo->operation ); - // - g_redoId--; - // free the undo - free( redo ); - // - g_bScreenUpdates = true; - UpdateSurfaceDialog(); - Sys_UpdateWindows( W_ALL ); + + int test_data; +}; +state_type m_state; +UndoObserver* m_observer; +public: +undoable_test() + : m_observer( GlobalUndoSystem().observer( this ) ){ } +~undoable_test(){ + GlobalUndoSystem().release( this ); +} +UndoMemento* exportState() const { + return new state_type( m_state ); +} +void importState( const UndoMemento* state ){ + ASSERT_NOTNULL( state ); -/* - ============= - Undo_RedoAvailable - ============= - */ -int Undo_RedoAvailable( void ){ - if ( g_lastredo ) { - return true; - } - return false; + m_observer->save( this ); + m_state = *( static_cast( state ) ); } -int Undo_GetUndoId( void ){ - if ( g_lastundo ) { - return g_lastundo->id; - } - return 0; +void mutate( unsigned int data ){ + m_observer->save( this ); + m_state.test_data = data; } +}; -/* - ============= - Undo_UndoAvailable - ============= - */ -int Undo_UndoAvailable( void ){ - if ( g_lastundo ) { - if ( g_lastundo->done ) { - return true; - } - } - return false; +#if 0 + +class TestUndo +{ +public: +TestUndo(){ + undoable_test test; + GlobalUndoSystem().begin( "bleh" ); + test.mutate( 3 ); + GlobalUndoSystem().begin( "blah" ); + test.mutate( 4 ); + GlobalUndoSystem().undo(); + GlobalUndoSystem().undo(); + GlobalUndoSystem().redo(); + GlobalUndoSystem().redo(); } +}; + +TestUndo g_TestUndo; + +#endif