| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- // SPDX-License-Identifier: LGPL-3.0-linking-exception
- { Useful tools for RTTI. Functions are used expecialy for save/load styles.
- Styles has construction similar to INI files:
- [Header]
- Author=Krzysztof Dibowski
- Description=My test style
- ControlClass=TBCButton
- [Properties]
- State.Border.Width=2
- .....
- But instead of IniFiles unit, we have own functions for read and write styles.
- ------------------------------------------------------------------------------
- originally written in 2012 by Krzysztof Dibowski dibowski at interia.pl
- }
- {******************************* CONTRIBUTOR(S) ******************************
- - Edivando S. Santos Brasil | [email protected]
- (Compatibility with delphi VCL 11/2018)
- ***************************** END CONTRIBUTOR(S) *****************************}
- unit BCRTTI;
- {$I bgracontrols.inc}
- interface
- uses
- Classes;
- type
- PBCStyleHeader = ^TBCStyleHeader;
- TBCStyleHeader = record
- Author: String;
- ControlClass: String;
- Description: String;
- end;
- // Function return data of specified section (header, properties, etc).
- // This is smart function, because it doesn't read whole file but read file
- // line by line and return only needed section. So it should fastest for reading
- // header info instead of TIniFile object which read, parse and index all file.
- function GetSectionData(const AFileName, ASectionName: String): TStrings;
- // Methods which read header from list or file and parse it into pascal record
- procedure GetStyleHeader(const AFileName: String; AOutHeader: PBCStyleHeader);
- // Function check if specified name is on ignored list
- function IsPropIgnored(const AName: String): Boolean;
- // Method load style saved by SaveStyle method
- procedure LoadStyle(AControl: TObject; const AFileName: String; ALogs: TStrings = nil);
- // Method save all (which are not on ignored list or readonly) public propertys to
- // the output string list. This method have support for property
- // tree (Propert1.Subpropert1.Color = 543467). Values are represented as "human readable"
- // (e.g. Align = alClient). Header info is save too.
- procedure SaveStyle(AControl: TObject; const AAuthor, ADescription: String;
- ATargetList: TStrings);
- implementation
- uses typinfo, variants, sysutils, {%H-}strutils;
- const
- tIGNORED_PROPS: array[0..5] of string =
- ('name','caption','left','top','height','width');
- sSECTION_HEADER_NAME = 'HEADER';
- sSECTION_PROP_NAME = 'PROPERTIES';
- sSECTION_HEADER = '['+sSECTION_HEADER_NAME+']';
- sSECTION_PROP = '['+sSECTION_PROP_NAME+']';
- procedure RemovePadChars(var S: String; const CSet: TSysCharset);
- var
- I,J,K: LONGINT;
- begin
- I:=Length(S);
- IF (I>0) Then
- Begin
- J:=I;
- While (j>0) and (S[J] IN CSet) DO DEC(J);
- if j=0 Then
- begin
- s:='';
- exit;
- end;
- k:=1;
- While (k<=I) And (S[k] IN CSet) DO
- INC(k);
- IF k>1 Then
- begin
- move(s[k],s[1],j-k+1);
- setlength(s,j-k+1);
- end
- else
- setlength(s,j);
- end;
- end;
- function TrimSet(const S: String;const CSet:TSysCharSet): String;
- begin
- result:=s;
- RemovePadChars(result,cset);
- end;
- function IsPropIgnored(const AName: String): Boolean;
- var
- i: Integer;
- begin
- Result := False;
- for i := Low(tIGNORED_PROPS) to High(tIGNORED_PROPS) do
- if SameText(tIGNORED_PROPS[i],Trim(AName)) then
- Exit(True);
- end;
- procedure LoadStyle(AControl: TObject; const AFileName: String;
- ALogs: TStrings = nil);
- var
- i, iDot: Integer;
- sPath, sVal: String;
- obj: TObject;
- sl: TStrings;
- const
- sLOG_NO_PROP = 'Can not find property "%s"';
- sLOG_SET_ERR = 'Can not set value "%s" to property "%s"';
- sLOG_READ_ONLY = 'Property "%s" is read-only';
- procedure _AddLog(const AText: String);
- begin
- if ALogs<>nil then
- ALogs.Add(AText);
- end;
- function _ValidateProp(AObj: TObject; const APropName: String): Boolean;
- begin
- Result := True;
- // If can't find property
- if not IsPublishedProp(AObj,APropName) then
- begin
- _AddLog(Format(sLOG_NO_PROP,[APropName]));
- Exit(False);
- end;
- // If read-only property
- if (GetPropInfo(AObj,APropName)^.SetProc=nil) then
- begin
- _AddLog(Format(sLOG_READ_ONLY,[APropName]));
- Exit(False);
- end;
- end;
- begin
- if not FileExists(AFileName) then
- Exit;
- if ALogs<>nil then
- ALogs.Clear;
- sl := GetSectionData(AFileName, sSECTION_PROP_NAME);
- try
- for i:=0 to Pred(sl.Count) do
- begin
- // Full path with hierarchy tree
- sPath := Trim(sl.Names[i]);
- // "Human readable" value
- sVal := Trim(sl.ValueFromIndex[i]);
- iDot := Pos('.', sPath);
- // If simple property then write it value
- if iDot=0 then
- begin
- if not _ValidateProp(AControl,sPath) then
- Continue;
- // Writting property value
- try
- SetPropValue(AControl,sPath,sVal)
- except
- _AddLog(Format(sLOG_SET_ERR,[sVal, sPath]));
- end
- end
- else
- begin
- //... else we must go down in hierarchy tree to the last
- // object and then write value to property
- obj := AControl;
- while iDot>0 do
- begin
- if not _ValidateProp(obj,Copy(sPath,1,iDot-1)) then
- begin
- obj := nil;
- Break;
- end;
- obj := GetObjectProp(obj,Copy(sPath,1,iDot-1));
- Delete(sPath,1,iDot);
- iDot := Pos('.', sPath);
- end;
- // If no dots, then this word is property name
- if (obj<>nil) and (sPath<>'') and _ValidateProp(obj,sPath) then
- begin
- try
- SetPropValue(obj,sPath,sVal)
- except
- _AddLog(Format(sLOG_SET_ERR,[sVal, sPath]));
- end
- end;
- end;
- end;
- finally
- sl.Free;
- end;
- end;
- procedure SaveStyle(AControl: TObject; const AAuthor, ADescription: String;
- ATargetList: TStrings);
- procedure _SaveProp(AObj: TObject; APath: String = '');
- var
- iCount, i: Integer;
- lst: TPropList;
- s: String;
- begin
- if AObj=nil then Exit;
- iCount := GetPropList(PTypeInfo(AObj.ClassInfo), tkProperties, @lst);
- for i := 0 to Pred(iCount) do
- { Notice:
- - IsPublishedProp return true for ALL public properties, not only
- for properties in Published section. For saving styles, we don't need
- all public properties, but only published (visible in object inspector).
- I don't know if this is a bug, I leave it. Maybe it will start
- working in future ;)
- - Second argument check if property should be ignored (but only from root tree),
- because we can't save basic properties of control like Name, Top, Left etc.
- - SetProc<>nil mean "not read only"
- }
- if IsPublishedProp(AObj,lst[i]^.Name) and
- ((AControl<>AObj) or (not IsPropIgnored(lst[i]^.Name))) and
- (lst[i]^.SetProc<>nil)
- then
- begin
- // Building property tree
- if APath=''
- then s := lst[i]^.Name
- else s := APath+'.'+lst[i]^.Name;
- // If property has subproperty, then we start recurrence to
- // build hierarchy tree.
- if (lst[i]^.PropType^.Kind = tkClass) then
- _SaveProp(GetObjectProp(AObj,lst[i]),s)
- else
- begin
- // We are in bottom node, so we can save final property with value
- s := s + ' = ' + String(GetPropValue(AObj,lst[i]^.Name,True));
- ATargetList.Add(s);
- end;
- end;
- end;
- begin
- if ATargetList=nil then
- Exit;
- ATargetList.Clear;
- ATargetList.Add(sSECTION_HEADER);
- ATargetList.Add('Author='+AAuthor);
- ATargetList.Add('Description='+ADescription);
- ATargetList.Add('ControlClass='+AControl.ClassName);
- ATargetList.Add('');
- ATargetList.Add(sSECTION_PROP);
- _SaveProp(AControl);
- end;
- function GetSectionData(const AFileName, ASectionName: String): TStrings;
- var
- f: TextFile;
- s: String;
- bReading: Boolean;
- begin
- Result := TStringList.Create;
- Result.Clear;
- if (not FileExists(AFileName)) or (ASectionName='') then
- Exit;
- AssignFile(f,AFileName);
- try
- Reset(f);
- bReading := False;
- while not EOF(f) do
- begin
- ReadLn(f,s);
- s := Trim(s);
- if s='' then
- Continue;
- // If current line is section tag
- if s[1]='[' then
- begin
- // If we currently reading section then we read it all and we must
- // break because another section occur
- if bReading then
- begin
- bReading := False;
- Break;
- end
- else
- // Otherwise if this is section we are looking for, then set flag
- // to "start reading"
- if SameText(ASectionName,TrimSet(s,['[',']'])) then
- bReading := True;
- end else
- // Read section line
- if bReading then
- Result.Add(s);
- end;
- finally
- CloseFile(f);
- end;
- end;
- procedure GetStyleHeader(const AFileName: String; AOutHeader: PBCStyleHeader);
- var sl: TStrings;
- begin
- if (AOutHeader=nil) or (not FileExists(AFileName)) then
- Exit;
- sl := GetSectionData(AFileName,sSECTION_HEADER_NAME);
- try
- // Header info (with format Author=Foo) should be at the top of file
- with AOutHeader^ do
- begin
- Author := sl.Values['Author'];
- Description := sl.Values['Description'];
- ControlClass := sl.Values['ControlClass'];
- end;
- finally
- sl.Free;
- end;
- end;
- end.
|