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:
DebugScopeTimer(const char * operation)45 DebugScopeTimer(const char* operation)
46 : m_operation(operation)
47 {
48 m_timer.start();
49 }
~DebugScopeTimer()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 static const int 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
StateApplicator(Undoable * undoable,UndoMemento * data)75 StateApplicator(Undoable* undoable, UndoMemento* data)
76 : m_undoable(undoable), m_data(data)
77 {
78 }
restore()79 void restore()
80 {
81 m_undoable->importState(m_data);
82 }
release()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:
empty() const93 bool empty() const
94 {
95 return m_states.empty();
96 }
size() const97 std::size_t size() const
98 {
99 return m_states.size();
100 }
save(Undoable * undoable)101 void save(Undoable* undoable)
102 {
103 m_states.push_front(StateApplicator(undoable, undoable->exportState()));
104 }
restore()105 void restore()
106 {
107 for(states_t::iterator i = m_states.begin(); i != m_states.end(); ++i)
108 {
109 (*i).restore();
110 }
111 }
release()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
OperationRadiantUndoSystem::Operation126 Operation(const char* command)
127 : m_command(command)
128 {
129 }
~OperationRadiantUndoSystem::Operation130 ~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:
UndoStack()146 UndoStack() : m_pending(0)
147 {
148 }
~UndoStack()149 ~UndoStack()
150 {
151 clear();
152 }
empty() const153 bool empty() const
154 {
155 return m_stack.empty();
156 }
size() const157 std::size_t size() const
158 {
159 return m_stack.size();
160 }
back()161 Operation* back()
162 {
163 return m_stack.back();
164 }
back() const165 const Operation* back() const
166 {
167 return m_stack.back();
168 }
front()169 Operation* front()
170 {
171 return m_stack.front();
172 }
front() const173 const Operation* front() const
174 {
175 return m_stack.front();
176 }
pop_front()177 void pop_front()
178 {
179 delete m_stack.front();
180 m_stack.pop_front();
181 }
pop_back()182 void pop_back()
183 {
184 delete m_stack.back();
185 m_stack.pop_back();
186 }
clear()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 }
start(const char * command)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 }
finish(const char * command)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 }
save(Undoable * undoable)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
UndoStackFiller()240 UndoStackFiller()
241 : m_stack(0)
242 {
243 }
save(Undoable * undoable)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 }
setStack(UndoStack * stack)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
mark_undoables(UndoStack * stack)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:
RadiantUndoSystem()276 RadiantUndoSystem()
277 : m_undo_levels(64)
278 {
279 }
~RadiantUndoSystem()280 ~RadiantUndoSystem()
281 {
282 clear();
283 }
observer(Undoable * undoable)284 UndoObserver* observer(Undoable* undoable)
285 {
286 ASSERT_NOTNULL(undoable);
287
288 return &m_undoables[undoable];
289 }
release(Undoable * undoable)290 void release(Undoable* undoable)
291 {
292 ASSERT_NOTNULL(undoable);
293
294 m_undoables.erase(undoable);
295 }
setLevels(std::size_t levels)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 }
getLevels() const309 std::size_t getLevels() const
310 {
311 return m_undo_levels;
312 }
size() const313 std::size_t size() const
314 {
315 return m_undo_stack.size();
316 }
startUndo()317 void startUndo()
318 {
319 m_undo_stack.start("unnamedCommand");
320 mark_undoables(&m_undo_stack);
321 }
finishUndo(const char * command)322 bool finishUndo(const char* command)
323 {
324 bool changed = m_undo_stack.finish(command);
325 mark_undoables(0);
326 return changed;
327 }
startRedo()328 void startRedo()
329 {
330 m_redo_stack.start("unnamedCommand");
331 mark_undoables(&m_redo_stack);
332 }
finishRedo(const char * command)333 bool finishRedo(const char* command)
334 {
335 bool changed = m_redo_stack.finish(command);
336 mark_undoables(0);
337 return changed;
338 }
start()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 }
finish(const char * command)349 void finish(const char* command)
350 {
351 if(finishUndo(command))
352 {
353 globalOutputStream() << command << '\n';
354 }
355 }
undo()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 }
redo()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 }
clear()392 void clear()
393 {
394 mark_undoables(0);
395 m_undo_stack.clear();
396 m_redo_stack.clear();
397 trackersClear();
398 }
trackerAttach(UndoTracker & tracker)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 }
trackerDetach(UndoTracker & tracker)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 }
trackersClear() const409 void trackersClear() const
410 {
411 for(Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i)
412 {
413 (*i)->clear();
414 }
415 }
trackersBegin() const416 void trackersBegin() const
417 {
418 for(Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i)
419 {
420 (*i)->begin();
421 }
422 }
trackersUndo() const423 void trackersUndo() const
424 {
425 for(Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i)
426 {
427 (*i)->undo();
428 }
429 }
trackersRedo() const430 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
UndoLevels_importString(RadiantUndoSystem & undo,const char * value)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;
UndoLevels_exportString(const RadiantUndoSystem & undo,const StringImportCallback & importer)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
UndoLevelsImport(RadiantUndoSystem & self,int value)456 void UndoLevelsImport(RadiantUndoSystem& self, int value)
457 {
458 self.setLevels(value);
459 }
460 typedef ReferenceCaller1<RadiantUndoSystem, int, UndoLevelsImport> UndoLevelsImportCaller;
UndoLevelsExport(const RadiantUndoSystem & self,const IntImportCallback & importCallback)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
Undo_constructPreferences(RadiantUndoSystem & undo,PreferencesPage & page)468 void Undo_constructPreferences(RadiantUndoSystem& undo, PreferencesPage& page)
469 {
470 page.appendSpinner("Undo Queue Size", 64, 0, 1024, UndoLevelsImportCaller(undo), UndoLevelsExportCaller(undo));
471 }
Undo_constructPage(RadiantUndoSystem & undo,PreferenceGroup & group)472 void Undo_constructPage(RadiantUndoSystem& undo, PreferenceGroup& group)
473 {
474 PreferencesPage page(group.createPage("Undo", "Undo Queue Settings"));
475 Undo_constructPreferences(undo, page);
476 }
Undo_registerPreferencesPage(RadiantUndoSystem & undo)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
UndoSystemAPI()493 UndoSystemAPI()
494 {
495 GlobalPreferenceSystem().registerPreference("UndoLevels", makeIntStringImportCallback(UndoLevelsImportCaller(m_undosystem)), makeIntStringExportCallback(UndoLevelsExportCaller(m_undosystem)));
496
497 Undo_registerPreferencesPage(m_undosystem);
498 }
getTable()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 {
state_typeundoable_test::state_type525 state_type() : test_data(0)
526 {
527 }
state_typeundoable_test::state_type528 state_type(const state_type& other) : UndoMemento(other), test_data(other.test_data)
529 {
530 }
releaseundoable_test::state_type531 void release()
532 {
533 delete this;
534 }
535
536 int test_data;
537 };
538 state_type m_state;
539 UndoObserver* m_observer;
540 public:
undoable_test()541 undoable_test()
542 : m_observer(GlobalUndoSystem().observer(this))
543 {
544 }
~undoable_test()545 ~undoable_test()
546 {
547 GlobalUndoSystem().release(this);
548 }
exportState() const549 UndoMemento* exportState() const
550 {
551 return new state_type(m_state);
552 }
importState(const UndoMemento * state)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
mutate(unsigned int data)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
591