bcrtti.pas 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. // SPDX-License-Identifier: LGPL-3.0-linking-exception
  2. { Useful tools for RTTI. Functions are used expecialy for save/load styles.
  3. Styles has construction similar to INI files:
  4. [Header]
  5. Author=Krzysztof Dibowski
  6. Description=My test style
  7. ControlClass=TBCButton
  8. [Properties]
  9. State.Border.Width=2
  10. .....
  11. But instead of IniFiles unit, we have own functions for read and write styles.
  12. ------------------------------------------------------------------------------
  13. originally written in 2012 by Krzysztof Dibowski dibowski at interia.pl
  14. }
  15. {******************************* CONTRIBUTOR(S) ******************************
  16. - Edivando S. Santos Brasil | [email protected]
  17. (Compatibility with delphi VCL 11/2018)
  18. ***************************** END CONTRIBUTOR(S) *****************************}
  19. unit BCRTTI;
  20. {$I bgracontrols.inc}
  21. interface
  22. uses
  23. Classes;
  24. type
  25. PBCStyleHeader = ^TBCStyleHeader;
  26. TBCStyleHeader = record
  27. Author: String;
  28. ControlClass: String;
  29. Description: String;
  30. end;
  31. // Function return data of specified section (header, properties, etc).
  32. // This is smart function, because it doesn't read whole file but read file
  33. // line by line and return only needed section. So it should fastest for reading
  34. // header info instead of TIniFile object which read, parse and index all file.
  35. function GetSectionData(const AFileName, ASectionName: String): TStrings;
  36. // Methods which read header from list or file and parse it into pascal record
  37. procedure GetStyleHeader(const AFileName: String; AOutHeader: PBCStyleHeader);
  38. // Function check if specified name is on ignored list
  39. function IsPropIgnored(const AName: String): Boolean;
  40. // Method load style saved by SaveStyle method
  41. procedure LoadStyle(AControl: TObject; const AFileName: String; ALogs: TStrings = nil);
  42. // Method save all (which are not on ignored list or readonly) public propertys to
  43. // the output string list. This method have support for property
  44. // tree (Propert1.Subpropert1.Color = 543467). Values are represented as "human readable"
  45. // (e.g. Align = alClient). Header info is save too.
  46. procedure SaveStyle(AControl: TObject; const AAuthor, ADescription: String;
  47. ATargetList: TStrings);
  48. implementation
  49. uses typinfo, variants, sysutils, {%H-}strutils;
  50. const
  51. tIGNORED_PROPS: array[0..5] of string =
  52. ('name','caption','left','top','height','width');
  53. sSECTION_HEADER_NAME = 'HEADER';
  54. sSECTION_PROP_NAME = 'PROPERTIES';
  55. sSECTION_HEADER = '['+sSECTION_HEADER_NAME+']';
  56. sSECTION_PROP = '['+sSECTION_PROP_NAME+']';
  57. procedure RemovePadChars(var S: String; const CSet: TSysCharset);
  58. var
  59. I,J,K: LONGINT;
  60. begin
  61. I:=Length(S);
  62. IF (I>0) Then
  63. Begin
  64. J:=I;
  65. While (j>0) and (S[J] IN CSet) DO DEC(J);
  66. if j=0 Then
  67. begin
  68. s:='';
  69. exit;
  70. end;
  71. k:=1;
  72. While (k<=I) And (S[k] IN CSet) DO
  73. INC(k);
  74. IF k>1 Then
  75. begin
  76. move(s[k],s[1],j-k+1);
  77. setlength(s,j-k+1);
  78. end
  79. else
  80. setlength(s,j);
  81. end;
  82. end;
  83. function TrimSet(const S: String;const CSet:TSysCharSet): String;
  84. begin
  85. result:=s;
  86. RemovePadChars(result,cset);
  87. end;
  88. function IsPropIgnored(const AName: String): Boolean;
  89. var
  90. i: Integer;
  91. begin
  92. Result := False;
  93. for i := Low(tIGNORED_PROPS) to High(tIGNORED_PROPS) do
  94. if SameText(tIGNORED_PROPS[i],Trim(AName)) then
  95. Exit(True);
  96. end;
  97. procedure LoadStyle(AControl: TObject; const AFileName: String;
  98. ALogs: TStrings = nil);
  99. var
  100. i, iDot: Integer;
  101. sPath, sVal: String;
  102. obj: TObject;
  103. sl: TStrings;
  104. const
  105. sLOG_NO_PROP = 'Can not find property "%s"';
  106. sLOG_SET_ERR = 'Can not set value "%s" to property "%s"';
  107. sLOG_READ_ONLY = 'Property "%s" is read-only';
  108. procedure _AddLog(const AText: String);
  109. begin
  110. if ALogs<>nil then
  111. ALogs.Add(AText);
  112. end;
  113. function _ValidateProp(AObj: TObject; const APropName: String): Boolean;
  114. begin
  115. Result := True;
  116. // If can't find property
  117. if not IsPublishedProp(AObj,APropName) then
  118. begin
  119. _AddLog(Format(sLOG_NO_PROP,[APropName]));
  120. Exit(False);
  121. end;
  122. // If read-only property
  123. if (GetPropInfo(AObj,APropName)^.SetProc=nil) then
  124. begin
  125. _AddLog(Format(sLOG_READ_ONLY,[APropName]));
  126. Exit(False);
  127. end;
  128. end;
  129. begin
  130. if not FileExists(AFileName) then
  131. Exit;
  132. if ALogs<>nil then
  133. ALogs.Clear;
  134. sl := GetSectionData(AFileName, sSECTION_PROP_NAME);
  135. try
  136. for i:=0 to Pred(sl.Count) do
  137. begin
  138. // Full path with hierarchy tree
  139. sPath := Trim(sl.Names[i]);
  140. // "Human readable" value
  141. sVal := Trim(sl.ValueFromIndex[i]);
  142. iDot := Pos('.', sPath);
  143. // If simple property then write it value
  144. if iDot=0 then
  145. begin
  146. if not _ValidateProp(AControl,sPath) then
  147. Continue;
  148. // Writting property value
  149. try
  150. SetPropValue(AControl,sPath,sVal)
  151. except
  152. _AddLog(Format(sLOG_SET_ERR,[sVal, sPath]));
  153. end
  154. end
  155. else
  156. begin
  157. //... else we must go down in hierarchy tree to the last
  158. // object and then write value to property
  159. obj := AControl;
  160. while iDot>0 do
  161. begin
  162. if not _ValidateProp(obj,Copy(sPath,1,iDot-1)) then
  163. begin
  164. obj := nil;
  165. Break;
  166. end;
  167. obj := GetObjectProp(obj,Copy(sPath,1,iDot-1));
  168. Delete(sPath,1,iDot);
  169. iDot := Pos('.', sPath);
  170. end;
  171. // If no dots, then this word is property name
  172. if (obj<>nil) and (sPath<>'') and _ValidateProp(obj,sPath) then
  173. begin
  174. try
  175. SetPropValue(obj,sPath,sVal)
  176. except
  177. _AddLog(Format(sLOG_SET_ERR,[sVal, sPath]));
  178. end
  179. end;
  180. end;
  181. end;
  182. finally
  183. sl.Free;
  184. end;
  185. end;
  186. procedure SaveStyle(AControl: TObject; const AAuthor, ADescription: String;
  187. ATargetList: TStrings);
  188. procedure _SaveProp(AObj: TObject; APath: String = '');
  189. var
  190. iCount, i: Integer;
  191. lst: TPropList;
  192. s: String;
  193. begin
  194. if AObj=nil then Exit;
  195. iCount := GetPropList(PTypeInfo(AObj.ClassInfo), tkProperties, @lst);
  196. for i := 0 to Pred(iCount) do
  197. { Notice:
  198. - IsPublishedProp return true for ALL public properties, not only
  199. for properties in Published section. For saving styles, we don't need
  200. all public properties, but only published (visible in object inspector).
  201. I don't know if this is a bug, I leave it. Maybe it will start
  202. working in future ;)
  203. - Second argument check if property should be ignored (but only from root tree),
  204. because we can't save basic properties of control like Name, Top, Left etc.
  205. - SetProc<>nil mean "not read only"
  206. }
  207. if IsPublishedProp(AObj,lst[i]^.Name) and
  208. ((AControl<>AObj) or (not IsPropIgnored(lst[i]^.Name))) and
  209. (lst[i]^.SetProc<>nil)
  210. then
  211. begin
  212. // Building property tree
  213. if APath=''
  214. then s := lst[i]^.Name
  215. else s := APath+'.'+lst[i]^.Name;
  216. // If property has subproperty, then we start recurrence to
  217. // build hierarchy tree.
  218. if (lst[i]^.PropType^.Kind = tkClass) then
  219. _SaveProp(GetObjectProp(AObj,lst[i]),s)
  220. else
  221. begin
  222. // We are in bottom node, so we can save final property with value
  223. s := s + ' = ' + String(GetPropValue(AObj,lst[i]^.Name,True));
  224. ATargetList.Add(s);
  225. end;
  226. end;
  227. end;
  228. begin
  229. if ATargetList=nil then
  230. Exit;
  231. ATargetList.Clear;
  232. ATargetList.Add(sSECTION_HEADER);
  233. ATargetList.Add('Author='+AAuthor);
  234. ATargetList.Add('Description='+ADescription);
  235. ATargetList.Add('ControlClass='+AControl.ClassName);
  236. ATargetList.Add('');
  237. ATargetList.Add(sSECTION_PROP);
  238. _SaveProp(AControl);
  239. end;
  240. function GetSectionData(const AFileName, ASectionName: String): TStrings;
  241. var
  242. f: TextFile;
  243. s: String;
  244. bReading: Boolean;
  245. begin
  246. Result := TStringList.Create;
  247. Result.Clear;
  248. if (not FileExists(AFileName)) or (ASectionName='') then
  249. Exit;
  250. AssignFile(f,AFileName);
  251. try
  252. Reset(f);
  253. bReading := False;
  254. while not EOF(f) do
  255. begin
  256. ReadLn(f,s);
  257. s := Trim(s);
  258. if s='' then
  259. Continue;
  260. // If current line is section tag
  261. if s[1]='[' then
  262. begin
  263. // If we currently reading section then we read it all and we must
  264. // break because another section occur
  265. if bReading then
  266. begin
  267. bReading := False;
  268. Break;
  269. end
  270. else
  271. // Otherwise if this is section we are looking for, then set flag
  272. // to "start reading"
  273. if SameText(ASectionName,TrimSet(s,['[',']'])) then
  274. bReading := True;
  275. end else
  276. // Read section line
  277. if bReading then
  278. Result.Add(s);
  279. end;
  280. finally
  281. CloseFile(f);
  282. end;
  283. end;
  284. procedure GetStyleHeader(const AFileName: String; AOutHeader: PBCStyleHeader);
  285. var sl: TStrings;
  286. begin
  287. if (AOutHeader=nil) or (not FileExists(AFileName)) then
  288. Exit;
  289. sl := GetSectionData(AFileName,sSECTION_HEADER_NAME);
  290. try
  291. // Header info (with format Author=Foo) should be at the top of file
  292. with AOutHeader^ do
  293. begin
  294. Author := sl.Values['Author'];
  295. Description := sl.Values['Description'];
  296. ControlClass := sl.Values['ControlClass'];
  297. end;
  298. finally
  299. sl.Free;
  300. end;
  301. end;
  302. end.