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