annotate 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
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
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
37 this(in string description, d_time timeStamp) {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
38 assert(description);
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
39 _description = description;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
40 _timeStamp = timeStamp;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
41 }
40
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
42
46
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
43 string description() const { return _description; }
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
44 d_time timeStamp() const { return _timeStamp; }
40
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
45
46
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
46 final bool merge(Edit subsequent) {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
47 if (mergeImpl(subsequent)) {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
48 // Adopt the new timestamp and description
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
49 _timeStamp = subsequent._timeStamp;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
50 _description = subsequent._description;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
51 return true;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
52 }
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
53 else {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
54 return false;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
55 }
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
56 }
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
57
40
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
58 void undo();
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
59 void redo();
46
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
60 protected bool mergeImpl(Edit other) { return false; }
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
61 }
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
62
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
63 interface IUndoManagerObserver {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
64 // Each description is null if the associated bool is false
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
65 void undoRedoUpdate(in bool canUndo, in string undoDescription,
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
66 in bool canRedo, in string redoDescription);
40
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
67 }
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
68
46
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
69 interface IUndoManager {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
70 void addEdit(Edit edit);
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
71 void undo();
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
72 void redo();
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
73 void reset();
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
74
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
75 void addObserver(IUndoManagerObserver observer);
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
76 void removeObserver(IUndoManagerObserver observer);
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
77 }
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
78
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
79 class UndoManager : IUndoManager {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
80 this(int maxUndoLevel = -1) {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
81 _maxUndoLevel = maxUndoLevel;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
82 }
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
83
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
84 void addEdit(Edit edit) {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
85 _redoEdits.length = 0;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
86
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
87 if (_undoEdits.empty || !_undoEdits.back.merge(edit)) {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
88 _undoEdits ~= edit;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
89 if (_maxUndoLevel >= 0 && _undoEdits.length > _maxUndoLevel) {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
90 _undoEdits.length = _undoEdits.length - 1;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
91 }
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
92 }
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
93
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
94 notifyObservers();
40
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
95 }
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
96
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
97 void undo() {
46
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
98 assert(canUndo);
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
99 auto edit = _undoEdits.back;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
100 edit.undo;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
101 _undoEdits.popBack;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
102 _redoEdits ~= edit;
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 redo() {
46
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
108 assert(canRedo);
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
109 auto edit = _redoEdits.back;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
110 edit.redo;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
111 _redoEdits.popBack;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
112 _undoEdits ~= 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
46
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
117 bool canUndo() const { return !_undoEdits.empty; }
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
118 bool canRedo() const { return !_redoEdits.empty; }
40
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
119
46
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
120 void reset() {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
121 _undoEdits.length = _redoEdits.length = 0;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
122 notifyObservers();
40
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
123 }
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
124
46
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
125 void addObserver(IUndoManagerObserver observer) {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
126 _observers ~= observer;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
127 }
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
128
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
129 void removeObserver(IUndoManagerObserver observer) {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
130 // NYI
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
131 }
40
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
132
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
133 // IUndoManager overrides:
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
134
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
135 private {
46
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
136 int _maxUndoLevel;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
137 Edit[] _undoEdits;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
138 Edit[] _redoEdits;
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
139 IUndoManagerObserver[] _observers; // FIXME, use a different container
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
140
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
141 void notifyObservers() {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
142 foreach (o; _observers) {
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
143 o.undoRedoUpdate(canUndo, canUndo ? _undoEdits.back.description : null,
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
144 canRedo, canRedo ? _redoEdits.back.description : null);
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
145 }
ad3ba55ae57b Undo framework mostly complete
daveb
parents: 40
diff changeset
146 }
40
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
147 }
1f97022e5c6d Checkpoint. Development continues...
daveb
parents:
diff changeset
148 }