123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749 |
- //
- // The graphics engine GXScene
- //
- unit GXS.XCollection;
- (* A polymorphism-enabled TCollection-like set of classes *)
- { .$DEFINE DEBUG_XCOLLECTION } // on define the most apps will not work
- interface
- {$I Stage.Defines.inc}
- uses
- System.Classes,
- System.SysUtils,
- System.Types,
- GXS.PersistentClasses,
- Stage.Strings
- {$IFDEF DEBUG_XCOLLECTION}, System.TypInfo {$ENDIF};
- type
- TXCollection = class;
- EFilerException = class(Exception)
- end;
- (* Base class for implementing a XCollection item.
- NOTES :
- Don't forget to override the ReadFromFiler/WriteToFiler persistence
- methods if you add data in a subclass !
- Subclasses must be registered using the RegisterXCollectionItemClass
- function for proper operation *)
- TXCollectionItem = class(TgxInterfacedPersistent)
- private
- FOwner: TXCollection;
- FName: string;
- protected
- function GetName: string; virtual;
- procedure SetName(const val: String); virtual;
- function GetOwner: TPersistent; override;
- // Override this function to write subclass data
- procedure WriteToFiler(writer: TWriter); virtual;
- // Override this function to read subclass data
- procedure ReadFromFiler(reader: TReader); virtual;
- // Override to perform things when owner form has been loaded
- procedure Loaded; dynamic;
- // Triggers an EFilerException with appropriate version message
- procedure RaiseFilerException(const archiveVersion: integer);
- public
- constructor Create(aOwner: TXCollection); virtual;
- destructor Destroy; override;
- function GetNamePath: string; override;
- property Owner: TXCollection read FOwner;
- // Default implementation uses WriteToFiler/ReadFromFiler
- procedure Assign(Source: TPersistent); override;
- procedure MoveUp;
- procedure MoveDown;
- function Index: integer;
- (* Returns a user-friendly denomination for the class.
- This denomination is used for picking a texture image class
- in the IDE expert *)
- class function FriendlyName: String; virtual; abstract;
- (* Returns a user-friendly description for the class.
- This denomination is used for helping the user when picking a
- texture image class in the IDE expert. If it's not overriden,
- takes its value from FriendlyName. *)
- class function FriendlyDescription: String; virtual;
- (* Category of the item class.
- This is a free string, it will used by the XCollectionEditor to
- regroup collectionitems and menu items *)
- class function ItemCategory: string; virtual;
- (* If true only one such XCollectionItem is allowed per BaseSceneObject.
- Inheritance is accounted for UniqueXCollectionItem resolution, ie.
- if TClassA is unique, and TClassB is a subclass of TClassA,
- whatever the unicity of TClassB, TClassA and TClassB won't be allowed
- to mix (since TClassB is a TClassA, and TClassA is unique).
- Attempting to break the unicity rules will not be possible at
- design-time (in Delphi IDE) and will trigger an exception at run-time. *)
- class function UniqueItem: Boolean; virtual;
- (* Allows the XCollectionItem class to determine if it should be allowed
- to be added to the given collection. *)
- class function CanAddTo(collection: TXCollection): Boolean; virtual;
- published
- property Name: string read FName write SetName;
- end;
- TXCollectionItemClass = class of TXCollectionItem;
- (* Holds a list of TXCollectionItem objects.
- This class looks a lot like a polymorphic-enabled TCollection, it is
- a much stripped down version of a proprietary TObjectList,
- if the copyrights are ever partially lifted
- on the originals, I'll base this code on them since they are way faster
- than VCL's lists and persistence mechanisms with polymorphism-support
- and full backward compatibility *)
- TXCollection = class(TPersistent)
- private
- FOwner: TPersistent;
- FList: TList;
- FCount: integer;
- // Archive Version is used to update the way data items is loaded
- FArchiveVersion: integer;
- protected
- function GetItems(Index: integer): TXCollectionItem;
- function GetOwner: TPersistent; override;
- procedure ReadFromFiler(reader: TReader);
- (* Here, we write all listed XCollection through their WriteToFiler methods,
- but to be able to restore them, we also write their classname, and to
- avoid wasting space if the same class appears multiple times we build up
- a lookup table while writing them, if the class is anew, the name is
- written, otherwise, only the index in the table is written.
- Using a global lookup table (instead of a "per-WriteData" one) could save
- more space, but would also increase dependencies, and this I don't want 8)*)
- procedure WriteToFiler(writer: TWriter);
- public
- constructor Create(aOwner: TPersistent); virtual;
- destructor Destroy; override;
- procedure Assign(Source: TPersistent); override;
- procedure Loaded;
- property Owner: TPersistent read FOwner write FOwner;
- function GetNamePath: string; override;
- (* Class of the items.
- Unlike TCollection, items can be of ItemsClass OR ANY of its
- subclasses, ie. this function is used only for asserting your adding
- objects of the right class, and not for persistence. *)
- class function ItemsClass: TXCollectionItemClass; virtual;
- property Items[index: integer]: TXCollectionItem read GetItems; default;
- property Count: integer read FCount;
- function Add(anItem: TXCollectionItem): integer;
- function GetOrCreate(anItem: TXCollectionItemClass): TXCollectionItem;
- procedure Delete(Index: integer);
- procedure Remove(anItem: TXCollectionItem);
- procedure Clear;
- function IndexOf(anItem: TXCollectionItem): integer;
- // Returns the index of the first XCollectionItem of the given class (or -1)
- function IndexOfClass(aClass: TXCollectionItemClass): integer;
- // Returns the first XCollection of the given class (or nil)
- function GetByClass(aClass: TXCollectionItemClass): TXCollectionItem;
- // Returns the index of the first XCollectionItem of the given name (or -1)
- function IndexOfName(const aName: string): integer;
- (* Indicates if an object of the given class can be added.
- This function is used to enforce Unique XCollection. *)
- function CanAdd(aClass: TXCollectionItemClass): Boolean; virtual;
- property ArchiveVersion: integer read FArchiveVersion;
- end;
- // Registers an event to be called when an XCollection is destroyed
- procedure RegisterXCollectionDestroyEvent(notifyEvent: TNotifyEvent);
- // DeRegisters event
- procedure DeRegisterXCollectionDestroyEvent(notifyEvent: TNotifyEvent);
- // Registers a TXCollectionItem subclass for persistence requirements
- procedure RegisterXCollectionItemClass(aClass: TXCollectionItemClass);
- // Removes a TXCollectionItem subclass from the list
- procedure UnregisterXCollectionItemClass(aClass: TXCollectionItemClass);
- // Retrieves a registered TXCollectionItemClass from its classname
- function FindXCollectionItemClass(const ClassName: string): TXCollectionItemClass;
- (* Creates and returns a copy of internal list of TXCollectionItem classes.
- Returned list should be freed by caller, the parameter defines an ancestor
- class filter. If baseClass is left nil, TXCollectionItem is used as ancestor. *)
- function GetXCollectionItemClassesList(baseClass: TXCollectionItemClass = nil): TList;
- procedure GetXCollectionClassesList(var ClassesList: TList;
- baseClass: TXCollectionItemClass = nil);
- implementation // -------------------------------------------------------------
- const
- (* Magic is a workaround that will allow us to know
- if the archive version is 0 (equivalent to:
- there is no ArchiveVersion stored in the DFM file) *)
- MAGIC: array [0 .. 3] of AnsiChar = 'XCOL';
- var
- vXCollectionItemClasses: TList;
- vXCollectionDestroyEvent: TNotifyEvent;
- // ---------- internal global routines (used by xcollection editor) -------------
- procedure RegisterXCollectionDestroyEvent(notifyEvent: TNotifyEvent);
- begin
- vXCollectionDestroyEvent := notifyEvent;
- end;
- procedure DeRegisterXCollectionDestroyEvent(notifyEvent: TNotifyEvent);
- begin
- vXCollectionDestroyEvent := nil;
- end;
- // ------------------------------------------------------------------------------
- procedure RegisterXCollectionItemClass(aClass: TXCollectionItemClass);
- begin
- if not Assigned(vXCollectionItemClasses) then
- vXCollectionItemClasses := TList.Create;
- if vXCollectionItemClasses.IndexOf(aClass) < 0 then
- vXCollectionItemClasses.Add(aClass);
- end;
- procedure UnregisterXCollectionItemClass(aClass: TXCollectionItemClass);
- begin
- if not Assigned(vXCollectionItemClasses) then
- exit;
- if vXCollectionItemClasses.IndexOf(aClass) >= 0 then
- vXCollectionItemClasses.Remove(aClass);
- end;
- function FindXCollectionItemClass(const ClassName: String): TXCollectionItemClass;
- var
- i: integer;
- begin
- Result := nil;
- if Assigned(vXCollectionItemClasses) then
- for i := 0 to vXCollectionItemClasses.Count - 1 do
- if TXCollectionItemClass(vXCollectionItemClasses[i]).ClassName = ClassName
- then
- begin
- Result := TXCollectionItemClass(vXCollectionItemClasses[i]);
- Break;
- end;
- end;
- function GetXCollectionItemClassesList(baseClass: TXCollectionItemClass = nil): TList;
- begin
- Result := TList.Create;
- GetXCollectionClassesList(Result, baseClass);
- end;
- procedure GetXCollectionClassesList(var ClassesList: TList;
- baseClass: TXCollectionItemClass = nil);
- var
- i: integer;
- begin
- if not Assigned(baseClass) then
- baseClass := TXCollectionItem;
- if Assigned(vXCollectionItemClasses) then
- for i := 0 to vXCollectionItemClasses.Count - 1 do
- if TXCollectionItemClass(vXCollectionItemClasses[i]).InheritsFrom(baseClass) then
- ClassesList.Add(vXCollectionItemClasses[i]);
- end;
- // ------------------
- // ------------------ TXCollectionItem ------------------
- // ------------------
- constructor TXCollectionItem.Create(aOwner: TXCollection);
- begin
- inherited Create;
- FOwner := aOwner;
- if Assigned(aOwner) then
- begin
- Assert(aOwner.CanAdd(TXCollectionItemClass(Self.ClassType)),
- 'Addition of ' + Self.ClassName + ' to ' + aOwner.ClassName +
- ' rejected.');
- aOwner.FList.Add(Self);
- aOwner.FCount := aOwner.FList.Count;
- end;
- FName := FriendlyName;
- end;
- destructor TXCollectionItem.Destroy;
- begin
- if Assigned(FOwner) then
- begin
- FOwner.FList.Remove(Self);
- FOwner.FCount := FOwner.FList.Count;
- end;
- inherited Destroy;
- end;
- procedure TXCollectionItem.Assign(Source: TPersistent);
- begin
- if Source is TXCollectionItem then
- begin
- FName := TXCollectionItem(Source).Name;
- end
- else
- inherited Assign(Source);
- end;
- procedure TXCollectionItem.SetName(const val: string);
- begin
- FName := val;
- end;
- function TXCollectionItem.GetOwner: TPersistent;
- begin
- Result := FOwner;
- end;
- procedure TXCollectionItem.WriteToFiler(writer: TWriter);
- begin
- with writer do
- begin
- WriteInteger(0); // Archive Version 0
- WriteString(FName);
- end;
- end;
- procedure TXCollectionItem.ReadFromFiler(reader: TReader);
- {$IFOPT C+}
- var
- ver: integer;
- {$ENDIF}
- begin
- with reader do
- begin
- {$IFOPT C+}
- ver := ReadInteger;
- Assert(ver = 0);
- {$ENDIF}
- FName := ReadString;
- end;
- end;
- procedure TXCollectionItem.Loaded;
- begin
- // does nothing by default
- end;
- function TXCollectionItem.GetName: string;
- begin
- Result := FName;
- end;
- function TXCollectionItem.GetNamePath: string;
- begin
- if FOwner <> nil then
- Result := Format('%s[%d]', [FOwner.GetNamePath, Index])
- else
- Result := inherited GetNamePath;
- end;
- procedure TXCollectionItem.MoveUp;
- var
- i: integer;
- begin
- if Assigned(Owner) then
- begin
- i := Owner.FList.IndexOf(Self);
- if i > 0 then
- Owner.FList.Exchange(i, i - 1);
- end;
- end;
- procedure TXCollectionItem.MoveDown;
- var
- i: integer;
- begin
- if Assigned(Owner) then
- begin
- i := Owner.FList.IndexOf(Self);
- if Cardinal(i) < Cardinal(Owner.FList.Count - 1) then
- Owner.FList.Exchange(i, i + 1);
- end;
- end;
- function TXCollectionItem.Index: integer;
- begin
- if Assigned(Owner) then
- Result := Owner.FList.IndexOf(Self)
- else
- Result := -1;
- end;
- procedure TXCollectionItem.RaiseFilerException(const archiveVersion: integer);
- begin
- raise EFilerException.Create(ClassName + strUnknownArchiveVersion +
- IntToStr(archiveVersion));
- end;
- class function TXCollectionItem.FriendlyDescription: string;
- begin
- Result := FriendlyName;
- end;
- class function TXCollectionItem.ItemCategory: string;
- begin
- Result := '';
- end;
- class function TXCollectionItem.UniqueItem: Boolean;
- begin
- Result := False;
- end;
- class function TXCollectionItem.CanAddTo(collection: TXCollection): Boolean;
- begin
- Result := True;
- end;
- // ------------------
- // ------------------ TXCollection ------------------
- // ------------------
- constructor TXCollection.Create(aOwner: TPersistent);
- begin
- inherited Create;
- FOwner := aOwner;
- FList := TList.Create;
- end;
- destructor TXCollection.Destroy;
- begin
- if Assigned(vXCollectionDestroyEvent) then
- vXCollectionDestroyEvent(Self);
- Clear;
- FList.Free;
- inherited Destroy;
- end;
- procedure TXCollection.Assign(Source: TPersistent);
- var
- i: integer;
- srcItem, newItem: TXCollectionItem;
- begin
- if not Assigned(Source) then
- begin
- Clear;
- end
- else if Source.ClassType = Self.ClassType then
- begin
- Clear;
- FList.Capacity := TXCollection(Source).FList.Count;
- for i := 0 to TXCollection(Source).Count - 1 do
- begin
- srcItem := TXCollectionItem(TXCollection(Source).FList[i]);
- newItem := TXCollectionItemClass(srcItem.ClassType).Create(Self);
- newItem.Assign(srcItem);
- end;
- end
- else
- inherited Assign(Source);
- FCount := FList.Count;
- end;
- procedure TXCollection.Loaded;
- var
- i: integer;
- begin
- for i := 0 to FList.Count - 1 do
- TXCollectionItem(FList[i]).Loaded;
- end;
- procedure TXCollection.WriteToFiler(writer: TWriter);
- var
- i, n: integer;
- classList: TList;
- XCollectionItem: TXCollectionItem;
- begin
- FArchiveVersion := 1;
- classList := TList.Create;
- try
- with writer do
- begin
- // Magic header and archive version are always written now
- WriteInteger(PInteger(@MAGIC[0])^);
- WriteInteger(FArchiveVersion);
- WriteInteger(FList.Count);
- for i := 0 to FList.Count - 1 do
- begin
- XCollectionItem := TXCollectionItem(FList[i]);
- n := classList.IndexOf(XCollectionItem.ClassType);
- if n < 0 then
- begin
- WriteString(XCollectionItem.ClassName);
- classList.Add(XCollectionItem.ClassType);
- end
- else
- WriteInteger(n);
- XCollectionItem.WriteToFiler(writer);
- end;
- end;
- finally
- classList.Free;
- end;
- end;
- procedure TXCollection.ReadFromFiler(reader: TReader);
- var
- vt: TValueType;
- Header: array [0 .. 3] of AnsiChar;
- n, lc, lcnum: integer;
- classList: TList;
- cName: string;
- XCollectionItemClass: TXCollectionItemClass;
- XCollectionItem: TXCollectionItem;
- begin
- // see WriteData for a description of what is going on here
- Clear;
- classList := TList.Create;
- try
- with reader do
- begin
- // save current reader position, it will be used to rewind the reader if the DFM is too old
- try
- vt := NextValue;
- if vt in [vaInt32, vaInt16, vaInt8] then
- PInteger(@Header[0])^ := ReadInteger
- else
- begin
- Read(Header[0], Length(Header));
- end;
- except
- Header[0] := #0;
- Header[1] := #0;
- Header[2] := #0;
- Header[3] := #0;
- end;
- // after reading the header, we need to compare it with the MAGIC reference
- if (Header[0] = MAGIC[0]) and (Header[1] = MAGIC[1]) and
- (Header[2] = MAGIC[2]) and (Header[3] = MAGIC[3]) then
- begin
- // if its ok we can just read the archive version
- FArchiveVersion := ReadInteger;
- lc := ReadInteger;
- end
- else
- begin
- // if the header is invalid (old DFM) just assume archive version is 0 and rewind reader
- FArchiveVersion := 0;
- lc := PInteger(@Header[0])^;
- end;
- for n := 1 to lc do
- begin
- if NextValue in [vaString, vaLString] then
- begin
- cName := ReadString;
- {$IFDEF DEBUG_XCOLLECTION}
- writeln('TXCollection.ReadFromFiler create class entry: ', cName);
- {$ENDIF}
- XCollectionItemClass := FindXCollectionItemClass(cName);
- Assert(Assigned(XCollectionItemClass),
- 'Class ' + cName +
- ' unknown. Add the relevant unit to your "uses".');
- classList.Add(XCollectionItemClass);
- end
- else
- begin
- {$IFDEF DEBUG_XCOLLECTION}
- Assert(NextValue in [vaInt8, vaInt16, vaInt32],
- 'Non-Integer ValueType: ' + GetEnumName(TypeInfo(TValueType),
- Ord(NextValue)));
- {$ENDIF}
- lcnum := ReadInteger;
- Assert((lcnum >= 0) and (lcnum < classList.Count),
- 'Invalid classlistIndex: ' + IntToStr(lcnum));
- XCollectionItemClass := TXCollectionItemClass(classList[lcnum]);
- {$IFDEF DEBUG_XCOLLECTION}
- writeln('TXCollection.ReadFromFiler create by number: ', lcnum,
- ' -> ', XCollectionItemClass.ClassName);
- {$ENDIF}
- end;
- if Assigned(XCollectionItemClass) then
- begin
- XCollectionItem := XCollectionItemClass.Create(Self);
- XCollectionItem.ReadFromFiler(reader);
- end;
- end;
- end;
- finally
- classList.Free;
- end;
- FCount := FList.Count;
- end;
- class function TXCollection.ItemsClass: TXCollectionItemClass;
- begin
- Result := TXCollectionItem;
- end;
- function TXCollection.GetItems(Index: integer): TXCollectionItem;
- begin
- Result := TXCollectionItem(FList[index]);
- end;
- function TXCollection.GetOwner: TPersistent;
- begin
- Result := FOwner;
- end;
- function TXCollection.GetNamePath: String;
- var
- s: String;
- begin
- Result := ClassName;
- if GetOwner = nil then
- exit;
- s := GetOwner.GetNamePath;
- if s = '' then
- exit;
- Result := s + '.XCollection';
- end;
- function TXCollection.Add(anItem: TXCollectionItem): integer;
- begin
- Assert(anItem.InheritsFrom(ItemsClass));
- Assert(CanAdd(TXCollectionItemClass(anItem.ClassType)));
- if Assigned(anItem.FOwner) then
- begin
- anItem.FOwner.FList.Remove(anItem);
- anItem.FOwner.FCount := anItem.FOwner.FList.Count;
- end;
- anItem.FOwner := Self;
- Result := FList.Add(anItem);
- FCount := FList.Count;
- end;
- function TXCollection.GetOrCreate(anItem: TXCollectionItemClass)
- : TXCollectionItem;
- var
- i: integer;
- begin
- Assert(anItem.InheritsFrom(ItemsClass));
- i := Self.IndexOfClass(anItem);
- if i >= 0 then
- Result := TXCollectionItem(Self[i])
- else
- Result := anItem.Create(Self);
- end;
- procedure TXCollection.Delete(Index: integer);
- begin
- Assert(Cardinal(index) < Cardinal(FList.Count));
- // doin' it the fast way
- with TXCollectionItem(FList[index]) do
- begin
- FOwner := nil;
- Free;
- end;
- FList.Delete(index);
- FCount := FList.Count;
- end;
- procedure TXCollection.Remove(anItem: TXCollectionItem);
- var
- i: integer;
- begin
- i := IndexOf(anItem);
- if i >= 0 then
- Delete(i);
- end;
- procedure TXCollection.Clear;
- var
- i: integer;
- begin
- // Fast kill of owned XCollection
- for i := 0 to FList.Count - 1 do
- with TXCollectionItem(FList[i]) do
- begin
- FOwner := nil;
- Free;
- end;
- FList.Clear;
- FCount := 0;
- end;
- function TXCollection.IndexOf(anItem: TXCollectionItem): integer;
- begin
- Result := FList.IndexOf(anItem);
- end;
- function TXCollection.IndexOfClass(aClass: TXCollectionItemClass): integer;
- var
- i: integer;
- begin
- Result := -1;
- for i := 0 to FList.Count - 1 do
- if TXCollectionItem(FList[i]) is aClass then
- begin
- Result := i;
- Break;
- end;
- end;
- function TXCollection.GetByClass(aClass: TXCollectionItemClass): TXCollectionItem;
- var
- i: integer;
- begin
- Result := nil;
- for i := 0 to FList.Count - 1 do
- if TXCollectionItem(FList[i]) is aClass then
- begin
- Result := TXCollectionItem(FList[i]);
- Break;
- end;
- end;
- function TXCollection.IndexOfName(const aName: string): integer;
- var
- i: integer;
- begin
- Result := -1;
- for i := 0 to FList.Count - 1 do
- if TXCollectionItem(FList[i]).Name = aName then
- begin
- Result := i;
- Break;
- end;
- end;
- function TXCollection.CanAdd(aClass: TXCollectionItemClass): Boolean;
- var
- i: integer;
- XCollectionItemClass: TXCollectionItemClass;
- begin
- Result := True;
- // Test if the class allows itself to be added to this collection
- if not aClass.CanAddTo(Self) then
- begin
- Result := False;
- Exit;
- end;
- // is the given class compatible with owned ones ?
- if aClass.UniqueItem then
- for i := 0 to Count - 1 do
- begin
- if Items[i] is aClass then
- begin
- Result := False;
- Break;
- end;
- end;
- // are the owned classes compatible with the given one ?
- if Result then
- for i := 0 to Count - 1 do
- begin
- XCollectionItemClass := TXCollectionItemClass(Items[i].ClassType);
- if (XCollectionItemClass.UniqueItem) and
- aClass.InheritsFrom(XCollectionItemClass) then
- begin
- Result := False;
- Break;
- end;
- end;
- end;
- initialization // ------------------------------------------------------------
- finalization
- vXCollectionItemClasses.Free;
- end.
|