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