annotate doodle/core/undo.d @ 49:576b9fba4677

Not much
author "David Bryant <bagnose@gmail.com>"
date Thu, 05 Aug 2010 22:49:41 +0930
parents 14f1c051c35b
children 08ffc44fc21a
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;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
4 import std.date;
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;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
34 d_time _timeStamp;
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;
576b9fba4677 Not much
"David Bryant <bagnose@gmail.com>"
parents: 47
diff changeset
39 _timeStamp = getUTCtime;
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
46
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
42 this(in string description, d_time timeStamp) {
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; }
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
49 d_time 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
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
90 void addEdit(Edit edit) {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
91 _redoEdits.length = 0;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
92
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
93 if (_undoEdits.empty || !_undoEdits.back.merge(edit)) {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
94 _undoEdits ~= edit;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
95 if (_maxUndoLevel >= 0 && _undoEdits.length > _maxUndoLevel) {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
96 _undoEdits.length = _undoEdits.length - 1;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
97 }
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
98 }
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
99
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
100 notifyObservers();
40
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
101 }
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
102
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
103 void undo() {
46
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
104 assert(canUndo);
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
105 auto edit = _undoEdits.back;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
106 edit.undo;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
107 _undoEdits.popBack;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
108 _redoEdits ~= edit;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
109
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
110 notifyObservers();
40
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
111 }
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
112
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
113 void redo() {
46
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
114 assert(canRedo);
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
115 auto edit = _redoEdits.back;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
116 edit.redo;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
117 _redoEdits.popBack;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
118 _undoEdits ~= edit;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
119
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
120 notifyObservers();
40
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
121 }
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
122
46
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
123 bool canUndo() const { return !_undoEdits.empty; }
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
124 bool canRedo() const { return !_redoEdits.empty; }
40
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
125
46
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
126 void reset() {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
127 _undoEdits.length = _redoEdits.length = 0;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
128 notifyObservers();
40
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
129 }
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
130
46
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
131 void addObserver(IUndoManagerObserver observer) {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
132 _observers ~= observer;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
133 }
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
134
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
135 void removeObserver(IUndoManagerObserver observer) {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
136 // NYI
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
137 }
40
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
138
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
139 // IUndoManager overrides:
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
140
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
141 private {
46
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
142 int _maxUndoLevel;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
143 Edit[] _undoEdits;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
144 Edit[] _redoEdits;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
145 IUndoManagerObserver[] _observers; // FIXME, use a different container
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
146
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
147 void notifyObservers() {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
148 foreach (o; _observers) {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
149 o.undoRedoUpdate(canUndo, canUndo ? _undoEdits.back.description : null,
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
150 canRedo, canRedo ? _redoEdits.back.description : null);
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
151 }
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
152 }
40
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
153 }
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
154 }