Mercurial > projects > doodle
comparison doodle/core/undo.d @ 47:14f1c051c35b
Moved undo into core.
author | daveb |
---|---|
date | Tue, 03 Aug 2010 16:57:06 +0930 |
parents | doodle/undo/undo.d@ad3ba55ae57b |
children | 576b9fba4677 |
comparison
equal
deleted
inserted
replaced
46:ad3ba55ae57b | 47:14f1c051c35b |
---|---|
1 module doodle.core.undo; | |
2 | |
3 import std.array; | |
4 import std.date; | |
5 | |
6 // An abstract framework for undo/redo. | |
7 // Assume the application works on one document at a time, | |
8 // therefore a single undo/redo history. | |
9 // Each change to the document is modelled as an Edit. | |
10 // Previous edits are represented by an undo stack | |
11 // and can be undone in order. | |
12 // As edits are undone the are placed on a redo stack | |
13 // and can be redone in order. | |
14 // As edits are redone the are placed on the undo stack, etc. | |
15 // | |
16 // When a new edit is made an attempt to merge it with | |
17 // the previous Edit is made. For example, typing characters | |
18 // in short succession generates many Edits that may be | |
19 // merged into one Edit. | |
20 // When a new edit is made the redo stack is cleared. | |
21 // | |
22 // Application code must generate Edits from user interaction. | |
23 // Typically application code will receive some input event and: | |
24 // * Attempt to perform an action, | |
25 // * If the action succeeds then encapsulate the action | |
26 // in an Edit and add it to the undo manager. | |
27 // Note, not all interaction results in Edits, for example, | |
28 // changing the view, zooming/scrolling, etc are not edits | |
29 // and do not affect undo/redo | |
30 | |
31 abstract class Edit { | |
32 private { | |
33 string _description; | |
34 d_time _timeStamp; | |
35 } | |
36 | |
37 this(in string description, d_time timeStamp) { | |
38 assert(description); | |
39 _description = description; | |
40 _timeStamp = timeStamp; | |
41 } | |
42 | |
43 string description() const { return _description; } | |
44 d_time timeStamp() const { return _timeStamp; } | |
45 | |
46 final bool merge(Edit subsequent) { | |
47 if (mergeImpl(subsequent)) { | |
48 // Adopt the new timestamp and description | |
49 _timeStamp = subsequent._timeStamp; | |
50 _description = subsequent._description; | |
51 return true; | |
52 } | |
53 else { | |
54 return false; | |
55 } | |
56 } | |
57 | |
58 void undo(); | |
59 void redo(); | |
60 protected bool mergeImpl(Edit other) { return false; } | |
61 } | |
62 | |
63 interface IUndoManagerObserver { | |
64 // Each description is null if the associated bool is false | |
65 void undoRedoUpdate(in bool canUndo, in string undoDescription, | |
66 in bool canRedo, in string redoDescription); | |
67 } | |
68 | |
69 interface IUndoManager { | |
70 void addEdit(Edit edit); | |
71 void undo(); | |
72 void redo(); | |
73 void reset(); | |
74 | |
75 void addObserver(IUndoManagerObserver observer); | |
76 void removeObserver(IUndoManagerObserver observer); | |
77 } | |
78 | |
79 class UndoManager : IUndoManager { | |
80 this(int maxUndoLevel = -1) { | |
81 _maxUndoLevel = maxUndoLevel; | |
82 } | |
83 | |
84 void addEdit(Edit edit) { | |
85 _redoEdits.length = 0; | |
86 | |
87 if (_undoEdits.empty || !_undoEdits.back.merge(edit)) { | |
88 _undoEdits ~= edit; | |
89 if (_maxUndoLevel >= 0 && _undoEdits.length > _maxUndoLevel) { | |
90 _undoEdits.length = _undoEdits.length - 1; | |
91 } | |
92 } | |
93 | |
94 notifyObservers(); | |
95 } | |
96 | |
97 void undo() { | |
98 assert(canUndo); | |
99 auto edit = _undoEdits.back; | |
100 edit.undo; | |
101 _undoEdits.popBack; | |
102 _redoEdits ~= edit; | |
103 | |
104 notifyObservers(); | |
105 } | |
106 | |
107 void redo() { | |
108 assert(canRedo); | |
109 auto edit = _redoEdits.back; | |
110 edit.redo; | |
111 _redoEdits.popBack; | |
112 _undoEdits ~= edit; | |
113 | |
114 notifyObservers(); | |
115 } | |
116 | |
117 bool canUndo() const { return !_undoEdits.empty; } | |
118 bool canRedo() const { return !_redoEdits.empty; } | |
119 | |
120 void reset() { | |
121 _undoEdits.length = _redoEdits.length = 0; | |
122 notifyObservers(); | |
123 } | |
124 | |
125 void addObserver(IUndoManagerObserver observer) { | |
126 _observers ~= observer; | |
127 } | |
128 | |
129 void removeObserver(IUndoManagerObserver observer) { | |
130 // NYI | |
131 } | |
132 | |
133 // IUndoManager overrides: | |
134 | |
135 private { | |
136 int _maxUndoLevel; | |
137 Edit[] _undoEdits; | |
138 Edit[] _redoEdits; | |
139 IUndoManagerObserver[] _observers; // FIXME, use a different container | |
140 | |
141 void notifyObservers() { | |
142 foreach (o; _observers) { | |
143 o.undoRedoUpdate(canUndo, canUndo ? _undoEdits.back.description : null, | |
144 canRedo, canRedo ? _redoEdits.back.description : null); | |
145 } | |
146 } | |
147 } | |
148 } |