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 | }
|
---|
43 | unit BCRTTI;
|
---|
44 |
|
---|
45 | {$mode objfpc}{$H+}
|
---|
46 |
|
---|
47 | interface
|
---|
48 |
|
---|
49 | uses
|
---|
50 | Classes;
|
---|
51 |
|
---|
52 | type
|
---|
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.
|
---|
64 | function GetSectionData(const AFileName, ASectionName: String): TStrings;
|
---|
65 | // Methods which read header from list or file and parse it into pascal record
|
---|
66 | procedure GetStyleHeader(const AFileName: String; AOutHeader: PBCStyleHeader);
|
---|
67 | // Function check if specified name is on ignored list
|
---|
68 | function IsPropIgnored(const AName: String): Boolean;
|
---|
69 | // Method load style saved by SaveStyle method
|
---|
70 | procedure 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.
|
---|
75 | procedure SaveStyle(AControl: TObject; const AAuthor, ADescription: String;
|
---|
76 | ATargetList: TStrings);
|
---|
77 |
|
---|
78 | implementation
|
---|
79 |
|
---|
80 | uses typinfo, variants, sysutils, strutils;
|
---|
81 |
|
---|
82 | const
|
---|
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 |
|
---|
90 | function IsPropIgnored(const AName: String): Boolean;
|
---|
91 | var
|
---|
92 | i: Integer;
|
---|
93 | begin
|
---|
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);
|
---|
98 | end;
|
---|
99 |
|
---|
100 | procedure LoadStyle(AControl: TObject; const AFileName: String;
|
---|
101 | ALogs: TStrings = nil);
|
---|
102 | var
|
---|
103 | i, iDot: Integer;
|
---|
104 | sPath, sVal: String;
|
---|
105 | obj: TObject;
|
---|
106 | sl: TStrings;
|
---|
107 | const
|
---|
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 |
|
---|
135 | begin
|
---|
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;
|
---|
196 | end;
|
---|
197 |
|
---|
198 | procedure 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;
|
---|
243 | begin
|
---|
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);
|
---|
255 | end;
|
---|
256 |
|
---|
257 | function GetSectionData(const AFileName, ASectionName: String): TStrings;
|
---|
258 | var
|
---|
259 | f: TextFile;
|
---|
260 | s: String;
|
---|
261 | sl: TStringList;
|
---|
262 | bReading: Boolean;
|
---|
263 | begin
|
---|
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;
|
---|
304 | end;
|
---|
305 |
|
---|
306 | procedure GetStyleHeader(const AFileName: String; AOutHeader: PBCStyleHeader);
|
---|
307 | var sl: TStrings;
|
---|
308 | begin
|
---|
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;
|
---|
324 | end;
|
---|
325 |
|
---|
326 | end.
|
---|
327 |
|
---|