-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathModels.pas
205 lines (172 loc) · 5.58 KB
/
Models.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
(*
@Abstract(Basic models unit)
(C) 2006-2007 George "Mirage" Bakhtadze. <a href="http://www.casteng.com">www.casteng.com</a> <br>
Created: Apr 04, 2007 <br>
Unit contains basic model (from Model-View-Controller) classes
*)
{$Include GDefines.inc}
unit Models;
interface
uses Basics, Props, BaseMsg;
const
// Operations array grow step
OperationsCapacityStep = 32;
type
// Operation flag
TOperationFlag = (// Operation is in applied state
ofApplied,
// Operation was handled
ofHandled,
// Operation is intermediate and for example should not be added to editor's queue
ofIntermediate
);
TOperationFlags = set of TOperationFlag;
// Auto-inverse operation class
TOperation = class
protected
// Should actually perform the operation. Repeated call should undo all changes made by previous call.
procedure DoApply; virtual; abstract;
// Should try to merge the operations and undo information taking in account applied state
function DoMerge(AOperation: TOperation): Boolean; virtual;
public
// Flag set
Flags: TOperationFlags;
// Applies the operation. Repeated call will undo the operation.
procedure Apply;
// Adds to the operation actions of the given one if possible and returns True if success. Both operations should be in applied or both in unapplied state.
function Merge(AOperation: TOperation): Boolean;
end;
// Operations manager
TOperationManager = class
private
FOperations: array of TOperation;
// Total valid operations
FTotalOperations,
// Last applied operation index
FCurOperation: Integer;
// Last operation applyed (added, undone, redone)
FLastOperation: TOperation;
procedure FreeRange(FromIndex, Count: Integer);
public
constructor Create;
destructor Destroy; override;
// Remove all operations
procedure Clear;
// Add an operation
procedure Add(Operation: TOperation);
// Undo last operations
procedure Undo;
// Redo previously undone operation
procedure Redo;
// Returns True if there is at least one operation to undo
function CanUndo: Boolean;
// Returns True if there is at least one operation to redo
function CanRedo: Boolean;
// Last operation applyed (added, undone, redone)
property LastOperation: TOperation read FLastOperation;
end;
IModel = interface
end;
TModel = class(TInterfacedObject, IModel)
protected
FOperations: TOperation; // Points to last applied operation
public
procedure GetProperties(const Result: TProperties); virtual; abstract;
procedure SetProperties(Properties: TProperties); virtual; abstract;
procedure DoOperation(const Operation: TOperation); virtual; abstract;
property LastOperation: TOperation read FOperations;
end;
// This message usually is sent to core handler when an operation is ready to apply. Default handler will free all unhandled operations.
TOperationMsg = class(TMessage)
Operation: TOperation;
constructor Create(AOperation: TOperation);
end;
implementation
{ TOperationMessage }
constructor TOperationMsg.Create(AOperation: TOperation);
begin
Operation := AOperation;
end;
{ TOperation }
function TOperation.DoMerge(AOperation: TOperation): Boolean;
begin
Result := False;
end;
procedure TOperation.Apply;
begin
DoApply;
if ofApplied in Flags then Exclude(Flags, ofApplied) else Include(Flags, ofApplied);
end;
function TOperation.Merge(AOperation: TOperation): Boolean;
begin
Assert(((ofApplied in Flags) and (ofApplied in AOperation.Flags)) or
(not (ofApplied in Flags) and not (ofApplied in AOperation.Flags)),
'TOperation.Merge: Both operations should be in applied or both in unapplied state');
Result := DoMerge(AOperation);
end;
{ TOperationManager }
procedure TOperationManager.FreeRange(FromIndex, Count: Integer);
var i: Integer;
begin
for i := FromIndex to FromIndex + Count - 1 do begin
if FLastOperation = FOperations[i] then FLastOperation := nil;
FOperations[i].Free;
FOperations[i] := nil;
end;
end;
constructor TOperationManager.Create;
begin
FLastOperation := nil;
FCurOperation := -1;
end;
destructor TOperationManager.Destroy;
begin
Clear;
inherited;
end;
procedure TOperationManager.Clear;
begin
FreeRange(0, FTotalOperations);
FLastOperation := nil;
FCurOperation := -1;
FTotalOperations := 0;
end;
procedure TOperationManager.Add(Operation: TOperation); // o o o X o o
begin
if CanUndo and FOperations[FCurOperation].Merge(Operation) then begin
Operation.Free;
// FOperations[FCurOperation].Apply;
Exit;
end;
Inc(FCurOperation);
if FCurOperation < FTotalOperations-1 then FreeRange(FCurOperation, FTotalOperations - FCurOperation -1);
FTotalOperations := FCurOperation+1;
if Length(FOperations) <= FTotalOperations then SetLength(FOperations, Length(FOperations) + OperationsCapacityStep);
FOperations[FCurOperation] := Operation;
FLastOperation := Operation
end;
procedure TOperationManager.Undo;
begin
if CanUndo then begin
FLastOperation := FOperations[FCurOperation];
FLastOperation.Apply;
Dec(FCurOperation);
end;
end;
procedure TOperationManager.Redo;
begin
if CanRedo then begin
Inc(FCurOperation);
FLastOperation := FOperations[FCurOperation];
FLastOperation.Apply;
end;
end;
function TOperationManager.CanUndo: Boolean;
begin
Result := FCurOperation >= 0;
end;
function TOperationManager.CanRedo: Boolean;
begin
Result := FCurOperation < FTotalOperations-1;
end;
end.