annotate doodle/core/undo.d @ 132:bc5baa585b32

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