Group import/export callbacks
[xonotic/netradiant.git] / radiant / undo.cpp
1 /*
2    Copyright (C) 2001-2006, William Joseph.
3    All Rights Reserved.
4
5    This file is part of GtkRadiant.
6
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.
11
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.
16
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
20  */
21
22 #include "undo.h"
23
24 #include "debugging/debugging.h"
25 #include "warnings.h"
26
27 #include "iundo.h"
28 #include "preferencesystem.h"
29 #include "string/string.h"
30 #include "generic/callback.h"
31 #include "preferences.h"
32 #include "stringio.h"
33
34 #include <list>
35 #include <map>
36 #include <set>
37
38 #include "timer.h"
39
40 class DebugScopeTimer
41 {
42 Timer m_timer;
43 const char* m_operation;
44 public:
45 DebugScopeTimer( const char* operation )
46         : m_operation( operation ){
47         m_timer.start();
48 }
49 ~DebugScopeTimer(){
50         unsigned int elapsed = m_timer.elapsed_msec();
51         if ( elapsed > 0 ) {
52                 globalOutputStream() << m_operation << ": " << elapsed << " msec\n";
53         }
54 }
55 };
56
57
58 class RadiantUndoSystem : public UndoSystem
59 {
60 UINT_CONSTANT( MAX_UNDO_LEVELS, 1024 );
61
62 class Snapshot
63 {
64 class StateApplicator
65 {
66 public:
67 Undoable* m_undoable;
68 private:
69 UndoMemento* m_data;
70 public:
71
72 StateApplicator( Undoable* undoable, UndoMemento* data )
73         : m_undoable( undoable ), m_data( data ){
74 }
75 void restore(){
76         m_undoable->importState( m_data );
77 }
78 void release(){
79         m_data->release();
80 }
81 };
82
83 typedef std::list<StateApplicator> states_t;
84 states_t m_states;
85
86 public:
87 bool empty() const {
88         return m_states.empty();
89 }
90 std::size_t size() const {
91         return m_states.size();
92 }
93 void save( Undoable* undoable ){
94         m_states.push_front( StateApplicator( undoable, undoable->exportState() ) );
95 }
96 void restore(){
97         for ( states_t::iterator i = m_states.begin(); i != m_states.end(); ++i )
98         {
99                 ( *i ).restore();
100         }
101 }
102 void release(){
103         for ( states_t::iterator i = m_states.begin(); i != m_states.end(); ++i )
104         {
105                 ( *i ).release();
106         }
107 }
108 };
109
110 struct Operation
111 {
112         Snapshot m_snapshot;
113         CopiedString m_command;
114
115         Operation( const char* command )
116                 : m_command( command ){
117         }
118         ~Operation(){
119                 m_snapshot.release();
120         }
121 };
122
123
124 class UndoStack
125 {
126 //! Note: using std::list instead of vector/deque, to avoid copying of undos
127 typedef std::list<Operation*> Operations;
128
129 Operations m_stack;
130 Operation* m_pending;
131
132 public:
133 UndoStack() : m_pending( 0 ){
134 }
135 ~UndoStack(){
136         clear();
137 }
138 bool empty() const {
139         return m_stack.empty();
140 }
141 std::size_t size() const {
142         return m_stack.size();
143 }
144 Operation* back(){
145         return m_stack.back();
146 }
147 const Operation* back() const {
148         return m_stack.back();
149 }
150 Operation* front(){
151         return m_stack.front();
152 }
153 const Operation* front() const {
154         return m_stack.front();
155 }
156 void pop_front(){
157         delete m_stack.front();
158         m_stack.pop_front();
159 }
160 void pop_back(){
161         delete m_stack.back();
162         m_stack.pop_back();
163 }
164 void clear(){
165         if ( !m_stack.empty() ) {
166                 for ( Operations::iterator i = m_stack.begin(); i != m_stack.end(); ++i )
167                 {
168                         delete *i;
169                 }
170                 m_stack.clear();
171         }
172 }
173 void start( const char* command ){
174         if ( m_pending != 0 ) {
175                 delete m_pending;
176         }
177         m_pending = new Operation( command );
178 }
179 bool finish( const char* command ){
180         if ( m_pending != 0 ) {
181                 delete m_pending;
182                 m_pending = 0;
183                 return false;
184         }
185         else
186         {
187                 ASSERT_MESSAGE( !m_stack.empty(), "undo stack empty" );
188                 m_stack.back()->m_command = command;
189                 return true;
190         }
191 }
192 void save( Undoable* undoable ){
193         if ( m_pending != 0 ) {
194                 m_stack.push_back( m_pending );
195                 m_pending = 0;
196         }
197         back()->m_snapshot.save( undoable );
198 }
199 };
200
201 UndoStack m_undo_stack;
202 UndoStack m_redo_stack;
203
204 class UndoStackFiller : public UndoObserver
205 {
206 UndoStack* m_stack;
207 public:
208
209 UndoStackFiller()
210         : m_stack( 0 ){
211 }
212 void save( Undoable* undoable ){
213         ASSERT_NOTNULL( undoable );
214
215         if ( m_stack != 0 ) {
216                 m_stack->save( undoable );
217                 m_stack = 0;
218         }
219 }
220 void setStack( UndoStack* stack ){
221         m_stack = stack;
222 }
223 };
224
225 typedef std::map<Undoable*, UndoStackFiller> undoables_t;
226 undoables_t m_undoables;
227
228 void mark_undoables( UndoStack* stack ){
229         for ( undoables_t::iterator i = m_undoables.begin(); i != m_undoables.end(); ++i )
230         {
231                 ( *i ).second.setStack( stack );
232         }
233 }
234
235 std::size_t m_undo_levels;
236
237 typedef std::set<UndoTracker*> Trackers;
238 Trackers m_trackers;
239 public:
240 RadiantUndoSystem()
241         : m_undo_levels( 64 ){
242 }
243 ~RadiantUndoSystem(){
244         clear();
245 }
246 UndoObserver* observer( Undoable* undoable ){
247         ASSERT_NOTNULL( undoable );
248
249         return &m_undoables[undoable];
250 }
251 void release( Undoable* undoable ){
252         ASSERT_NOTNULL( undoable );
253
254         m_undoables.erase( undoable );
255 }
256 void setLevels( std::size_t levels ){
257         if ( levels > MAX_UNDO_LEVELS() ) {
258                 levels = MAX_UNDO_LEVELS();
259         }
260
261         while ( m_undo_stack.size() > levels )
262         {
263                 m_undo_stack.pop_front();
264         }
265         m_undo_levels = levels;
266 }
267 std::size_t getLevels() const {
268         return m_undo_levels;
269 }
270 std::size_t size() const {
271         return m_undo_stack.size();
272 }
273 void startUndo(){
274         m_undo_stack.start( "unnamedCommand" );
275         mark_undoables( &m_undo_stack );
276 }
277 bool finishUndo( const char* command ){
278         bool changed = m_undo_stack.finish( command );
279         mark_undoables( 0 );
280         return changed;
281 }
282 void startRedo(){
283         m_redo_stack.start( "unnamedCommand" );
284         mark_undoables( &m_redo_stack );
285 }
286 bool finishRedo( const char* command ){
287         bool changed = m_redo_stack.finish( command );
288         mark_undoables( 0 );
289         return changed;
290 }
291 void start(){
292         m_redo_stack.clear();
293         if ( m_undo_stack.size() == m_undo_levels ) {
294                 m_undo_stack.pop_front();
295         }
296         startUndo();
297         trackersBegin();
298 }
299 void finish( const char* command ){
300         if ( finishUndo( command ) ) {
301                 globalOutputStream() << command << '\n';
302         }
303 }
304 void undo(){
305         if ( m_undo_stack.empty() ) {
306                 globalOutputStream() << "Undo: no undo available\n";
307         }
308         else
309         {
310                 Operation* operation = m_undo_stack.back();
311                 globalOutputStream() << "Undo: " << operation->m_command.c_str() << "\n";
312
313                 startRedo();
314                 trackersUndo();
315                 operation->m_snapshot.restore();
316                 finishRedo( operation->m_command.c_str() );
317                 m_undo_stack.pop_back();
318         }
319 }
320 void redo(){
321         if ( m_redo_stack.empty() ) {
322                 globalOutputStream() << "Redo: no redo available\n";
323         }
324         else
325         {
326                 Operation* operation = m_redo_stack.back();
327                 globalOutputStream() << "Redo: " << operation->m_command.c_str() << "\n";
328
329                 startUndo();
330                 trackersRedo();
331                 operation->m_snapshot.restore();
332                 finishUndo( operation->m_command.c_str() );
333                 m_redo_stack.pop_back();
334         }
335 }
336 void clear(){
337         mark_undoables( 0 );
338         m_undo_stack.clear();
339         m_redo_stack.clear();
340         trackersClear();
341 }
342 void trackerAttach( UndoTracker& tracker ){
343         ASSERT_MESSAGE( m_trackers.find( &tracker ) == m_trackers.end(), "undo tracker already attached" );
344         m_trackers.insert( &tracker );
345 }
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 );
349 }
350 void trackersClear() const {
351         for ( Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i )
352         {
353                 ( *i )->clear();
354         }
355 }
356 void trackersBegin() const {
357         for ( Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i )
358         {
359                 ( *i )->begin();
360         }
361 }
362 void trackersUndo() const {
363         for ( Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i )
364         {
365                 ( *i )->undo();
366         }
367 }
368 void trackersRedo() const {
369         for ( Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i )
370         {
371                 ( *i )->redo();
372         }
373 }
374 };
375
376
377
378 void UndoLevels_importString( RadiantUndoSystem& undo, const char* value ){
379         int levels;
380         Int_importString( levels, value );
381         undo.setLevels( levels );
382 }
383 typedef ReferenceCaller<RadiantUndoSystem, void(const char*), UndoLevels_importString> UndoLevelsImportStringCaller;
384 void UndoLevels_exportString( const RadiantUndoSystem& undo, const ImportExportCallback<const char *>::Import_t& importer ){
385         Int_exportString( static_cast<int>( undo.getLevels() ), importer );
386 }
387 typedef ConstReferenceCaller<RadiantUndoSystem, void(const ImportExportCallback<const char *>::Import_t&), UndoLevels_exportString> UndoLevelsExportStringCaller;
388
389 #include "generic/callback.h"
390
391 void UndoLevelsImport( RadiantUndoSystem& self, int value ){
392         self.setLevels( value );
393 }
394 typedef ReferenceCaller<RadiantUndoSystem, void(int), UndoLevelsImport> UndoLevelsImportCaller;
395 void UndoLevelsExport( const RadiantUndoSystem& self, const ImportExportCallback<int>::Import_t& importCallback ){
396         importCallback( static_cast<int>( self.getLevels() ) );
397 }
398 typedef ConstReferenceCaller<RadiantUndoSystem, void(const ImportExportCallback<int>::Import_t&), UndoLevelsExport> UndoLevelsExportCaller;
399
400
401 void Undo_constructPreferences( RadiantUndoSystem& undo, PreferencesPage& page ){
402         page.appendSpinner( "Undo Queue Size", 64, 0, 1024, {ImportExportCallback<int>::Import_t( UndoLevelsImportCaller( undo ) ), ImportExportCallback<int>::Export_t( UndoLevelsExportCaller( undo ) )} );
403 }
404 void Undo_constructPage( RadiantUndoSystem& undo, PreferenceGroup& group ){
405         PreferencesPage page( group.createPage( "Undo", "Undo Queue Settings" ) );
406         Undo_constructPreferences( undo, page );
407 }
408 void Undo_registerPreferencesPage( RadiantUndoSystem& undo ){
409         PreferencesDialog_addSettingsPage( ReferenceCaller<RadiantUndoSystem, void(PreferenceGroup&), Undo_constructPage>( undo ) );
410 }
411
412 class UndoSystemDependencies : public GlobalPreferenceSystemModuleRef
413 {
414 };
415
416 class UndoSystemAPI
417 {
418 RadiantUndoSystem m_undosystem;
419 public:
420 typedef UndoSystem Type;
421 STRING_CONSTANT( Name, "*" );
422
423 UndoSystemAPI(){
424         GlobalPreferenceSystem().registerPreference( "UndoLevels", makeIntStringImportCallback( UndoLevelsImportCaller( m_undosystem ) ), makeIntStringExportCallback( UndoLevelsExportCaller( m_undosystem ) ) );
425
426         Undo_registerPreferencesPage( m_undosystem );
427 }
428 UndoSystem* getTable(){
429         return &m_undosystem;
430 }
431 };
432
433 #include "modulesystem/singletonmodule.h"
434 #include "modulesystem/moduleregistry.h"
435
436 typedef SingletonModule<UndoSystemAPI, UndoSystemDependencies> UndoSystemModule;
437 typedef Static<UndoSystemModule> StaticUndoSystemModule;
438 StaticRegisterModule staticRegisterUndoSystem( StaticUndoSystemModule::instance() );
439
440
441
442
443
444
445
446
447
448
449 class undoable_test : public Undoable
450 {
451 struct state_type : public UndoMemento
452 {
453         state_type() : test_data( 0 ){
454         }
455         state_type( const state_type& other ) : UndoMemento( other ), test_data( other.test_data ){
456         }
457         void release(){
458                 delete this;
459         }
460
461         int test_data;
462 };
463 state_type m_state;
464 UndoObserver* m_observer;
465 public:
466 undoable_test()
467         : m_observer( GlobalUndoSystem().observer( this ) ){
468 }
469 ~undoable_test(){
470         GlobalUndoSystem().release( this );
471 }
472 UndoMemento* exportState() const {
473         return new state_type( m_state );
474 }
475 void importState( const UndoMemento* state ){
476         ASSERT_NOTNULL( state );
477
478         m_observer->save( this );
479         m_state = *( static_cast<const state_type*>( state ) );
480 }
481
482 void mutate( unsigned int data ){
483         m_observer->save( this );
484         m_state.test_data = data;
485 }
486 };
487
488 #if 0
489
490 class TestUndo
491 {
492 public:
493 TestUndo(){
494         undoable_test test;
495         GlobalUndoSystem().begin( "bleh" );
496         test.mutate( 3 );
497         GlobalUndoSystem().begin( "blah" );
498         test.mutate( 4 );
499         GlobalUndoSystem().undo();
500         GlobalUndoSystem().undo();
501         GlobalUndoSystem().redo();
502         GlobalUndoSystem().redo();
503 }
504 };
505
506 TestUndo g_TestUndo;
507
508 #endif