Props.pas
上传用户:yj_qiu
上传日期:2022-08-08
资源大小:23636k
文件大小:41k
源码类别:

游戏引擎

开发平台:

Delphi

  1. {
  2.  @Abstract(Item properties support unit)
  3.  (C) 2004 George "Mirage" Bakhtadze. <a href="http://www.casteng.com">www.casteng.com</a> <br>
  4.  The source code may be used under either MPL 1.1 or LGPL 2.1 license. See included license.txt file <br>
  5.  Unit contains a generic properties support implementation
  6. }
  7. {$Include GDefines.inc}
  8. unit Props;
  9. interface
  10. uses
  11.   TextFile,
  12.   BaseTypes, Basics;
  13. type TPropertyValueType = Integer;
  14. const
  15.   // Properties collection grow step
  16.   PropsCapacityStep = 32;
  17.   // File signature 
  18.   PropertiesFileSignature: TFileSignature  = 'PC00';
  19.     // Possible value types
  20.   // No value
  21.   vtNone = 0;
  22.   // Unsigned integer (natural) value
  23.   vtNat = 1;
  24.   // Integer value
  25.   vtInt = 2;
  26.   // Single-precision floating-point value
  27.   vtSingle = 3;
  28.   // Double-precision floating-point value
  29.   vtDouble = 10;
  30.   // AnsiString value
  31.   vtString     = 4;
  32.   // Color value
  33.   vtColor      = 5;
  34.   // Boolean value
  35.   vtBoolean    = 6;
  36.   // Enumerated value. Enumeration used to specify possible options.
  37.   vtEnumerated = 7;
  38.   // A link to an object
  39.   vtObjectLink = 8;
  40.   // Bynary data. Enumeration used to specify data size. Memory for the data managed mostly by application (see @Link(RetrieveBinPropertyData)).
  41.   vtBinary     = 9;
  42.   // Numerical (2xSingle) sample data. Enumeration used to specify data size. Memory for the data managed mostly by application (see @Link(RetrieveBinPropertyData)).
  43.   vtSingleSample = 11;
  44.   // Color gradient (Single + TColor) sample data. Enumeration used to specify data size. Memory for the data managed mostly by application (see @Link(RetrieveBinPropertyData)).
  45.   vtGradientSample = 12;
  46.     // Boolean value indices
  47.   // Off, no, etc
  48.   bvOff = 0;
  49.   // On, yes, etc
  50.   bvOn = 1;
  51.   // Boolean value strings
  52.   OnOffStr: array[False..True] of string[3] = ('Off', 'On');
  53.   // Boolean values enumeration
  54.   OnOffEnum = 'Off' + StringDelimiter + 'On';
  55. type
  56.   // Possible property options
  57.   TPOption = (// the property is hidden in an editor
  58.               poHidden,
  59.               // the property is read-only
  60.               poReadonly,
  61.               // the property is a derivative from other values so can not be changed directly
  62.               poDerivative,
  63.               // forces the set to be 32-bit
  64.               poDecor = 31);
  65.   // Property options set
  66.   TPOptions = set of TPOption;
  67.   PProperty = ^TProperty;
  68.   { Property data structure. <br>
  69.     <b>Name</b> - property name. Should be unique within @Link(TProperties). <br>
  70.     <b>Value</b> - property value. <br>
  71.     <b>Enumeration</b> - a set of string values, separated by <b>StringDelimiter</b>, determining possible values for <b>Value</b>. <br>
  72.     <b>Description</b> - property description. <br>
  73.     <b>ValueType</b> - property value type. <br>
  74.     <b>Options</b> - property option set. }
  75.   TProperty = packed record
  76.     Name: AnsiString;
  77.     Value, Enumeration, Description: AnsiString;
  78.     ValueType: Integer;
  79.     Options: TPOptions;
  80.   end;
  81.   // A delegate used in <b>ForEach</b> method
  82.   TPropertyDelegate = function(const Key: TProperty; var CustomData: Integer): Boolean of object;
  83.   // @Abstract(Main properties class)
  84.   TProperties = class
  85.   private
  86.     FTempBuffers: array of Pointer;
  87.     // Sets the value of the specified property
  88.     procedure SetValueProc(const Name, Value: AnsiString);
  89.   protected
  90.     // Number of properties
  91.     FTotalProperties: Integer;
  92.     // Set of properties
  93.     Properties: array of TProperty;
  94.     // Returns the index of the specified property
  95.     function GetIndex(const Name: AnsiString): Integer; virtual;
  96.     // Returns the value of the specified by index property
  97.     function GetValueByIndex(Index: Integer): AnsiString; virtual;
  98.     // Returns the value of the specified property
  99.     function GetValue(const Name: AnsiString): AnsiString;
  100.     // Sets the value of the specified by index property
  101.     procedure SetValueByIndex(Index: Integer; const Value: AnsiString);
  102.     // Sets the value of the specified property
  103.     function SetValue(const Name, Value: AnsiString): Integer; virtual;
  104.     // Returns an integer representation of the value with the specified type and enumeration
  105.     function ValueToInteger(ValueType: Integer; const Value, Enumeration: AnsiString): Int32; virtual;
  106.     // Returns a floating-point representation of the value with the specified type and enumeration
  107.     function ValueToFloat(const Value: AnsiString): Extended; virtual;
  108.     // Sets the value index of the specified by index enumerated property
  109.     procedure SetEnumeratedValueByIndex(Index, ValueIndex: Integer);
  110.     // Returns a pointer to the property data structure by property index
  111.     function GetPropertyByIndex(const Index: Integer): PProperty; virtual;
  112.   public
  113.     // This field becomes True with any change of properties or values
  114.     Changed: Boolean;
  115.     destructor Destroy; override;
  116.     // Calls the <b>Action</b> delegate for each property
  117.     procedure DoForEach(Action: TPropertyDelegate; var CustomData: Integer);
  118.     // Adds a properties set specified by <b>Props</b> to the current set. If OverrideExisting existing values will be overridden by the new ones
  119.     procedure Merge(const Props: TProperties; OverrideExisting: Boolean);
  120.     // Returns True if both properties are equal
  121.     function IsEqualProperty(const Prop1, Prop2: TProperty): Boolean;
  122.     // Returns True if both property sets are equal
  123.     function IsEqual(const Props: TProperties): Boolean;
  124.     // Returns @True if a property with the specified name exists in the set
  125.     function Exists(const Name: AnsiString): Boolean;
  126.     // Returns @True if a property with the specified name exists and has a valid value
  127.     function Valid(const Name: AnsiString): Boolean; virtual;
  128.     // Returns property name by its index
  129.     function GetNameByIndex(const Index: Integer): AnsiString; virtual;
  130.     // Returns a pointer to property by its name or nil if it is not found
  131.     function GetProperty(const Name: AnsiString): PProperty; virtual;
  132.     { Moves a binary property data to a previously allocated Dest and frees memory referenced by property value.
  133.       Returns size of the binary property in elements or zero if no property or data found. }
  134.     function RetrieveBinPropertyData(const PropName: AnsiString; Dest: Pointer): Integer;
  135.     // Returns size of the specified binary property in elements of the specified size
  136.     function GetBinPropertySize(const PropName: AnsiString; ElementSize: Integer): Integer;
  137.     // Returns an integer representation of the properties value
  138.     function GetAsInteger(const Name: AnsiString): Integer; virtual;
  139.     // Returns property value type
  140.     function GetType(const Name: AnsiString): TPropertyValueType; virtual;
  141.     // Returns property value type as string
  142.     function GetTypeAsString(Index: Integer): AnsiString; virtual;
  143.     // Returns property option set
  144.     function GetOptions(const Name: AnsiString): TPOptions; virtual;
  145.     // Adds a new property to the set and returns its index
  146.     function Add(const AName: AnsiString; const AValueType: Integer; const AOptions: TPOptions; const AValue, AEnumeration: AnsiString; const ADescription: AnsiString = ''): Integer;
  147.     // Adds a new enumerated property to the set and returns its index
  148.     function AddEnumerated(const AName: AnsiString; const AOptions: TPOptions; AValue: Integer; const AEnumeration: AnsiString): Integer;
  149.     { Adds a new binary property to the set and returns its index. AData should stay valid during the properties lifetime.
  150.       if @Link(RetrieveBinPropertyData) function is used to retrieve the data, no part of dynamic array or other managed memory can be passed in AData }
  151.     function AddBinary(const AName: AnsiString; const AOptions: TPOptions; AData: Pointer; DataSize: Integer): Integer;
  152.     { Adds a variable of a set type as a set of boolean values to the current property set. <br>
  153.       <b>VisibleMembers</b> is a set of members which should be added. If it's empty all members will be added. }
  154.     procedure AddSetProperty(const Name: AnsiString; Value, VisibleMembers: TSet32; ValuesEnum: TAnsiStringArray; const ADescription: AnsiString); overload;
  155.     // Sets a set of boolean properties added by @Link(AddSetProperty) with the specified set variable
  156.     function SetSetProperty(const Name: AnsiString; var Res: TSet32; ValuesEnum: TAnsiStringArray): Boolean; overload;
  157.     { Adds a variable of a set type as a set of boolean values to the current property set. <br>
  158.       <b>VisibleMembers</b> is a set of members which should be added. If it's empty all members will be added. }
  159.     procedure AddSetProperty(const Name: AnsiString; Value, VisibleMembers: TSet32; const ValuesEnum, ADescription: AnsiString); overload;
  160.     // Sets a set of boolean properties added by @Link(AddSetProperty) with the specified set variable
  161.     function SetSetProperty(const Name: AnsiString; var Res: TSet32; const ValuesEnum: AnsiString): Boolean; overload;
  162.     // Returns the properties in XML format
  163.     function GetAsXML: AnsiString; virtual;
  164.     // Writes the properties to a stream and return @True if success
  165.     function Write(Stream: TStream): Boolean; virtual;
  166.     // Reads the properties from a stream and return @True if success
  167.     function Read(Stream: TStream): Boolean; virtual;
  168.     { Creates temporary copy of memory buffer or a new memory buffer if Src is nil and returns a pointer to the copy.
  169.       The copy will be automatically destroyed when used by RetrieveBinPropertyData or while clearing or destroying TProperties instance.
  170.       Should be used with binary properties to pass in AddBinary()-like methods. }
  171.     function TempCopy(Src: Pointer; Size: Integer): Pointer;
  172.     { Finds and frees a previously created temporary memory buffer and returns True if it was found and freed }
  173.     function FindAndFreeTemp(Data: Pointer): Boolean;
  174.     // Clear the current property set as well as temporal memory storage
  175.     procedure Clear; virtual;
  176.     property TotalProperties: Integer read FTotalProperties;
  177.     // Property values by names
  178.     property Values[const Name: AnsiString]: AnsiString read GetValue write SetValueProc; default;
  179.   end;
  180.   // Base file configuration class
  181.   TBaseFileConfig = class(TProperties)
  182.   private
  183.     FileName: BaseTypes.TFileName;
  184.   public
  185.     constructor Create(const AFileName: BaseTypes.TFileName);
  186.     function SaveAs(const AFilename: BaseTypes.TFileName): Boolean; virtual; abstract;
  187.     function LoadFrom(const AFilename: BaseTypes.TFileName): Boolean; virtual; abstract;
  188.     function Save: Boolean;
  189.     function Load: Boolean;
  190.   end;
  191.   // File configuration implementation class
  192.   TFileConfig = class(TBaseFileConfig)
  193.     function SaveAs(const AFilename: BaseTypes.TFileName): Boolean; override;
  194.     function LoadFrom(const AFilename: BaseTypes.TFileName): Boolean; override;
  195.   end;
  196.   // File configuration implementation which preserves tabs, spaces and commented lines in the configuration file
  197.   TNiceFileConfig = class(TBaseFileConfig)
  198.   protected
  199.     function GetIndex(const Name: AnsiString): Integer; override;
  200.     function GetValueByIndex(Index: Integer): AnsiString; override;
  201.   public
  202.     function GetNameByIndex(const Index: Integer): AnsiString; override;
  203.     function SaveAs(const AFilename: BaseTypes.TFileName): Boolean; override;
  204.     function LoadFrom(const AFilename: BaseTypes.TFileName): Boolean; override;
  205.   end;
  206.   procedure CopyProperty(const SrcProp: TProperty; var DestProp: TProperty);
  207.   // Adds to <b>Properties</b> a <b>TColor4s</b> value as four floating-point components and one color property
  208.   procedure AddColor4sProperty(Properties: TProperties; const Name: AnsiString; const Color: TColor4s);
  209.   // Adds to <b>Properties</b> a <b>TColor</b> value as four floating-point components and one color property
  210.   procedure AddColorProperty(Properties: TProperties; const Name: AnsiString; const Color: TColor);
  211.   { Sets a color property in <b>Properties</b> with the given in <b>Res</b> value. <br>
  212.     Returns the resulting value in <b>Res</b> and @True if new <b>Res</b> value differs from the initial one }
  213.   function SetColor4sProperty(Properties: TProperties; const Name: AnsiString; var Res: TColor4s): Boolean;
  214.   { Sets a color property in <b>Properties</b> with the given in <b>Res</b> value. <br>
  215.     Returns the resulting value in <b>Res</b> and @True if new <b>Res</b> value differs from the initial one }
  216.   function SetColorProperty(Properties: TProperties; const Name: AnsiString; var Res: TColor): Boolean;
  217. implementation
  218. uses SysUtils;
  219. procedure CopyProperty(const SrcProp: TProperty; var DestProp: TProperty);
  220. begin
  221.   with DestProp do begin
  222.     Name        := SrcProp.Name;
  223.     Value       := SrcProp.Value;
  224.     Enumeration := SrcProp.Enumeration;
  225.     Description := SrcProp.Description;
  226.     ValueType   := SrcProp.ValueType;
  227.     Options     := SrcProp.Options;
  228.   end;
  229. end;
  230. procedure AddColor4sProperty(Properties: TProperties; const Name: AnsiString; const Color: TColor4s);
  231. begin
  232.   Properties.Add(Name,            vtColor,  [], '#' + IntToHexA(GetColorFrom4S(Color).C, 8), '');
  233.   Properties.Add(Name + 'Red',   vtSingle, [], FormatA('%1.3F', [Color.R]), '0-1');
  234.   Properties.Add(Name + 'Green', vtSingle, [], FormatA('%1.3F', [Color.G]), '0-1');
  235.   Properties.Add(Name + 'Blue',  vtSingle, [], FormatA('%1.3F', [Color.B]), '0-1');
  236.   Properties.Add(Name + 'Alpha', vtSingle, [], FormatA('%1.3F', [Color.A]), '0-1');
  237. end;
  238. procedure AddColorProperty(Properties: TProperties; const Name: AnsiString; const Color: TColor);
  239. begin
  240.   AddColor4sProperty(Properties, Name, ColorTo4S(Color));
  241. end;
  242. function SetColor4sProperty(Properties: TProperties; const Name: AnsiString; var Res: TColor4s): Boolean;
  243. var NewVec: TColor4s;
  244. begin
  245.   NewVec := Res;
  246.   if Properties.Valid(Name)            then NewVec   := ColorTo4S(Nat32(Properties.GetAsInteger(Name)));
  247.   if Properties.Valid(Name + 'Red')   then NewVec.R := StrToFloatDefA(Properties[Name + 'Red'],   0.5);
  248.   if Properties.Valid(Name + 'Green') then NewVec.G := StrToFloatDefA(Properties[Name + 'Green'], 0.5);
  249.   if Properties.Valid(Name + 'Blue')  then NewVec.B := StrToFloatDefA(Properties[Name + 'Blue'],  0.5);
  250.   if Properties.Valid(Name + 'Alpha') then NewVec.A := StrToFloatDefA(Properties[Name + 'Alpha'], 0.5);
  251.   Result := isNan(Res.R) or isNan(Res.G) or isNan(Res.B) or isNan(Res.A) or
  252.            (NewVec.R <> Res.R) or (NewVec.G <> Res.G) or (NewVec.B <> Res.B) or (NewVec.A <> Res.A);
  253.   if Result then Res := NewVec;
  254. end;
  255. function SetColorProperty(Properties: TProperties; const Name: AnsiString; var Res: TColor): Boolean;
  256. var V: TColor4s;
  257. begin
  258.   V := ColorTo4S(Res);
  259.   Result := SetColor4sProperty(Properties, Name, V);
  260.   if Result then Res := GetColorFrom4s(V);
  261. end;
  262. function PropertyOptionsToString(const Options: TPOptions): AnsiString;
  263. begin
  264.   Result := '';
  265.   if poHidden in Options then Result := 'Hidden';
  266.   if poReadonly in Options then begin
  267.     if Result <> '' then  Result := Result + ', ';
  268.     Result := Result + 'Readonly';
  269.   end;
  270. end;
  271. { TProperties }
  272. procedure TProperties.DoForEach(Action: TPropertyDelegate; var CustomData: Integer);
  273. var i: Integer;
  274. begin
  275.   if @Action <> nil then for i := 0 to FTotalProperties-1 do if not Action(Properties[i], CustomData) then Exit;
  276. end;
  277. procedure TProperties.Merge(const Props: TProperties; OverrideExisting: Boolean);
  278. var i: Integer;
  279. begin
  280.   if Assigned(Props) then
  281.     for i := 0 to Props.FTotalProperties-1 do
  282.       if OverrideExisting or not Exists(Props.Properties[i].Name) then
  283.         Properties[Add(Props.Properties[i].Name, Props.Properties[i].ValueType, Props.Properties[i].Options, Props.Properties[i].Value, Props.Properties[i].Enumeration)].Description := Props.Properties[i].Description;
  284. end;
  285. function TProperties.IsEqualProperty(const Prop1, Prop2: TProperty): Boolean;
  286. begin
  287.   Result := (Prop1.ValueType = Prop2.ValueType) and (Prop1.Options = Prop2.Options) and
  288.             (Prop1.Name = Prop2.Name) and (Prop1.Value = Prop2.Value) and
  289.             (Prop1.Enumeration = Prop2.Enumeration) and (Prop1.Description = Prop2.Description);
  290. end;
  291. function TProperties.IsEqual(const Props: TProperties): Boolean;
  292. var i: Integer;
  293. begin
  294.   Result := False;
  295.   if TotalProperties <> Props.TotalProperties then Exit;
  296.   i := TotalProperties;
  297.   while (i >= 0) and Props.Exists(Properties[i].Name) and IsEqualProperty(Properties[i], Props.GetProperty(Properties[i].Name)^) do Dec(i);
  298.   Result := i < 0;
  299. end;
  300. function TProperties.Exists(const Name: AnsiString): Boolean;
  301. // Returns TRUE if property with this name exists, FALSE otherwise
  302. begin
  303.   Result := GetIndex(Name) > -1;
  304. end;
  305. function TProperties.Valid(const Name: AnsiString): Boolean;
  306. var Index, i: Integer; Enums: TAnsiStringArray; TotalEnums: Integer; Val64: Int64;
  307. begin
  308.   Result := False;
  309.   Index := GetIndex(Name);
  310.   if Index = -1 then Exit;                                          // Return invalid if not exists
  311.   case Properties[Index].ValueType of
  312.     vtNat: begin
  313.        Val64 := StrToInt64Def(GetValueByIndex(Index), -1);
  314.        Result := (Val64 >= 0) and (Val64 <= MaxNat32);
  315.     end;
  316.     vtInt:    Result := IsDecimalInteger(GetValueByIndex(Index));
  317.     vtSingle,
  318.     vtDouble: Result := IsFloat(GetValueByIndex(Index));
  319.     vtString,
  320.     vtObjectLink,
  321.     vtBinary: Result := True;
  322.     vtSingleSample, vtGradientSample: Result := True;//(StrToIntDef(Properties[Index].Enumeration, -1) mod (SizeOf(Single)*2)) = 0;
  323.     vtColor:  Result := IsColor(GetValueByIndex(Index));
  324.     vtBoolean,
  325.     vtEnumerated: begin
  326.       TotalEnums := SplitA(Properties[Index].Enumeration, StringDelimiter, Enums, True);
  327.       Result := False;
  328.       for i := 0 to TotalEnums-1 do if Enums[i] = GetValueByIndex(Index) then begin
  329.         Result := True; Break;
  330.       end;
  331.       Enums := nil;
  332.     end;
  333.     else Result := False;
  334.   end;
  335. end;
  336. function TProperties.GetProperty(const Name: AnsiString): PProperty;
  337. var Index: Integer;
  338. begin
  339.   Index := GetIndex(Name);
  340.   if Index > -1 then Result := @Properties[Index] else Result := nil;
  341. end;
  342. function TProperties.RetrieveBinPropertyData(const PropName: AnsiString; Dest: Pointer): Integer;
  343. var Temp: Pointer; Prop: PProperty;
  344. begin
  345.   Prop := GetProperty(PropName);
  346.   Result := StrToIntDef(Prop^.Enumeration, 0);
  347.   Temp := Pointer(StrToIntDef(Prop^.Value, 0));
  348.   if not Assigned(Temp) or (Result = 0) then Exit;
  349.   if Assigned(Dest) then Move(Temp^, Dest^, Result);
  350.   FindAndFreeTemp(Temp);                                                         // Free temporary property data
  351. end;
  352. function TProperties.GetBinPropertySize(const PropName: AnsiString; ElementSize: Integer): Integer;
  353. var Prop: PProperty;
  354. begin
  355.   Assert(ElementSize > 0);
  356.   Prop := GetProperty(PropName);
  357.   if Assigned(Prop) then
  358.     Result := StrToIntDef(Prop^.Enumeration, 0)
  359.   else
  360.     Result := 0;
  361.   if Result mod ElementSize = 0 then
  362.     Result := Result div ElementSize
  363.   else
  364.     Result := 0;
  365. end;
  366. function TProperties.GetNameByIndex(const Index: Integer): AnsiString;
  367. begin
  368.   if (Index >= 0) and (Index < FTotalProperties) then
  369.    Result := Properties[Index].Name else
  370.     Result := '';
  371. end;
  372. function TProperties.GetPropertyByIndex(const Index: Integer): PProperty;
  373. begin
  374.   if (Index >= 0) and (Index < FTotalProperties) then begin
  375.     Result := @Properties[Index];
  376.   end else Result := nil;
  377. end;
  378. function TProperties.GetValue(const Name: AnsiString): AnsiString;
  379. // Returns value of property with given name
  380. var i: Integer;
  381. begin
  382.   i := GetIndex(Name);
  383.   if i > -1 then Result := GetValueByIndex(i) else Result := '';
  384. end;
  385. function TProperties.GetAsInteger(const Name: AnsiString): Integer;
  386. var Index: Integer;
  387. begin
  388.   Result := 0;
  389.   Index := GetIndex(Name);
  390.   if Index = -1 then Exit;
  391.   Result := ValueToInteger(Properties[Index].ValueType, GetValueByIndex(Index), Properties[Index].Enumeration);
  392. end;
  393. function TProperties.GetType(const Name: AnsiString): TPropertyValueType;
  394. // Returns type of property with the given name
  395. var i: Integer;
  396. begin
  397.   i := GetIndex(Name);
  398.   if i > -1 then Result := Properties[i].ValueType else Result := 0;
  399. end;
  400. function TProperties.GetTypeAsString(Index: Integer): AnsiString;
  401. begin
  402.   case Properties[Index].ValueType of
  403.     vtNat:            Result := 'Unsigned';
  404.     vtInt:            Result := 'Integer';
  405.     vtSingle:         Result := 'Real';
  406.     vtDouble:         Result := 'Double';
  407.     vtString:         Result := 'String';
  408.     vtObjectLink:     Result := 'Object link';
  409.     vtBinary:         Result := 'Binary data';
  410.     vtSingleSample:   Result := 'Sampled data';
  411.     vtGradientSample: Result := 'Sampled gradient';
  412.     vtColor:          Result := 'Color';
  413.     vtBoolean:        Result := 'Boolean';
  414.     vtEnumerated:     Result := 'Enumerated';
  415.     else              Result := 'Unknown';
  416.   end;
  417. end;
  418. function TProperties.GetOptions(const Name: AnsiString): TPOptions;
  419. // Returns options of property with given name
  420. var i: Integer;
  421. begin
  422.   i := GetIndex(Name);
  423.   if i > -1 then Result := Properties[i].Options else Result := [];
  424. end;
  425. function TProperties.Add(const AName: AnsiString; const AValueType: Integer; const AOptions: TPOptions; const AValue, AEnumeration: AnsiString; const ADescription: AnsiString = ''): Integer;
  426. // Adds a new property into list
  427. // Returns index of the new property
  428. begin
  429.   Result := GetIndex(AName);
  430. //  if  = nil then SetLength(Properties, 1000000);
  431.   if Result = -1 then begin
  432.     Inc(FTotalProperties); //SetLength(Properties, FTotalProperties);
  433.     Result := FTotalProperties-1;
  434.     if Length(Properties) < FTotalProperties then SetLength(Properties, Length(Properties) + PropsCapacityStep);
  435.   end;
  436.   Properties[Result].Name        := AName;
  437.   Properties[Result].ValueType   := AValueType;
  438.   Properties[Result].Options     := AOptions;
  439.   Properties[Result].Enumeration := AEnumeration;
  440.   Properties[Result].Description := ADescription;
  441.   SetValueByIndex(Result, AValue);
  442.   if AValueType = vtBoolean then Properties[Result].Enumeration := OnOffEnum; // Handle Boolean value type as enumerated type
  443.   Changed := True;
  444. end;
  445. function TProperties.AddEnumerated(const AName: AnsiString; const AOptions: TPOptions; AValue: Integer; const AEnumeration: AnsiString): Integer;
  446. begin
  447.   Result := Add(AName, vtEnumerated, AOptions, '', AEnumeration);
  448.   // Set enumerated value to one from list using given value as index
  449.   SetEnumeratedValueByIndex(Result, AValue);
  450. end;
  451. function TProperties.AddBinary(const AName: AnsiString; const AOptions: TPOptions; AData: Pointer; DataSize: Integer): Integer;
  452. begin
  453.   Result := Add(AName, vtBinary, AOptions, IntToStrA(Cardinal(AData)), IntToStrA(DataSize));
  454. end;
  455. procedure TProperties.AddSetProperty(const Name: AnsiString; Value, VisibleMembers: TSet32; ValuesEnum: TAnsiStringArray; const ADescription: AnsiString);
  456. var i: Integer; s: AnsiString; EmptySet: Boolean;
  457. begin
  458.   s := '[';
  459.   EmptySet := True;
  460.   for i := 0 to High(ValuesEnum) do
  461.     if (VisibleMembers = []) or (i in VisibleMembers) then begin
  462.       Add(Name + '' + ValuesEnum[i], vtBoolean, [], OnOffStr[i in Value], '');
  463.       if i in Value then begin
  464.         if not EmptySet then s := s + ', ';
  465.         s := s + ValuesEnum[i];
  466.         EmptySet := False;
  467.       end;
  468.     end;
  469.   s := s + ']';
  470.   Add(Name, vtString, [poReadonly], s, ADescription);
  471. end;
  472. function TProperties.SetSetProperty(const Name: AnsiString; var Res: TSet32; ValuesEnum: TAnsiStringArray): Boolean;
  473. var i: Integer; NewSet: TSet32;
  474. begin
  475.   NewSet := Res;
  476.   for i := 0 to High(ValuesEnum) do
  477.     if Valid(Name + '' + ValuesEnum[i]) then
  478.       if GetAsInteger(Name + '' + ValuesEnum[i]) > 0 then
  479.         Include(NewSet, i) else
  480.           Exclude(NewSet, i);
  481.   Result := NewSet <> Res;
  482.   if Result then Res := NewSet;
  483. end;
  484. procedure TProperties.AddSetProperty(const Name: AnsiString; Value, VisibleMembers: TSet32; const ValuesEnum, ADescription: AnsiString);
  485. var Enum: TAnsiStringArray;
  486. begin
  487.   Basics.SplitA(ValuesEnum, StringDelimiter, Enum, True);
  488.   AddSetProperty(Name, Value, VisibleMembers, Enum, ADescription);
  489.   Enum := nil;
  490. end;
  491. function TProperties.SetSetProperty(const Name: AnsiString; var Res: TSet32; const ValuesEnum: AnsiString): Boolean;
  492. var Enum: TAnsiStringArray;
  493. begin
  494.   Basics.SplitA(ValuesEnum, StringDelimiter, Enum, True);
  495.   Result := SetSetProperty(Name, Res, Enum);
  496.   Enum := nil;
  497. end;
  498. function TProperties.GetIndex(const Name: AnsiString): Integer;
  499. // Returns index of named property
  500. begin
  501.   for Result := 0 to FTotalProperties - 1 do if Name = Properties[Result].Name then Exit;
  502.   Result := -1;
  503. end;
  504. function TProperties.GetValueByIndex(Index: Integer): AnsiString;
  505. begin
  506.   Assert((Index >= 0) and (Index < FTotalProperties), ClassName + '.GetValueByIndex: Invalid property index: ' + IntToStr(Index));
  507.   Result := Properties[Index].Value;
  508. end;
  509. procedure TProperties.SetValueByIndex(Index: Integer; const Value: AnsiString);
  510. begin
  511.   if Index > -1 then begin
  512.     Properties[Index].Value := Value;
  513.     Changed := True;
  514.   end;
  515. end;
  516. function TProperties.SetValue(const Name, Value: AnsiString): Integer;
  517. // Puts property with given name in OProperty
  518. // Returns: 0 if OK; -1 if no property found
  519. begin
  520.   Result := GetIndex(Name);
  521.   SetValueByIndex(Result, Value);
  522. end;
  523. function TProperties.ValueToInteger(ValueType: Integer; const Value, Enumeration: AnsiString): Int32;
  524. var Enums: TAnsiStringArray; TotalEnums: Int32; Val64: Int64;
  525. begin
  526.   case ValueType of
  527.     vtNat, vtBinary, vtSingleSample, vtGradientSample: begin
  528.       Val64 := StrToInt64Def(Value, 0);
  529.       {$IFDEF LOGGING} if (Val64 < 0) or (Val64 > MaxNat32) then Log.Log(ClassName + '.ValueToInteger: Unsigned integer property out of range: ' + Value, lkWarning); {$ENDIF}
  530.       Result := Int32(Nat32(Val64 and $FFFFFFFF));
  531.     end;
  532.     vtInt: begin
  533.       Val64 := StrToInt64Def(Value, 0);
  534.       {$IFDEF LOGGING} if (Val64 < MinInt32) or (Val64 > MaxInt32) then Log.Log(ClassName + '.ValueToInteger: Integer property out of range: ' + Value, lkWarning); {$ENDIF}
  535.       Result := Int32(Nat32(Val64 and $FFFFFFFF));
  536.     end;
  537.     vtSingle,
  538.     vtDouble:     Result := Trunc(0.5 + StrToFloatDef(Value, 0));
  539.     vtString:     Result := StrToIntDef(Value, 0);
  540.     vtObjectLink: Result := Ord(Value <> '');
  541.     vtColor:      Result := Integer(Nat32(ColorStrToIntDef(Value, $80808080)));
  542.     vtBoolean:    Result := Ord(Value = OnOffStr[True]);
  543.     vtEnumerated: begin
  544.       TotalEnums := SplitA(Enumeration, StringDelimiter, Enums, True);
  545.       Result := TotalEnums-1;
  546.       while (Result >= 0) and (Enums[Result] <> Value) do Dec(Result);
  547.       Enums := nil;
  548.     end;
  549.     else begin
  550.       Result := 0;
  551.       Assert(False, 'Invalid property type');
  552.     end;
  553.   end;
  554. end;
  555. function TProperties.ValueToFloat(const Value: AnsiString): Extended;
  556. begin
  557.   Result := StrToFloatDef(Value, 0);
  558. end;
  559. procedure TProperties.SetEnumeratedValueByIndex(Index, ValueIndex: Integer);
  560. var Enums: TAnsiStringArray; TotalEnums: Integer;
  561. begin
  562.   Properties[Index].Value := '-1';                              // "-1" is value for invalid enumerations or in case of invalid index
  563.   if Properties[Index].Enumeration <> '' then begin
  564.     TotalEnums := SplitA(Properties[Index].Enumeration, StringDelimiter, Enums, True);
  565.     if ValueIndex < TotalEnums then Properties[Index].Value := Enums[ValueIndex];
  566.     Enums := nil;
  567.   end;
  568. end;
  569. function TProperties.GetAsXML: AnsiString;
  570. // Returns string which represents properties in following format:
  571. // <Properties>
  572. // [<Group Name = "-Name-">]
  573. // <Property Name = "-Name-" Type = "-ValueType-" Options = "-Options-">-Value-</Property>
  574. // [</Group>]
  575. // </Properties>
  576. const IndentStr: array[0..1] of AnsiChar = ''; ReturnStr: array[0..1] of AnsiChar = #13#10;
  577. var i: Integer; Indent: AnsiString;
  578. begin
  579.   Result := '<Properties>' + ReturnStr;
  580.   Indent := IndentStr;
  581.   for i := 0 to FTotalProperties - 1 do begin
  582. {    if Properties[i].ValueType = vtGroupBegin then begin
  583.       Result := Result + Indent + '<Group Name = "' + Properties[i].Name + '">' + ReturnStr;
  584.       Indent := Indent + IndentStr;
  585.     end else if Properties[i].ValueType = vtGroupEnd then begin
  586.       Result := Result + Indent + '</Group>' + ReturnStr;
  587.       Indent := Copy(Indent, 1, Length(Indent)-Length(IndentStr));
  588.     end else}
  589.      Result := Result + Indent + '<Property Name = "' + Properties[i].Name + '"' +
  590.                                  ' Type = "' + GetTypeAsString(Properties[i].ValueType) + '"' +
  591.                                  ' Options = "' + PropertyOptionsToString(Properties[i].Options) + '"' +
  592.                         '>' + GetValueByIndex(i) + '</Property>' + ReturnStr;
  593.   end;
  594.   Result := Result + '</Properties>';
  595. end;
  596. procedure TProperties.SetValueProc(const Name, Value: AnsiString);
  597. begin
  598.   SetValue(Name, Value);
  599. end;
  600. function TProperties.Write(Stream: TStream): Boolean;
  601. var
  602.   i, DataSize, TotalProps: Integer;
  603.   IData: Int32;
  604.   Data: Pointer; SData: Single; DData: Double;
  605. begin
  606.   TotalProps := 0;
  607.   for i := 0 to FTotalProperties-1 do if not (poDerivative in Properties[i].Options) then Inc(TotalProps);
  608.   Result := Stream.WriteCheck(PropertiesFileSignature, SizeOf(PropertiesFileSignature)) and
  609.             Stream.WriteCheck(TotalProps, SizeOf(TotalProps));
  610. // Save properties
  611.   for i := 0 to FTotalProperties-1 do if not (poDerivative in Properties[i].Options) then begin
  612.     Result := Result and
  613.               Stream.WriteCheck(Properties[i].ValueType, SizeOf(Properties[i].ValueType)) and
  614.               Stream.WriteCheck(Properties[i].Options,   SizeOf(Properties[i].Options))   and
  615.               SaveString(Stream, Properties[i].Name);
  616.     case Properties[i].ValueType of
  617.       vtNat, vtInt, vtColor, vtBoolean, vtEnumerated: begin
  618.         IData := ValueToInteger(Properties[i].ValueType, Properties[i].Value, Properties[i].Enumeration);
  619.         Result := Result and Stream.WriteCheck(IData, SizeOf(IData));
  620.       end;
  621.       vtSingle: begin
  622.         SData := ValueToFloat(Properties[i].Value);
  623.         Result := Result and Stream.WriteCheck(SData, SizeOf(SData));
  624.       end;
  625.       vtDouble: begin
  626.         DData := ValueToFloat(Properties[i].Value);
  627.         Result := Result and Stream.WriteCheck(DData, SizeOf(DData));
  628.       end;
  629.       vtString, vtObjectLink: Result := Result and SaveString(Stream, Properties[i].Value);
  630.       vtBinary, vtSingleSample, vtGradientSample: begin
  631.         Data     := Pointer(StrToInt(Properties[i].Value));
  632.         DataSize := StrToInt(Properties[i].Enumeration);
  633.         Result := Result and
  634.                   Stream.WriteCheck(DataSize, SizeOf(DataSize)) and
  635.                   Stream.WriteCheck(Data^, DataSize);
  636.       end;
  637.       else Assert(False, 'Invalid property type');
  638.     end;
  639.     Result := Result and
  640.               SaveString(Stream, Properties[i].Enumeration) and
  641.               SaveString(Stream, Properties[i].Description);
  642.   end;
  643. end;
  644. function TProperties.Read(Stream: TStream): Boolean;
  645. var
  646.   Sign: TFileSignature; i, DataSize: Integer;
  647.   IData: Integer; Data: Pointer; SData: Single; DData: Double;
  648. begin
  649.   Result := False;
  650.   if not Stream.ReadCheck(Sign, SizeOf(PropertiesFileSignature)) then Exit;
  651.   if Sign <> PropertiesFileSignature then
  652.     if not ErrorHandler(TInvalidFormat.Create('Invalid property signature')) then Exit;
  653. // Load properties
  654.   Clear;
  655.   if not Stream.ReadCheck(FTotalProperties, SizeOf(FTotalProperties)) then Exit;
  656.   SetLength(Properties, FTotalProperties);
  657.   for i := 0 to FTotalProperties-1 do begin
  658.     if not ( Stream.ReadCheck(Properties[i].ValueType, SizeOf(Properties[i].ValueType)) and
  659.              Stream.ReadCheck(Properties[i].Options,   SizeOf(Properties[i].Options))   and
  660.              LoadString(Stream, Properties[i].Name) ) then Exit;
  661.     {$IFDEF COMPATMODE}
  662.     if Properties[i].ValueType = vtBinary then begin
  663.       if not Stream.ReadCheck(DataSize, SizeOf(DataSize)) then Exit;
  664.       GetMem(Data, DataSize); ??
  665.       if not Stream.ReadCheck(Data^, DataSize) then Exit;
  666.       Properties[i].Value       := IntToStr(Cardinal(Data));
  667.       Properties[i].Enumeration := IntToStr(DataSize);
  668.     end else if not LoadString(Stream, Properties[i].Value) then Exit;
  669.     {$ELSE}
  670.     case Properties[i].ValueType of
  671.       vtNat, vtInt, vtColor, vtBoolean, vtEnumerated: begin
  672.         if not Stream.ReadCheck(IData, SizeOf(IData)) then Exit;
  673.         case Properties[i].ValueType of
  674.           vtNat:     Properties[i].Value := IntToStrA(Cardinal(IData));
  675.           vtInt:     Properties[i].Value := IntToStrA(IData);
  676.           vtColor:   Properties[i].Value := '#' + IntToHexA(Nat32(IData), 8);
  677.           vtBoolean: Properties[i].Value := OnOffStr[IData <> 0];
  678.         end;
  679.       end;
  680.       vtSingle: begin
  681.         if not Stream.ReadCheck(SData, SizeOf(SData)) then Exit;
  682.         Properties[i].Value := FloatToStrA(SData);
  683.       end;
  684.       vtDouble: begin
  685.         if not Stream.ReadCheck(DData, SizeOf(DData)) then Exit;
  686.         Properties[i].Value := FloatToStrA(DData);
  687.       end;
  688.       vtString, vtObjectLink: if not LoadString(Stream, Properties[i].Value) then Exit;
  689.       vtBinary, vtSingleSample, vtGradientSample: begin
  690.         if not Stream.ReadCheck(DataSize, SizeOf(DataSize)) then Exit;
  691.         if DataSize > 0 then begin
  692. //          Data := TempCopy(nil, DataSize);                             // Application should free this memory by itself or use @Link(RetrieveBinPropertyData)
  693.           GetMem(Data, DataSize);
  694.           if not Stream.ReadCheck(Data^, DataSize) then Exit;
  695.         end else
  696.           Data := nil;
  697.         Properties[i].Value       := IntToStrA(Cardinal(Data));
  698.         Properties[i].Enumeration := IntToStrA(DataSize);
  699.       end;
  700.       else Assert(False, 'Invalid property type');
  701.     end;
  702.     {$ENDIF}
  703.     if not ( LoadString(Stream, Properties[i].Enumeration) and
  704.              LoadString(Stream, Properties[i].Description) ) then Exit;
  705.     {$IFNDEF COMPATMODE}
  706.     if Properties[i].ValueType = vtEnumerated then SetEnumeratedValueByIndex(i, IData);
  707.     {$ENDIF}
  708.   end;
  709.   Result := True;
  710. end;
  711. (*function TProperties.Read(Stream: TStream): Boolean;
  712. var Sign: TFileSignature; i, DataSize: Integer; Data: Pointer;
  713. begin
  714.   Result := False;
  715.   if not Stream.ReadCheck(Sign, SizeOf(PropertiesFileSignature)) then Exit;
  716.   if Sign <> PropertiesFileSignature then raise EInvalidFormat.Create('Invalid property signature');
  717. // Load properties
  718.   Clear;
  719.   if not Stream.ReadCheck(FTotalProperties, SizeOf(FTotalProperties)) then Exit;
  720.   SetLength(Properties, FTotalProperties);
  721.   for i := 0 to FTotalProperties-1 do begin
  722.     if not ( Stream.ReadCheck(Properties[i].ValueType, SizeOf(Properties[i].ValueType)) and
  723.              Stream.ReadCheck(Properties[i].Options,   SizeOf(Properties[i].Options))   and
  724.              LoadString(Stream, Properties[i].Name) ) then Exit;
  725.     if Properties[i].ValueType = vtBinary then begin
  726.       if not Stream.ReadCheck(DataSize, SizeOf(DataSize)) then Exit;
  727.       GetMem(Data, DataSize);
  728.       if not Stream.ReadCheck(Data^, DataSize) then Exit;
  729.       Properties[i].Value       := IntToStr(Cardinal(Data));
  730.       Properties[i].Enumeration := IntToStr(DataSize);
  731.     end else if not LoadString(Stream, Properties[i].Value) then Exit;
  732.     if not ( LoadString(Stream, Properties[i].Enumeration) and
  733.              LoadString(Stream, Properties[i].Description) ) then Exit;
  734.   end;
  735.   Result := True;
  736. end;*)
  737. function TProperties.TempCopy(Src: Pointer; Size: Integer): Pointer;
  738. var i: Integer;
  739. begin
  740.   i := 0;
  741.   while (i < Length(FTempBuffers)) and Assigned(FTempBuffers[i]) do Inc(i);
  742.   if i >= Length(FTempBuffers) then SetLength(FTempBuffers, i+1);
  743.   GetMem(FTempBuffers[i], Size);
  744.   if Src <> nil then Move(Src^, FTempBuffers[i]^, Size);
  745.   Result := FTempBuffers[i];
  746. end;
  747. function TProperties.FindAndFreeTemp(Data: Pointer): Boolean;
  748. var i: Integer;
  749. begin
  750.   i := High(FTempBuffers);
  751.   while (i >= 0) and (FTempBuffers[i] <> Data) do Dec(i);
  752.   if i >= 0 then begin
  753.     FreeMem(FTempBuffers[i]);
  754.     FTempBuffers[i] := nil;
  755.   end;
  756.   Result := i >= 0;
  757. end;
  758. procedure TProperties.Clear;
  759. var i: Integer;
  760. begin
  761.   for i := 0 to FTotalProperties-1 do begin
  762.     Properties[i].Name        := '';
  763.     Properties[i].Value       := '';
  764.     Properties[i].Enumeration := '';
  765.     Properties[i].Description := '';
  766.   end;
  767.   SetLength(Properties, 0); FTotalProperties := 0;
  768.   for i := 0 to High(FTempBuffers) do if Assigned(FTempBuffers[i]) then FreeMem(FTempBuffers[i]);
  769.   Changed := True;
  770. end;
  771. destructor TProperties.Destroy;
  772. begin
  773.   Clear;
  774.   inherited;
  775. end;
  776. { TBaseFileConfig }
  777. function TBaseFileConfig.Save: Boolean;
  778. begin
  779.   Result := SaveAs(FileName);
  780. end;
  781. function TBaseFileConfig.Load: Boolean;
  782. begin
  783.   Result := LoadFrom(FileName);
  784. end;
  785. constructor TBaseFileConfig.Create(const AFileName: BaseTypes.TFileName);
  786. begin
  787.   FileName := AFileName;
  788.   Load;
  789. end;
  790. { TFileConfig }
  791. function TFileConfig.SaveAs(const AFilename: BaseTypes.TFileName): Boolean;
  792. var cf: Text; i: Integer;
  793. begin
  794.   Result := True;
  795. {$I-}
  796.   Assign(cf, AFileName); Rewrite(cf);
  797.   if IOResult <> 0 then begin
  798.     {$IFDEF LOGGING} Log.Log(ClassName + '.SaveAs: Error opening file "' + FileName + '"', lkError); {$ENDIF}
  799.     Result := False;
  800.     Exit;
  801.   end;
  802.   for i := 0 to FTotalProperties-1 do begin
  803.     Writeln(cf, Properties[i].Name + '=' + Properties[i].Value);
  804.     if IOResult <> 0 then begin
  805.       {$IFDEF LOGGING} Log.Log(ClassName + '.SaveAs: Error writing to file "' + FileName + '"', lkError); {$ENDIF}
  806.       Result := False;
  807.       Break;
  808.     end;
  809.   end;
  810.   Close(cf);  
  811. end;
  812. function TFileConfig.LoadFrom(const AFilename: BaseTypes.TFileName): Boolean;
  813. var cf: Text; s: AnsiString; SplitPos: Integer;
  814. begin
  815.   Result := True;
  816. {$I-}
  817.   Assign(cf, AFileName); Reset(cf);
  818.   if IOResult <> 0 then begin
  819.     {$IFDEF LOGGING} Log.Log(ClassName + '.LoadFrom: Error opening file "' + FileName + '"', lkError); {$ENDIF}
  820.     Result := False;
  821.     Exit;
  822.   end;
  823.   Clear;
  824.   while not EOF(cf) do begin
  825.     Readln(cf, s);
  826.     if IOResult <> 0 then begin
  827.       {$IFDEF LOGGING} Log.Log(ClassName + '.LoadFrom: Error reading from file "' + FileName + '"', lkError); {$ENDIF}
  828.       Result := False;
  829.       Break;
  830.     end;
  831.     s := TrimSpacesA(s);
  832.     SplitPos := Pos('=', s);
  833.     Add(TrimSpacesA(Copy(s, 1, SplitPos-1)), vtString, [], TrimSpacesA(Copy(s, SplitPos+1, Length(s))), '', '');
  834.   end;
  835.   Close(cf);
  836.   Changed := True;
  837. end;
  838. { TNiceFileConfig }
  839. const CommentChar = '##'; LineNumberSeparator = #9;
  840. function TNiceFileConfig.GetIndex(const Name: AnsiString): Integer;
  841. var i: Integer; s: AnsiString;
  842. begin
  843.   Result := -1;
  844.   for i := 0 to FTotalProperties - 1 do begin
  845.     s := TrimSpacesA(Copy(Properties[i].Name, Pos(LineNumberSeparator, Properties[i].Name) + 1, Length(Properties[i].Name)));
  846.     if Name = s then begin
  847.       Result := i; Exit;
  848.     end;
  849.   end;
  850. end;
  851. function TNiceFileConfig.GetNameByIndex(const Index: Integer): AnsiString;
  852. begin
  853.   Result := TrimSpacesA(Copy(inherited GetNameByIndex(Index), Pos(LineNumberSeparator, inherited GetNameByIndex(Index)) + 1, Length(inherited GetNameByIndex(Index))));
  854. end;
  855. function TNiceFileConfig.GetValueByIndex(Index: Integer): AnsiString;
  856. begin
  857.   Result := TrimSpacesA(inherited GetValueByIndex(Index));
  858. end;
  859. function TNiceFileConfig.SaveAs(const AFilename: BaseTypes.TFileName): Boolean;
  860. var cf: Text; i, LineSepPos: Integer; s: AnsiString;
  861. begin
  862.   Result := True;
  863. {$I-}
  864.   Assign(cf, AFileName); Rewrite(cf);
  865.   if IOResult <> 0 then begin
  866.     {$IFDEF LOGGING} Log.Log(ClassName + '.SaveAs: Error opening file "' + FileName + '"', lkError); {$ENDIF}
  867.     Result := False;
  868.     Exit;
  869.   end;
  870.   for i := 0 to FTotalProperties-1 do begin
  871.     s := Properties[i].Name;
  872.     LineSepPos := Pos(LineNumberSeparator, s);
  873.     s := Copy(s, LineSepPos+1, Length(s));
  874.     if s <> '' then s := s + '=';
  875.     s := s + Properties[i].Value;
  876.     if Properties[i].Description <> '' then s := s + Properties[i].Description;
  877.     Writeln(cf, s);
  878.     if IOResult <> 0 then begin
  879.       {$IFDEF LOGGING} Log.Log(ClassName + '.SaveAs: Error writing to file "' + FileName + '"', lkError); {$ENDIF}
  880.       Result := False;
  881.       Break;
  882.     end;
  883.   end;
  884.   Close(cf);
  885. end;
  886. function TNiceFileConfig.LoadFrom(const AFilename: BaseTypes.TFileName): Boolean;
  887. var cf: Text; s, Value: AnsiString; SplitPos, CommentPos, ValueType, Line: Integer;
  888. begin
  889.   Result := True;
  890. {$I-}
  891.   Assign(cf, AFileName); Reset(cf);
  892.   if IOResult <> 0 then begin
  893.     {$IFDEF LOGGING} Log.Log(ClassName + '.LoadFrom: Error opening file "' + FileName + '"', lkError); {$ENDIF}
  894.     Result := False;
  895.     Exit;
  896.   end;
  897.   Clear;
  898.   
  899.   Line := 1;
  900.   while not EOF(cf) do begin
  901.     Readln(cf, s);
  902.     if IOResult <> 0 then begin
  903.       {$IFDEF LOGGING} Log.Log(ClassName + '.LoadFrom: Error reading from file "' + FileName + '"', lkError); {$ENDIF}
  904.       Result := False;
  905.       Break;
  906.     end;
  907.     CommentPos := Pos(CommentChar, s);
  908.     if CommentPos = 0 then CommentPos := Length(s)+1;
  909.     SplitPos := Pos('=', s);
  910.     Value := Copy(s, SplitPos+1, CommentPos - SplitPos - 1);
  911.     if isColor(Value) then ValueType := vtColor else
  912.       if (TrimSpaces(Value) = OnOffStr[False]) or (TrimSpaces(Value) = OnOffStr[True]) then
  913.         ValueType := vtBoolean else
  914.           ValueType := vtString;
  915.     Add(IntToStrA(Line) + LineNumberSeparator + Copy(s, 1, SplitPos-1), ValueType, [], Value, '', Copy(s, CommentPos + Length(CommentChar)-1, Length(s)));
  916.     Inc(Line);
  917.   end;
  918.   Close(cf);
  919.   Changed := True;
  920. end;
  921. end.