2 Copyright (C) 2001-2006, William Joseph.
5 This file is part of GtkRadiant.
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24 #include "debugging/debugging.h"
28 #include "preferencesystem.h"
29 #include "string/string.h"
30 #include "generic/callback.h"
31 #include "preferences.h"
43 const char* m_operation;
45 DebugScopeTimer( const char* operation )
46 : m_operation( operation ){
50 unsigned int elapsed = m_timer.elapsed_msec();
52 globalOutputStream() << m_operation << ": " << elapsed << " msec\n";
58 class RadiantUndoSystem : public UndoSystem
60 INTEGER_CONSTANT( MAX_UNDO_LEVELS, 1024 );
72 StateApplicator( Undoable* undoable, UndoMemento* data )
73 : m_undoable( undoable ), m_data( data ){
76 m_undoable->importState( m_data );
83 typedef std::list<StateApplicator> states_t;
88 return m_states.empty();
90 std::size_t size() const {
91 return m_states.size();
93 void save( Undoable* undoable ){
94 m_states.push_front( StateApplicator( undoable, undoable->exportState() ) );
97 for ( states_t::iterator i = m_states.begin(); i != m_states.end(); ++i )
103 for ( states_t::iterator i = m_states.begin(); i != m_states.end(); ++i )
113 CopiedString m_command;
115 Operation( const char* command )
116 : m_command( command ){
119 m_snapshot.release();
126 //! Note: using std::list instead of vector/deque, to avoid copying of undos
127 typedef std::list<Operation*> Operations;
130 Operation* m_pending;
133 UndoStack() : m_pending( 0 ){
139 return m_stack.empty();
141 std::size_t size() const {
142 return m_stack.size();
145 return m_stack.back();
147 const Operation* back() const {
148 return m_stack.back();
151 return m_stack.front();
153 const Operation* front() const {
154 return m_stack.front();
157 delete m_stack.front();
161 delete m_stack.back();
165 if ( !m_stack.empty() ) {
166 for ( Operations::iterator i = m_stack.begin(); i != m_stack.end(); ++i )
173 void start( const char* command ){
174 if ( m_pending != 0 ) {
177 m_pending = new Operation( command );
179 bool finish( const char* command ){
180 if ( m_pending != 0 ) {
187 ASSERT_MESSAGE( !m_stack.empty(), "undo stack empty" );
188 m_stack.back()->m_command = command;
192 void save( Undoable* undoable ){
193 if ( m_pending != 0 ) {
194 m_stack.push_back( m_pending );
197 back()->m_snapshot.save( undoable );
201 UndoStack m_undo_stack;
202 UndoStack m_redo_stack;
204 class UndoStackFiller : public UndoObserver
212 void save( Undoable* undoable ){
213 ASSERT_NOTNULL( undoable );
215 if ( m_stack != 0 ) {
216 m_stack->save( undoable );
220 void setStack( UndoStack* stack ){
225 typedef std::map<Undoable*, UndoStackFiller> undoables_t;
226 undoables_t m_undoables;
228 void mark_undoables( UndoStack* stack ){
229 for ( undoables_t::iterator i = m_undoables.begin(); i != m_undoables.end(); ++i )
231 ( *i ).second.setStack( stack );
235 std::size_t m_undo_levels;
237 typedef std::set<UndoTracker*> Trackers;
241 : m_undo_levels( 64 ){
243 ~RadiantUndoSystem(){
246 UndoObserver* observer( Undoable* undoable ){
247 ASSERT_NOTNULL( undoable );
249 return &m_undoables[undoable];
251 void release( Undoable* undoable ){
252 ASSERT_NOTNULL( undoable );
254 m_undoables.erase( undoable );
256 void setLevels( std::size_t levels ){
257 if ( levels > MAX_UNDO_LEVELS() ) {
258 levels = MAX_UNDO_LEVELS();
261 while ( m_undo_stack.size() > levels )
263 m_undo_stack.pop_front();
265 m_undo_levels = levels;
267 std::size_t getLevels() const {
268 return m_undo_levels;
270 std::size_t size() const {
271 return m_undo_stack.size();
274 m_undo_stack.start( "unnamedCommand" );
275 mark_undoables( &m_undo_stack );
277 bool finishUndo( const char* command ){
278 bool changed = m_undo_stack.finish( command );
283 m_redo_stack.start( "unnamedCommand" );
284 mark_undoables( &m_redo_stack );
286 bool finishRedo( const char* command ){
287 bool changed = m_redo_stack.finish( command );
292 m_redo_stack.clear();
293 if ( m_undo_stack.size() == m_undo_levels ) {
294 m_undo_stack.pop_front();
299 void finish( const char* command ){
300 if ( finishUndo( command ) ) {
301 globalOutputStream() << command << '\n';
305 if ( m_undo_stack.empty() ) {
306 globalOutputStream() << "Undo: no undo available\n";
310 Operation* operation = m_undo_stack.back();
311 globalOutputStream() << "Undo: " << operation->m_command.c_str() << "\n";
315 operation->m_snapshot.restore();
316 finishRedo( operation->m_command.c_str() );
317 m_undo_stack.pop_back();
321 if ( m_redo_stack.empty() ) {
322 globalOutputStream() << "Redo: no redo available\n";
326 Operation* operation = m_redo_stack.back();
327 globalOutputStream() << "Redo: " << operation->m_command.c_str() << "\n";
331 operation->m_snapshot.restore();
332 finishUndo( operation->m_command.c_str() );
333 m_redo_stack.pop_back();
338 m_undo_stack.clear();
339 m_redo_stack.clear();
342 void trackerAttach( UndoTracker& tracker ){
343 ASSERT_MESSAGE( m_trackers.find( &tracker ) == m_trackers.end(), "undo tracker already attached" );
344 m_trackers.insert( &tracker );
346 void trackerDetach( UndoTracker& tracker ){
347 ASSERT_MESSAGE( m_trackers.find( &tracker ) != m_trackers.end(), "undo tracker cannot be detached" );
348 m_trackers.erase( &tracker );
350 void trackersClear() const {
351 for ( Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i )
356 void trackersBegin() const {
357 for ( Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i )
362 void trackersUndo() const {
363 for ( Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i )
368 void trackersRedo() const {
369 for ( Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i )
378 void UndoLevels_importString( RadiantUndoSystem& undo, const char* value ){
380 Int_importString( levels, value );
381 undo.setLevels( levels );
383 typedef ReferenceCaller1<RadiantUndoSystem, const char*, UndoLevels_importString> UndoLevelsImportStringCaller;
384 void UndoLevels_exportString( const RadiantUndoSystem& undo, const StringImportCallback& importer ){
385 Int_exportString( static_cast<int>( undo.getLevels() ), importer );
387 typedef ConstReferenceCaller1<RadiantUndoSystem, const StringImportCallback&, UndoLevels_exportString> UndoLevelsExportStringCaller;
389 #include "generic/callback.h"
391 void UndoLevelsImport( RadiantUndoSystem& self, int value ){
392 self.setLevels( value );
394 typedef ReferenceCaller1<RadiantUndoSystem, int, UndoLevelsImport> UndoLevelsImportCaller;
395 void UndoLevelsExport( const RadiantUndoSystem& self, const IntImportCallback& importCallback ){
396 importCallback( static_cast<int>( self.getLevels() ) );
398 typedef ConstReferenceCaller1<RadiantUndoSystem, const IntImportCallback&, UndoLevelsExport> UndoLevelsExportCaller;
401 void Undo_constructPreferences( RadiantUndoSystem& undo, PreferencesPage& page ){
402 page.appendSpinner( "Undo Queue Size", 64, 0, 1024, IntImportCallback( UndoLevelsImportCaller( undo ) ), IntExportCallback( UndoLevelsExportCaller( undo ) ) );
404 void Undo_constructPage( RadiantUndoSystem& undo, PreferenceGroup& group ){
405 PreferencesPage page( group.createPage( "Undo", "Undo Queue Settings" ) );
406 Undo_constructPreferences( undo, page );
408 void Undo_registerPreferencesPage( RadiantUndoSystem& undo ){
409 PreferencesDialog_addSettingsPage( ReferenceCaller1<RadiantUndoSystem, PreferenceGroup&, Undo_constructPage>( undo ) );
412 class UndoSystemDependencies : public GlobalPreferenceSystemModuleRef
418 RadiantUndoSystem m_undosystem;
420 typedef UndoSystem Type;
421 STRING_CONSTANT( Name, "*" );
424 GlobalPreferenceSystem().registerPreference( "UndoLevels", makeIntStringImportCallback( UndoLevelsImportCaller( m_undosystem ) ), makeIntStringExportCallback( UndoLevelsExportCaller( m_undosystem ) ) );
426 Undo_registerPreferencesPage( m_undosystem );
428 UndoSystem* getTable(){
429 return &m_undosystem;
433 #include "modulesystem/singletonmodule.h"
434 #include "modulesystem/moduleregistry.h"
436 typedef SingletonModule<UndoSystemAPI, UndoSystemDependencies> UndoSystemModule;
437 typedef Static<UndoSystemModule> StaticUndoSystemModule;
438 StaticRegisterModule staticRegisterUndoSystem( StaticUndoSystemModule::instance() );
449 class undoable_test : public Undoable
451 struct state_type : public UndoMemento
453 state_type() : test_data( 0 ){
455 state_type( const state_type& other ) : UndoMemento( other ), test_data( other.test_data ){
464 UndoObserver* m_observer;
467 : m_observer( GlobalUndoSystem().observer( this ) ){
470 GlobalUndoSystem().release( this );
472 UndoMemento* exportState() const {
473 return new state_type( m_state );
475 void importState( const UndoMemento* state ){
476 ASSERT_NOTNULL( state );
478 m_observer->save( this );
479 m_state = *( static_cast<const state_type*>( state ) );
482 void mutate( unsigned int data ){
483 m_observer->save( this );
484 m_state.test_data = data;
495 GlobalUndoSystem().begin( "bleh" );
497 GlobalUndoSystem().begin( "blah" );
499 GlobalUndoSystem().undo();
500 GlobalUndoSystem().undo();
501 GlobalUndoSystem().redo();
502 GlobalUndoSystem().redo();