source: trunk/Packages/bgracontrols/bcrtti.pas

Last change on this file was 2, checked in by chronos, 5 years ago
File size: 9.8 KB
Line 
1{ Useful tools for RTTI. Functions are used expecialy for save/load styles.
2 Styles has construction similar to INI files:
3
4 [Header]
5 Author=Krzysztof Dibowski
6 Description=My test style
7 ControlClass=TBCButton
8
9 [Properties]
10 State.Border.Width=2
11 .....
12
13 But instead of IniFiles unit, we have own functions for read and write styles.
14
15 ------------------------------------------------------------------------------
16 Copyright (C) 2012 Krzysztof Dibowski dibowski at interia.pl
17
18 This library is free software; you can redistribute it and/or modify it
19 under the terms of the GNU Library General Public License as published by
20 the Free Software Foundation; either version 2 of the License, or (at your
21 option) any later version with the following modification:
22
23 As a special exception, the copyright holders of this library give you
24 permission to link this library with independent modules to produce an
25 executable, regardless of the license terms of these independent modules,and
26 to copy and distribute the resulting executable under terms of your choice,
27 provided that you also meet, for each linked independent module, the terms
28 and conditions of the license of that module. An independent module is a
29 module which is not derived from or based on this library. If you modify
30 this library, you may extend this exception to your version of the library,
31 but you are not obligated to do so. If you do not wish to do so, delete this
32 exception statement from your version.
33
34 This program is distributed in the hope that it will be useful, but WITHOUT
35 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
36 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
37 for more details.
38
39 You should have received a copy of the GNU Library General Public License
40 along with this library; if not, write to the Free Software Foundation,
41 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
42}
43unit BCRTTI;
44
45{$mode objfpc}{$H+}
46
47interface
48
49uses
50 Classes;
51
52type
53 PBCStyleHeader = ^TBCStyleHeader;
54 TBCStyleHeader = record
55 Author: String;
56 ControlClass: String;
57 Description: String;
58 end;
59
60// Function return data of specified section (header, properties, etc).
61// This is smart function, because it doesn't read whole file but read file
62// line by line and return only needed section. So it should fastest for reading
63// header info instead of TIniFile object which read, parse and index all file.
64function GetSectionData(const AFileName, ASectionName: String): TStrings;
65// Methods which read header from list or file and parse it into pascal record
66procedure GetStyleHeader(const AFileName: String; AOutHeader: PBCStyleHeader);
67// Function check if specified name is on ignored list
68function IsPropIgnored(const AName: String): Boolean;
69// Method load style saved by SaveStyle method
70procedure LoadStyle(AControl: TObject; const AFileName: String; ALogs: TStrings = nil);
71// Method save all (which are not on ignored list or readonly) public propertys to
72// the output string list. This method have support for property
73// tree (Propert1.Subpropert1.Color = 543467). Values are represented as "human readable"
74// (e.g. Align = alClient). Header info is save too.
75procedure SaveStyle(AControl: TObject; const AAuthor, ADescription: String;
76 ATargetList: TStrings);
77
78implementation
79
80uses typinfo, variants, sysutils, strutils;
81
82const
83 tIGNORED_PROPS: array[0..5] of string =
84 ('name','caption','left','top','height','width');
85 sSECTION_HEADER_NAME = 'HEADER';
86 sSECTION_PROP_NAME = 'PROPERTIES';
87 sSECTION_HEADER = '['+sSECTION_HEADER_NAME+']';
88 sSECTION_PROP = '['+sSECTION_PROP_NAME+']';
89
90function IsPropIgnored(const AName: String): Boolean;
91var
92 i: Integer;
93begin
94 Result := False;
95 for i := Low(tIGNORED_PROPS) to High(tIGNORED_PROPS) do
96 if SameText(tIGNORED_PROPS[i],Trim(AName)) then
97 Exit(True);
98end;
99
100procedure LoadStyle(AControl: TObject; const AFileName: String;
101 ALogs: TStrings = nil);
102var
103 i, iDot: Integer;
104 sPath, sVal: String;
105 obj: TObject;
106 sl: TStrings;
107const
108 sLOG_NO_PROP = 'Can not find property "%s"';
109 sLOG_SET_ERR = 'Can not set value "%s" to property "%s"';
110 sLOG_READ_ONLY = 'Property "%s" is read-only';
111
112 procedure _AddLog(const AText: String);
113 begin
114 if ALogs<>nil then
115 ALogs.Add(AText);
116 end;
117
118 function _ValidateProp(AObj: TObject; const APropName: String): Boolean;
119 begin
120 Result := True;
121 // If can't find property
122 if not IsPublishedProp(AObj,APropName) then
123 begin
124 _AddLog(Format(sLOG_NO_PROP,[APropName]));
125 Exit(False);
126 end;
127 // If read-only property
128 if (GetPropInfo(AObj,APropName)^.SetProc=nil) then
129 begin
130 _AddLog(Format(sLOG_READ_ONLY,[APropName]));
131 Exit(False);
132 end;
133 end;
134
135begin
136 if not FileExists(AFileName) then
137 Exit;
138
139 if ALogs<>nil then
140 ALogs.Clear;
141
142 sl := GetSectionData(AFileName, sSECTION_PROP_NAME);
143 try
144 for i:=0 to Pred(sl.Count) do
145 begin
146 // Full path with hierarchy tree
147 sPath := Trim(sl.Names[i]);
148 // "Human readable" value
149 sVal := Trim(sl.ValueFromIndex[i]);
150 iDot := Pos('.', sPath);
151 // If simple property then write it value
152 if iDot=0 then
153 begin
154 if not _ValidateProp(AControl,sPath) then
155 Continue;
156 // Writting property value
157 try
158 SetPropValue(AControl,sPath,sVal)
159 except
160 _AddLog(Format(sLOG_SET_ERR,[sVal, sPath]));
161 end
162 end
163 else
164 begin
165 //... else we must go down in hierarchy tree to the last
166 // object and then write value to property
167 obj := AControl;
168 while iDot>0 do
169 begin
170 if not _ValidateProp(obj,Copy(sPath,1,iDot-1)) then
171 begin
172 obj := nil;
173 Break;
174 end;
175
176 obj := GetObjectProp(obj,Copy(sPath,1,iDot-1));
177
178 Delete(sPath,1,iDot);
179 iDot := Pos('.', sPath);
180 end;
181
182 // If no dots, then this word is property name
183 if (obj<>nil) and (sPath<>'') and _ValidateProp(obj,sPath) then
184 begin
185 try
186 SetPropValue(obj,sPath,sVal)
187 except
188 _AddLog(Format(sLOG_SET_ERR,[sVal, sPath]));
189 end
190 end;
191 end;
192 end;
193 finally
194 sl.Free;
195 end;
196end;
197
198procedure SaveStyle(AControl: TObject; const AAuthor, ADescription: String;
199 ATargetList: TStrings);
200
201 procedure _SaveProp(AObj: TObject; APath: String = '');
202 var
203 iCount, i: Integer;
204 lst: TPropList;
205 s: String;
206 begin
207 if AObj=nil then Exit;
208
209 iCount := GetPropList(PTypeInfo(AObj.ClassInfo), tkProperties, @lst);
210 for i := 0 to Pred(iCount) do
211 { Notice:
212 - IsPublishedProp return true for ALL public properties, not only
213 for properties in Published section. For saving styles, we don't need
214 all public properties, but only published (visible in object inspector).
215 I don't know if this is a bug, I leave it. Maybe it will start
216 working in future ;)
217 - Second argument check if property should be ignored (but only from root tree),
218 because we can't save basic properties of control like Name, Top, Left etc.
219 - SetProc<>nil mean "not read only"
220 }
221 if IsPublishedProp(AObj,lst[i]^.Name) and
222 ((AControl<>AObj) or (not IsPropIgnored(lst[i]^.Name))) and
223 (lst[i]^.SetProc<>nil)
224 then
225 begin
226 // Building property tree
227 if APath=''
228 then s := lst[i]^.Name
229 else s := APath+'.'+lst[i]^.Name;
230
231 // If property has subproperty, then we start recurrence to
232 // build hierarchy tree.
233 if (lst[i]^.PropType^.Kind = tkClass) then
234 _SaveProp(GetObjectProp(AObj,lst[i]),s)
235 else
236 begin
237 // We are in bottom node, so we can save final property with value
238 s := s + ' = ' + String(GetPropValue(AObj,lst[i]^.Name,True));
239 ATargetList.Add(s);
240 end;
241 end;
242 end;
243begin
244 if ATargetList=nil then
245 Exit;
246 ATargetList.Clear;
247
248 ATargetList.Add(sSECTION_HEADER);
249 ATargetList.Add('Author='+AAuthor);
250 ATargetList.Add('Description='+ADescription);
251 ATargetList.Add('ControlClass='+AControl.ClassName);
252 ATargetList.Add('');
253 ATargetList.Add(sSECTION_PROP);
254 _SaveProp(AControl);
255end;
256
257function GetSectionData(const AFileName, ASectionName: String): TStrings;
258var
259 f: TextFile;
260 s: String;
261 sl: TStringList;
262 bReading: Boolean;
263begin
264 Result := TStringList.Create;
265 Result.Clear;
266
267 if (not FileExists(AFileName)) or (ASectionName='') then
268 Exit;
269
270 AssignFile(f,AFileName);
271 try
272 Reset(f);
273 bReading := False;
274 while not EOF(f) do
275 begin
276 ReadLn(f,s);
277 s := Trim(s);
278 if s='' then
279 Continue;
280
281 // If current line is section tag
282 if s[1]='[' then
283 begin
284 // If we currently reading section then we read it all and we must
285 // break because another section occur
286 if bReading then
287 begin
288 bReading := False;
289 Break;
290 end
291 else
292 // Otherwise if this is section we are looking for, then set flag
293 // to "start reading"
294 if SameText(ASectionName,TrimSet(s,['[',']'])) then
295 bReading := True;
296 end else
297 // Read section line
298 if bReading then
299 Result.Add(s);
300 end;
301 finally
302 CloseFile(f);
303 end;
304end;
305
306procedure GetStyleHeader(const AFileName: String; AOutHeader: PBCStyleHeader);
307var sl: TStrings;
308begin
309 if (AOutHeader=nil) or (not FileExists(AFileName)) then
310 Exit;
311
312 sl := GetSectionData(AFileName,sSECTION_HEADER_NAME);
313 try
314 // Header info (with format Author=Foo) should be at the top of file
315 with AOutHeader^ do
316 begin
317 Author := sl.Values['Author'];
318 Description := sl.Values['Description'];
319 ControlClass := sl.Values['ControlClass'];
320 end;
321 finally
322 sl.Free;
323 end;
324end;
325
326end.
327
Note: See TracBrowser for help on using the repository browser.