| 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 |
|
|---|