source: trunk/ItemList.pas

Last change on this file was 342, checked in by chronos, 5 hours ago
  • Added: More tests.
File size: 16.6 KB
Line 
1unit ItemList;
2
3interface
4
5uses
6 Classes, SysUtils, Generics.Collections, DOM, XML, Common, Graphics, Math;
7
8type
9 TItemList = class;
10 TUndefinedEnum = (eeNone);
11
12 TDataType = (dtNone, dtString, dtBoolean, dtInteger, dtFloat, dtColor,
13 dtTime, dtDate, dtDateTime, dtEnumeration, dtReference);
14
15 { TItemField }
16
17 TItemField = class
18 SysName: string;
19 Name: string;
20 Index: Integer;
21 DataType: TDataType;
22 Position: TPoint;
23 Size: TPoint;
24 EnumStates: TStringList;
25 VisibleIfIndex: Integer;
26 constructor Create;
27 destructor Destroy; override;
28 procedure Assign(Source: TItemField);
29 end;
30
31 { TItemFields }
32
33 TItemFields = class(TObjectList<TItemField>)
34 function AddField(Index: Integer; SysName, Name: string; DataType: TDataType): TItemField;
35 function SearchByIndex(Index: Integer): TItemField;
36 end;
37
38 { TItem }
39
40 TItem = class
41 private
42 procedure AssignValue(Source: TItem; Field: TItemField);
43 function CompareValue(Item: TItem; Field: TItemField): Boolean;
44 procedure LoadValueFromNode(Node: TDOMNode; Field: TItemField); virtual;
45 procedure SaveValueToNode(Node: TDOMNode; Field: TItemField); virtual;
46 public
47 Id: Integer;
48 Name: string;
49 class function GetFields: TItemFields; virtual;
50 function GetField(Index: Integer): TItemField;
51 procedure GetValue(Index: Integer; out Value); virtual;
52 function GetValueInteger(Index: Integer): Integer;
53 function GetValueString(Index: Integer): string;
54 function GetValueColor(Index: Integer): TColor;
55 function GetValueBoolean(Index: Integer): Boolean;
56 function GetValueEnumeration(Index: Integer): TUndefinedEnum;
57 function GetValueReference(Index: Integer): TItem;
58 function GetValueAsText(Index: Integer): string;
59 procedure SetValue(Index: Integer; var Value); virtual;
60 procedure SetValueInteger(Index: Integer; Value: Integer);
61 procedure SetValueString(Index: Integer; Value: string);
62 procedure SetValueColor(Index: Integer; Value: TColor);
63 procedure SetValueBoolean(Index: Integer; Value: Boolean);
64 procedure SetValueEnumeration(Index: Integer; Value: TUndefinedEnum);
65 procedure SetValueReference(Index: Integer; Value: TItem);
66 procedure Assign(Source: TItem); virtual;
67 function Compare(Item: TItem): Boolean; virtual;
68 function ToString: string; override;
69 procedure LoadFromNode(Node: TDOMNode); virtual;
70 procedure SaveToNode(Node: TDOMNode); virtual;
71 class function GetClassSysName: string; virtual;
72 class function GetClassName: string; virtual;
73 function GetReferenceList(Index: Integer): TItemList; virtual;
74 constructor Create; virtual;
75 end;
76
77 TItemClass = class of TItem;
78
79 { TItemList }
80
81 TItemList = class(TObjectList<TItem>)
82 private
83 procedure RecalculateNewId(Reset: Boolean);
84 procedure RecalculateItemsId;
85 public
86 NewId: Integer;
87 class function GetItemClass: TItemClass; virtual;
88 function IncrementName(Name: string): string;
89 function GetNextAvailableName(Name: string): string;
90 function FindById(Id: Integer): TItem;
91 function FindByName(Name: string): TItem;
92 function GetNewId: Integer;
93 function ToString: string; override;
94 procedure Assign(Source: TItemList); virtual;
95 function Compare(ItemList: TItemList): Boolean; virtual;
96 function AddItem(Name: string = ''): TItem; virtual;
97 function CreateItem(Name: string = ''): TItem; virtual;
98 procedure LoadFromNode(Node: TDOMNode); virtual;
99 procedure SaveToNode(Node: TDOMNode); virtual;
100 constructor Create(FreeObjects: Boolean = True);
101 end;
102
103const
104 DataTypeStr: array[TDataType] of string = ('None', 'String', 'Boolean',
105 'Integer', 'Float', 'Color', 'Time', 'Date', 'DateTime', 'Enumeration',
106 'Reference');
107
108resourcestring
109 SUnsupportedDataType = 'Unsupported field value data type %s';
110 SUnsupportedValueIndex = 'Unsupported value index %d';
111
112
113implementation
114
115resourcestring
116 SYes = 'Yes';
117 SNo = 'No';
118 SItem = 'Item';
119 SName = 'Name';
120
121{ TItemField }
122
123constructor TItemField.Create;
124begin
125 EnumStates := TStringList.Create;
126end;
127
128destructor TItemField.Destroy;
129begin
130 FreeAndNil(EnumStates);
131 inherited;
132end;
133
134procedure TItemField.Assign(Source: TItemField);
135begin
136 SysName := Source.SysName;
137 Name := Source.Name;
138 Index := Source.Index;
139 DataType := Source.DataType;
140 Position := Source.Position;
141 Size := Source.Size;
142 EnumStates.Assign(Source.EnumStates);
143end;
144
145{ TItemList }
146
147procedure TItemList.Assign(Source: TItemList);
148var
149 I: Integer;
150begin
151 while Count > Source.Count do Delete(Count - 1);
152 while Count < Source.Count do AddItem('');
153 for I := 0 to Count - 1 do
154 TItem(Items[I]).Assign(Source.Items[I]);
155end;
156
157function TItemList.Compare(ItemList: TItemList): Boolean;
158var
159 I: Integer;
160begin
161 Result := Count = ItemList.Count;
162 if not Result then Exit;
163 for I := 0 to Count - 1 do begin
164 Result := Result and TItem(Items[I]).Compare(ItemList.Items[I]);
165 if not Result then Break;
166 end;
167end;
168
169function TItemList.AddItem(Name: string): TItem;
170begin
171 Result := CreateItem(Name);
172 Result.Id := GetNewId;
173 Add(Result);
174end;
175
176function TItemList.CreateItem(Name: string): TItem;
177begin
178 Result := GetItemClass.Create;
179 Result.Name := Name;
180end;
181
182procedure TItemList.LoadFromNode(Node: TDOMNode);
183var
184 Node2: TDOMNode;
185 NewItem: TItem;
186begin
187 Count := 0;
188 Node2 := Node.FirstChild;
189 while Assigned(Node2) and (Node2.NodeName = UnicodeString(GetItemClass.GetClassSysName)) do begin
190 NewItem := CreateItem;
191 NewItem.LoadFromNode(Node2);
192 Add(NewItem);
193 Node2 := Node2.NextSibling;
194 end;
195end;
196
197procedure TItemList.SaveToNode(Node: TDOMNode);
198var
199 I: Integer;
200 NewNode2: TDOMNode;
201begin
202 RecalculateItemsId;
203 for I := 0 to Count - 1 do
204 with TItem(Items[I]) do begin
205 NewNode2 := Node.OwnerDocument.CreateElement(UnicodeString(GetItemClass.GetClassSysName));
206 Node.AppendChild(NewNode2);
207 SaveToNode(NewNode2);
208 end;
209end;
210
211constructor TItemList.Create(FreeObjects: Boolean);
212begin
213 inherited;
214 NewId := 1;
215end;
216
217procedure TItemList.RecalculateNewId(Reset: Boolean);
218var
219 I: Integer;
220begin
221 NewId := 1;
222 for I := 0 to Count - 1 do
223 with TItem(Items[I]) do begin
224 NewId := Max(NewId, Id + 1);
225 end;
226end;
227
228procedure TItemList.RecalculateItemsId;
229var
230 I: Integer;
231begin
232 for I := 0 to Count - 1 do
233 Items[I].Id := I + 1;
234 NewId := Count + 1;
235end;
236
237class function TItemList.GetItemClass: TItemClass;
238begin
239 Result := TItem;
240end;
241
242function TItemList.IncrementName(Name: string): string;
243var
244 I: Integer;
245 Num: Integer;
246begin
247 I := LastPos(' ', Name);
248 if I > 0 then begin
249 if TryStrToInt(Copy(Name, I + 1, Length(Name)), Num) then
250 Result := Trim(Copy(Name, 1, I - 1)) + ' ' + IntToStr(Num + 1)
251 else Result := Name + ' 2';
252 end else Result := Name + ' 2';
253end;
254
255function TItemList.GetNextAvailableName(Name: string): string;
256begin
257 Result := Name;
258 while Assigned(FindByName(Result)) do
259 Result := IncrementName(Result);
260end;
261
262function TItemList.FindById(Id: Integer): TItem;
263var
264 I: Integer;
265begin
266 I := 0;
267 while (I < Count) and (Items[I].Id <> Id) do Inc(I);
268 if I < Count then Result := Items[I]
269 else Result := nil;
270end;
271
272function TItemList.FindByName(Name: string): TItem;
273var
274 I: Integer;
275begin
276 I := 0;
277 while (I < Count) and (Items[I].Name <> Name) do Inc(I);
278 if I < Count then Result := Items[I]
279 else Result := nil;
280end;
281
282function TItemList.GetNewId: Integer;
283begin
284 Result := NewId;
285 Inc(NewId);
286end;
287
288function TItemList.ToString: string;
289var
290 I: Integer;
291begin
292 Result := '';
293 for I := 0 to Count - 1 do
294 with TItem(Items[I]) do begin
295 Result := Result + ToString + LineEnding;
296 end;
297end;
298
299{ TItemFields }
300
301function TItemFields.AddField(Index: Integer; SysName, Name: string; DataType: TDataType): TItemField;
302begin
303 Result := TItemField.Create;
304 Result.Index := Index;
305 Result.Name := Name;
306 Result.SysName := SysName;
307 Result.DataType := DataType;
308 Add(Result);
309end;
310
311function TItemFields.SearchByIndex(Index: Integer): TItemField;
312var
313 I: Integer;
314begin
315 I := 0;
316 while (I < Count) and (Items[I].Index <> Index) do Inc(I);
317 if I < Count then Result := Items[I]
318 else Result := nil;
319end;
320
321{ TItem }
322
323procedure TItem.AssignValue(Source: TItem; Field: TItemField);
324begin
325 if Field.DataType = dtString then begin
326 SetValueString(Field.Index, Source.GetValueString(Field.Index));
327 end else
328 if Field.DataType = dtColor then begin
329 SetValueColor(Field.Index, Source.GetValueColor(Field.Index));
330 end else
331 if Field.DataType = dtInteger then begin
332 SetValueInteger(Field.Index, Source.GetValueInteger(Field.Index));
333 end else
334 if Field.DataType = dtBoolean then begin
335 SetValueBoolean(Field.Index, Source.GetValueBoolean(Field.Index));
336 end else
337 if Field.DataType = dtEnumeration then begin
338 SetValueEnumeration(Field.Index, Source.GetValueEnumeration(Field.Index));
339 end else
340 if Field.DataType = dtReference then begin
341 SetValueReference(Field.Index, Source.GetValueReference(Field.Index));
342 end else
343 raise Exception.Create(Format(SUnsupportedDataType, [DataTypeStr[Field.DataType]]));
344end;
345
346function TItem.CompareValue(Item: TItem; Field: TItemField): Boolean;
347begin
348 if Field.DataType = dtString then begin
349 Result := GetValueString(Field.Index) = Item.GetValueString(Field.Index);
350 end else
351 if Field.DataType = dtColor then begin
352 Result := GetValueColor(Field.Index) = Item.GetValueColor(Field.Index);
353 end else
354 if Field.DataType = dtInteger then begin
355 Result := GetValueInteger(Field.Index) = Item.GetValueInteger(Field.Index);
356 end else
357 if Field.DataType = dtBoolean then begin
358 Result := GetValueBoolean(Field.Index) = Item.GetValueBoolean(Field.Index);
359 end else
360 if Field.DataType = dtEnumeration then begin
361 Result := GetValueEnumeration(Field.Index) = Item.GetValueEnumeration(Field.Index);
362 end else
363 if Field.DataType = dtReference then begin
364 Result := GetValueReference(Field.Index) = Item.GetValueReference(Field.Index);
365 end else
366 raise Exception.Create(Format(SUnsupportedDataType, [DataTypeStr[Field.DataType]]));
367end;
368
369procedure TItem.LoadValueFromNode(Node: TDOMNode; Field: TItemField);
370var
371 ReadId: Integer;
372 ReferenceList: TItemList;
373begin
374 if Field.DataType = dtString then begin
375 SetValueString(Field.Index, ReadString(Node, Field.SysName, ''));
376 end else
377 if Field.DataType = dtColor then begin
378 SetValueColor(Field.Index, ReadInteger(Node, Field.SysName, 0));
379 end else
380 if Field.DataType = dtInteger then begin
381 SetValueInteger(Field.Index, ReadInteger(Node, Field.SysName, 0));
382 end else
383 if Field.DataType = dtBoolean then begin
384 SetValueBoolean(Field.Index, ReadBoolean(Node, Field.SysName, False));
385 end else
386 if Field.DataType = dtEnumeration then begin
387 SetValueEnumeration(Field.Index, TUndefinedEnum(ReadInteger(Node, Field.SysName, 0)));
388 end else
389 if Field.DataType = dtReference then begin
390 ReadId := ReadInteger(Node, Field.SysName, 0);
391 ReferenceList := GetReferenceList(Field.Index);
392 if (ReadId > 0) and Assigned(ReferenceList) then
393 SetValueReference(Field.Index, TItem(ReferenceList[ReadId]));
394 end else
395 raise Exception.Create(Format(SUnsupportedDataType, [DataTypeStr[Field.DataType]]));
396end;
397
398procedure TItem.SaveValueToNode(Node: TDOMNode; Field: TItemField);
399var
400 Item: TItem;
401begin
402 if Field.DataType = dtString then begin
403 WriteString(Node, Field.SysName, GetValueString(Field.Index));
404 end else
405 if Field.DataType = dtColor then begin
406 WriteInteger(Node, Field.SysName, GetValueColor(Field.Index));
407 end else
408 if Field.DataType = dtInteger then begin
409 WriteInteger(Node, Field.SysName, GetValueInteger(Field.Index));
410 end else
411 if Field.DataType = dtBoolean then begin
412 WriteBoolean(Node, Field.SysName, GetValueBoolean(Field.Index));
413 end else
414 if Field.DataType = dtEnumeration then begin
415 WriteInteger(Node, Field.SysName, Integer(GetValueEnumeration(Field.Index)));
416 end else
417 if Field.DataType = dtReference then begin
418 Item := TItem(GetValueReference(Field.Index));
419 if Assigned(Item) then WriteInteger(Node, Field.SysName, Item.Id)
420 else WriteInteger(Node, Field.SysName, 0);
421 end else
422 raise Exception.Create(Format(SUnsupportedDataType, [DataTypeStr[Field.DataType]]));
423end;
424
425class function TItem.GetFields: TItemFields;
426begin
427 Result := TItemFields.Create;
428 Result.AddField(1, 'Name', SName, dtString);
429end;
430
431function TItem.GetField(Index: Integer): TItemField;
432var
433 Fields: TItemFields;
434begin
435 Result := TItemField.Create;
436 Fields := GetFields;
437 try
438 Result.Assign(Fields.SearchByIndex(Index));
439 finally
440 Fields.Free;
441 end;
442end;
443
444procedure TItem.GetValue(Index: Integer; out Value);
445begin
446 raise Exception.Create(Format(SUnsupportedValueIndex, [Index]));
447end;
448
449function TItem.GetValueString(Index: Integer): string;
450begin
451 GetValue(Index, Result);
452end;
453
454function TItem.GetValueInteger(Index: Integer): Integer;
455begin
456 GetValue(Index, Result);
457end;
458
459function TItem.GetValueColor(Index: Integer): TColor;
460begin
461 GetValue(Index, Result);
462end;
463
464function TItem.GetValueBoolean(Index: Integer): Boolean;
465begin
466 GetValue(Index, Result);
467end;
468
469function TItem.GetValueEnumeration(Index: Integer): TUndefinedEnum;
470begin
471 GetValue(Index, Result);
472end;
473
474function TItem.GetValueReference(Index: Integer): TItem;
475begin
476 GetValue(Index, Result);
477end;
478
479function TItem.GetValueAsText(Index: Integer): string;
480var
481 Field: TItemField;
482 Item: TItem;
483begin
484 Field := GetField(Index);
485 try
486 if Field.DataType = dtInteger then Result := IntToStr(GetValueInteger(Index))
487 else if Field.DataType = dtString then Result := GetValueString(Index)
488 else if Field.DataType = dtColor then Result := ''
489 else if Field.DataType = dtEnumeration then Result := Field.EnumStates[Integer(GetValueEnumeration(Index))]
490 else if Field.DataType = dtReference then begin
491 Item := TItem(GetValueReference(Index));
492 if Assigned(Item) then Result := Item.Name
493 else Result := '';
494 end else if Field.DataType = dtBoolean then begin
495 if GetValueBoolean(Index) then Result := SYes else Result := SNo;
496 end else
497 raise Exception.Create(Format(SUnsupportedDataType, [DataTypeStr[Field.DataType]]));
498 finally
499 Field.Free;
500 end;
501end;
502
503procedure TItem.SetValue(Index: Integer; var Value);
504begin
505 raise Exception.Create(Format(SUnsupportedValueIndex, [Index]));
506end;
507
508procedure TItem.SetValueInteger(Index: Integer; Value: Integer);
509begin
510 SetValue(Index, Value);
511end;
512
513procedure TItem.SetValueString(Index: Integer; Value: string);
514begin
515 SetValue(Index, Value);
516end;
517
518procedure TItem.SetValueColor(Index: Integer; Value: TColor);
519begin
520 SetValue(Index, Value);
521end;
522
523procedure TItem.SetValueBoolean(Index: Integer; Value: Boolean);
524begin
525 SetValue(Index, Value);
526end;
527
528procedure TItem.SetValueEnumeration(Index: Integer;
529 Value: TUndefinedEnum);
530begin
531 SetValue(Index, Value);
532end;
533
534procedure TItem.SetValueReference(Index: Integer; Value: TItem);
535begin
536 SetValue(Index, Value);
537end;
538
539procedure TItem.Assign(Source: TItem);
540var
541 I: Integer;
542 Fields: TItemFields;
543begin
544 Id := Source.Id;
545 if Source is ClassType then begin
546 Fields := GetFields;
547 try
548 for I := 0 to Fields.Count - 1 do
549 AssignValue(Source, Fields[I]);
550 finally
551 Fields.Free;
552 end;
553 end;
554end;
555
556function TItem.Compare(Item: TItem): Boolean;
557var
558 I: Integer;
559 Fields: TItemFields;
560begin
561 Result := True;
562 Result := Result and (Id = Item.Id);
563 if Item is ClassType then begin
564 Fields := GetFields;
565 try
566 for I := 0 to Fields.Count - 1 do begin
567 Result := Result and CompareValue(Item, Fields[I]);
568 if not Result then Break;
569 end;
570 finally
571 Fields.Free;
572 end;
573 end;
574end;
575
576function TItem.ToString: string;
577var
578 Fields: TItemFields;
579 I: Integer;
580begin
581 Result := 'Id: ' + IntToStr(Id) + LineEnding;
582 Fields := GetFields;
583 try
584 for I := 0 to Fields.Count - 1 do begin
585 Result := Result + Fields[I].SysName + ': ' + GetValueAsText(Fields[I].Index) + LineEnding;
586 end;
587 finally
588 Fields.Free;
589 end;
590end;
591
592procedure TItem.LoadFromNode(Node: TDOMNode);
593var
594 Fields: TItemFields;
595 I: Integer;
596begin
597 Id := ReadInteger(Node, 'Id', 0);
598 Fields := GetFields;
599 try
600 for I := 0 to Fields.Count - 1 do begin
601 LoadValueFromNode(Node, Fields[I]);
602 end;
603 finally
604 Fields.Free;
605 end;
606end;
607
608procedure TItem.SaveToNode(Node: TDOMNode);
609var
610 Fields: TItemFields;
611 I: Integer;
612begin
613 WriteInteger(Node, 'Id', Id);
614 Fields := GetFields;
615 try
616 for I := 0 to Fields.Count - 1 do begin
617 SaveValueToNode(Node, Fields[I]);
618 end;
619 finally
620 Fields.Free;
621 end;
622end;
623
624class function TItem.GetClassSysName: string;
625begin
626 Result := 'Item';
627end;
628
629class function TItem.GetClassName: string;
630begin
631 Result := SItem;
632end;
633
634function TItem.GetReferenceList(Index: Integer): TItemList;
635begin
636 Result := nil;
637end;
638
639constructor TItem.Create;
640begin
641end;
642
643end.
644
Note: See TracBrowser for help on using the repository browser.