lazarus/lcl/propertystorage.pas
2024-11-23 08:10:58 +02:00

814 lines
21 KiB
ObjectPascal

{ $Id$ }
{
*****************************************************************************
This file is part of the Lazarus Component Library (LCL)
See the file COPYING.modifiedLGPL.txt, included in this distribution,
for details about the license.
*****************************************************************************
}
unit PropertyStorage;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, RTLConsts, RTTIUtils;
Type
TPlacementOperation = (poSave, poRestore);
TCustomPropertyStorage = Class;
TStoredValue = Class;
TStoredValues = Class;
{ TStoredValue }
{$ifdef storevariant}
TStoredType = Variant;
{$else}
TStoredType = AnsiString;
{$endif}
TStoredValueEvent = procedure(Sender: TStoredValue;
var Value: TStoredType) of object;
TStoredValue = class(TCollectionItem)
private
FName: string;
FValue: TStoredType;
FKeyString: string;
FOnSave: TStoredValueEvent;
FOnRestore: TStoredValueEvent;
function IsValueStored: Boolean;
function GetStoredValues: TStoredValues;
protected
function GetDisplayName: string; override;
procedure SetDisplayName(const AValue: string); override;
public
constructor Create(ACollection: TCollection); override;
procedure Assign(Source: TPersistent); override;
procedure Clear;
procedure Save; virtual;
procedure Restore; virtual;
property StoredValues: TStoredValues read GetStoredValues;
published
property Name: string read FName write SetDisplayName;
property Value: TStoredType read FValue write FValue stored IsValueStored;
property KeyString: string read FKeyString write FKeyString;
property OnSave: TStoredValueEvent read FOnSave write FOnSave;
property OnRestore: TStoredValueEvent read FOnRestore write FOnRestore;
end;
{ TStoredValues }
TStoredValues = class(TOwnedCollection)
private
FStorage: TCustomPropertyStorage;
function GetValue(const AName: string): TStoredValue;
procedure SetValue(const AName: string; AStoredValue: TStoredValue);
function GetStoredValue(const AName: string): TStoredType;
procedure SetStoredValue(const AName: string; Value: TStoredType);
function GetItem(Index: Integer): TStoredValue;
procedure SetItem(Index: Integer; AStoredValue: TStoredValue);
public
constructor Create(AOwner: TPersistent);
function IndexOf(const AName: string): Integer;
procedure SaveValues; virtual;
procedure RestoreValues; virtual;
property Storage: TCustomPropertyStorage read FStorage write FStorage;
property Items[Index: Integer]: TStoredValue read GetItem write SetItem; default;
property Values[const Name: string]: TStoredValue read GetValue write SetValue;
property StoredValue[const Name: string]: TStoredType read GetStoredValue write SetStoredValue;
end;
{ TCustomPropertyStorage }
TPropertyStorageLink = class(TPersistent)
private
FStorage: TCustomPropertyStorage;
FOnSave: TNotifyEvent;
FOnLoad: TNotifyEvent;
function GetRootSection: string;
procedure SetStorage(Value: TCustomPropertyStorage);
protected
procedure SaveProperties; virtual;
procedure LoadProperties; virtual;
public
destructor Destroy; override;
property Storage: TCustomPropertyStorage read FStorage write SetStorage;
property RootSection: string read GetRootSection;
property OnSave: TNotifyEvent read FOnSave write FOnSave;
property OnLoad: TNotifyEvent read FOnLoad write FOnLoad;
end;
{ TCustomPropertyStorage }
TPropertyStorageSaveExceptionEvent = procedure(Sender: TObject;
const ExClassName: String;
const ExMessage: String) of object;
TCustomPropertyStorage = Class (TComponent)
private
FOnRestoringProperties: TNotifyEvent;
FOnSavingProperties: TNotifyEvent;
FStoredValues: TStoredValues;
FActive: Boolean;
FLinks: TList;
FSaved: Boolean;
FOnSaveProperties: TNotifyEvent;
FOnRestoreProperties: TNotifyEvent;
FOnSaveException:TPropertyStorageSaveExceptionEvent;
procedure AddLink(ALink: TPropertyStorageLink);
procedure RemoveLink(ALink: TPropertyStorageLink);
procedure NotifyLinks(Operation: TPlacementOperation);
procedure SetStoredValues(Value: TStoredValues);
function GetStoredValue(const AName: string): TStoredType;
procedure SetStoredValue(const AName: string; Value: TStoredType);
protected
function GetRoot: TComponent; virtual;
function RootSection: String; Virtual;
procedure SaveProperties; virtual;
procedure RestoreProperties; virtual;
procedure GetPropertyList(List: TStrings); virtual; abstract;
procedure FinishPropertyList(List: TStrings); virtual;
function DoReadInteger(const Section, Ident : String; DefaultValue: Integer): Integer; Virtual;
function DoReadString(const Section, Ident, DefaultValue: string): string; Virtual; Abstract;
procedure DoWriteString(const Section, Ident, Value: string); Virtual; Abstract;
procedure DoWriteInteger(const Section, Ident : String; Value: Integer); Virtual;
procedure DoEraseSections(const ARootSection : String); virtual; abstract;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Save; virtual;
procedure Restore; virtual;
// Public Read/Write methods
procedure StorageNeeded(ReadOnly: Boolean); Virtual;
procedure FreeStorage; Virtual;
function ReadBoolean(const Ident: string; DefaultValue: Boolean): Boolean;
function ReadString(const Ident, DefaultValue: string): string;
function ReadInteger(const Ident: string; DefaultValue: Longint): Longint;
procedure ReadRect(const Ident: string; out ARect: TRect;
const Default: TRect);
procedure ReadStrings(const Ident: string; const List: TStrings;
const DefaultList: TStrings = nil);
procedure WriteString(const Ident, Value: string);
procedure WriteInteger(const Ident: string; Value: Longint);
procedure WriteBoolean(const Ident: string; Value: Boolean);
procedure WriteRect(const Ident: string; const Value: TRect);
procedure WriteStrings(const Ident: string; const List: TStrings);
procedure EraseSections;
public
property StoredValue[const AName: string]: TStoredType read GetStoredValue write SetStoredValue;
property Root: TComponent read GetRoot;
property Active: Boolean read FActive write FActive default True;
property StoredValues: TStoredValues read FStoredValues write SetStoredValues;
property OnSavingProperties: TNotifyEvent read FOnSavingProperties write FOnSavingProperties;
property OnSaveProperties: TNotifyEvent read FOnSaveProperties write FOnSaveProperties;
property OnRestoringProperties: TNotifyEvent read FOnRestoringProperties write FOnRestoringProperties;
property OnRestoreProperties: TNotifyEvent read FOnRestoreProperties write FOnRestoreProperties;
property OnSaveException: TPropertyStorageSaveExceptionEvent read FOnSaveException write FOnSaveException;
end;
implementation
function XorEncode(const Key, Source: string): string;
var
I: Integer;
C: Byte;
begin
Result := '';
for I := 1 to Length(Source) do begin
if Length(Key) > 0 then
C := Byte(Key[1 + ((I - 1) mod Length(Key))]) xor Byte(Source[I])
else
C := Byte(Source[I]);
Result := Result + AnsiLowerCase(IntToHex(C, 2));
end;
end;
function XorDecode(const Key, Source: string): string;
var
I: Integer;
C: Char;
begin
Result := '';
for I := 0 to Length(Source) div 2 - 1 do begin
C := Chr(StrToIntDef('$' + Copy(Source, (I * 2) + 1, 2), Ord(' ')));
if Length(Key) > 0 then
C := Chr(Byte(Key[1 + (I mod Length(Key))]) xor Byte(C));
Result := Result + C;
end;
end;
{ TPropertyStorageLink }
destructor TPropertyStorageLink.Destroy;
begin
FOnSave := nil;
FOnLoad := nil;
SetStorage(nil);
inherited Destroy;
end;
function TPropertyStorageLink.GetRootSection: string;
begin
if Assigned(FStorage) then
Result:=FStorage.RootSection
else
Result:='';
if Result<>'' then
Result:=Result+'\';
end;
procedure TPropertyStorageLink.SetStorage(Value: TCustomPropertyStorage);
begin
if FStorage <> Value then
begin
if FStorage <> nil then
FStorage.RemoveLink(Self);
if Value <> nil then
Value.AddLink(Self);
end;
end;
procedure TPropertyStorageLink.SaveProperties;
begin
if Assigned(FOnSave) then
FOnSave(Self);
end;
procedure TPropertyStorageLink.LoadProperties;
begin
if Assigned(FOnLoad) then
FOnLoad(Self);
end;
{ TStoredValue }
constructor TStoredValue.Create(ACollection: TCollection);
begin
inherited Create(ACollection);
{$ifdef storevariant}
FValue := Unassigned;
{$else}
FValue:='';
{$endif}
end;
procedure TStoredValue.Assign(Source: TPersistent);
begin
if (Source is TStoredValue) and (Source <> nil) then
begin
{$ifdef storevariant}
if VarIsEmpty(TStoredValue(Source).FValue) then
Clear
else
{$endif}
Value := TStoredValue(Source).FValue;
Name := TStoredValue(Source).Name;
KeyString := TStoredValue(Source).KeyString;
end;
end;
function TStoredValue.GetDisplayName: string;
begin
if FName = '' then
Result := inherited GetDisplayName
else
Result := FName;
end;
procedure TStoredValue.SetDisplayName(const AValue: string);
begin
if (AValue <> '') and (AnsiCompareText(AValue, FName) <> 0)
and (Collection is TStoredValues)
and (TStoredValues(Collection).IndexOf(AValue) >= 0) then
raise Exception.Create(SDuplicateString);
FName := AValue;
inherited;
end;
function TStoredValue.GetStoredValues: TStoredValues;
begin
if Collection is TStoredValues then
Result := TStoredValues(Collection)
else
Result := nil;
end;
procedure TStoredValue.Clear;
begin
{$ifdef storevariant}
FValue := Unassigned;
{$else}
FValue := '';
{$endif}
end;
function TStoredValue.IsValueStored: Boolean;
begin
{$ifdef storevariant}
Result := not VarIsEmpty(FValue);
{$else}
Result := (FValue<>'');
{$endif}
end;
procedure TStoredValue.Save;
var
SaveValue: TStoredType;
SaveStrValue: string;
begin
SaveValue := Value;
if Assigned(FOnSave) then
FOnSave(Self, SaveValue);
{$ifdef storevariant}
SaveStrValue := VarToStr(SaveValue);
{$else}
SaveStrValue := SaveValue;
{$endif}
if KeyString <> '' then
SaveStrValue := XorEncode(KeyString, SaveStrValue);
StoredValues.Storage.WriteString(Name, SaveStrValue);
end;
procedure TStoredValue.Restore;
var
RestoreValue: TStoredType;
RestoreStrValue, DefaultStrValue: string;
begin
{$ifdef storevariant}
DefaultStrValue := VarToStr(Value);
{$else}
DefaultStrValue := Value;
{$endif}
if KeyString <> '' then
DefaultStrValue := XorEncode(KeyString, DefaultStrValue);
RestoreStrValue := StoredValues.Storage.ReadString(Name, DefaultStrValue);
if KeyString <> '' then
RestoreStrValue := XorDecode(KeyString, RestoreStrValue);
RestoreValue := RestoreStrValue;
if Assigned(FOnRestore) then
FOnRestore(Self, RestoreValue);
Value := RestoreValue;
end;
{ TStoredValues }
constructor TStoredValues.Create(AOwner: TPersistent);
begin
inherited Create(AOwner, TStoredValue);
If AOwner is TCustomPropertyStorage then
FStorage:=TCustomPropertyStorage(AOwner);
end;
function TStoredValues.IndexOf(const AName: string): Integer;
begin
for Result := 0 to Count - 1 do
if AnsiCompareText(Items[Result].Name, AName) = 0 then Exit;
Result := -1;
end;
function TStoredValues.GetItem(Index: Integer): TStoredValue;
begin
Result := TStoredValue(inherited Items[Index]);
end;
procedure TStoredValues.SetItem(Index: Integer; AStoredValue: TStoredValue);
begin
inherited SetItem(Index, TCollectionItem(AStoredValue));
end;
function TStoredValues.GetStoredValue(const AName: string): TStoredType;
var
AStoredValue: TStoredValue;
begin
AStoredValue := GetValue(AName);
if AStoredValue = nil then
{$ifdef storevariant}
Result := Null
{$else}
Result := ''
{$endif}
else
Result := AStoredValue.Value;
end;
procedure TStoredValues.SetStoredValue(const AName: string; Value: TStoredType);
var
AStoredValue: TStoredValue;
begin
AStoredValue := GetValue(AName);
if AStoredValue = nil then begin
AStoredValue := TStoredValue(Add);
AStoredValue.Name := AName;
AStoredValue.Value := Value;
end
else AStoredValue.Value := Value;
end;
function TStoredValues.GetValue(const AName: string): TStoredValue;
var
I: Integer;
begin
I := IndexOf(AName);
if I < 0 then
Result := nil
else
Result := Items[I];
end;
procedure TStoredValues.SetValue(const AName: string; AStoredValue: TStoredValue);
var
I: Integer;
begin
I := IndexOf(AName);
if I >= 0 then
Items[I].Assign(AStoredValue);
end;
procedure TStoredValues.SaveValues;
var
I: Integer;
begin
for I := 0 to Count - 1 do
Items[I].Save;
end;
procedure TStoredValues.RestoreValues;
var
I: Integer;
begin
for I := 0 to Count - 1 do
Items[I].Restore;
end;
{ TCustomPropertyStorage }
constructor TCustomPropertyStorage.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FActive := True;
FLinks := TList.Create;
FStoredValues:=TStoredValues.Create(Self);
FStoredValues.Storage:=Self;
end;
destructor TCustomPropertyStorage.Destroy;
begin
FreeStorage;
FStoredValues.Free;
while FLinks.Count > 0 do
RemoveLink(TPropertyStorageLink(FLinks.Last));
FreeAndNil(FLinks);
inherited Destroy;
end;
procedure TCustomPropertyStorage.AddLink(ALink: TPropertyStorageLink);
begin
FLinks.Add(ALink);
ALink.FStorage := Self;
end;
procedure TCustomPropertyStorage.NotifyLinks(Operation: TPlacementOperation);
var
I: Integer;
begin
for I := 0 to FLinks.Count - 1 do
with TPropertyStorageLink(FLinks[I]) do
case Operation of
poSave: SaveProperties;
poRestore: LoadProperties;
end;
end;
procedure TCustomPropertyStorage.RemoveLink(ALink: TPropertyStorageLink);
begin
ALink.FStorage := nil;
FLinks.Remove(ALink);
end;
function TCustomPropertyStorage.GetRoot: TComponent;
begin
Result:=Owner;
end;
function TCustomPropertyStorage.RootSection : string;
var
ARoot: TPersistent;
Prepend: String;
begin
Result:='';
ARoot:=Root;
while ARoot<>nil do begin
if (ARoot is TComponent) and (TComponent(ARoot).Name<>'') then
Prepend:=TComponent(ARoot).Name
else begin
Prepend:=ARoot.ClassName;
ARoot:=nil;
end;
if Result<>'' then
Result:=Prepend+'.'+Result
else
Result:=Prepend;
if not (ARoot is TComponent) then break;
ARoot:=TComponent(ARoot).Owner;
end;
end;
procedure TCustomPropertyStorage.Save;
begin
if Active and not (csDesigning in ComponentState) then begin
StorageNeeded(False);
Try
if Assigned(FOnSavingProperties) then
FOnSavingProperties(Self);
try
SaveProperties;
FStoredValues.SaveValues;
NotifyLinks(poSave);
if Assigned(FOnSaveProperties) then
FOnSaveProperties(Self);
FSaved := True;
except
on E: Exception do
begin
if Assigned(FOnSaveException) then
FOnSaveException(Self, E.ClassName, E.Message);
end;
end;
Finally
FreeStorage;
end;
end;
end;
procedure TCustomPropertyStorage.Restore;
begin
if Active and not (csDesigning in ComponentState) then begin
FSaved := False;
StorageNeeded(True);
try
if Assigned(FOnRestoringProperties) then
FOnRestoringProperties(Self);
FStoredValues.RestoreValues;
RestoreProperties;
NotifyLinks(poRestore);
if Assigned(FOnRestoreProperties) then
FOnRestoreProperties(Self);
finally
FreeStorage;
end;
end;
end;
procedure TCustomPropertyStorage.SaveProperties;
Var
AStoredList : TStringList;
begin
AStoredList:=TStringList.Create;
Try
GetPropertyList(AStoredList);
FinishPropertyList(AStoredList);
StorageNeeded(False);
Try
with TPropsStorage.Create do
try
Section := RootSection;
OnWriteString := @DoWriteString;
OnEraseSection := @DoEraseSections;
StoreObjectsProps(Owner,AStoredList);
finally
Free;
end;
Finally
FreeStorage;
end;
finally
AStoredList.Free;
end;
end;
procedure TCustomPropertyStorage.RestoreProperties;
Var
L : TStringList;
begin
L:=TStringList.Create;
Try
GetPropertyList(L);
FinishPropertyList(L);
StorageNeeded(True);
Try
with TPropsStorage.Create do
try
Section := RootSection;
OnReadString := @DoReadString;
try
LoadObjectsProps(Owner,L);
except
{ ignore any exceptions }
// ToDo: Why?
end;
finally
Free;
end;
Finally
FreeStorage;
end;
finally
L.Free;
end;
end;
procedure TCustomPropertyStorage.FinishPropertyList(List: TStrings);
var
i: Integer;
CompName: string;
PropName: string;
ARoot: TComponent;
AComponent: TComponent;
begin
// set Objects (i.e. the component of each property)
ARoot:=Root;
for i:=List.Count-1 downto 0 do begin
if ParseStoredItem(List[I], CompName, PropName) then begin
if CompareText(ARoot.Name,CompName)=0 then
List.Objects[i]:=ARoot
else begin
AComponent:=Root.FindComponent(CompName);
if AComponent<>nil then
List.Objects[i]:=AComponent
else
List.Delete(i);
end;
end else begin
List.Delete(i);
end;
end;
end;
function TCustomPropertyStorage.DoReadInteger(const Section, Ident: String;
DefaultValue: Integer): Integer;
begin
Result:=StrToIntDef(DoReadString(Section,Ident,IntToStr(DefaultValue)),DefaultValue);
end;
procedure TCustomPropertyStorage.DoWriteInteger(const Section, Ident: String;
Value: Integer);
begin
DoWriteString(Section,Ident,IntToStr(Value))
end;
procedure TCustomPropertyStorage.StorageNeeded(ReadOnly: Boolean);
begin
end;
procedure TCustomPropertyStorage.FreeStorage;
begin
end;
function TCustomPropertyStorage.ReadString(const Ident, DefaultValue: string): string;
begin
StorageNeeded(True);
try
Result := DoReadString(RootSection, Ident, DefaultValue);
finally
FreeStorage;
end;
end;
procedure TCustomPropertyStorage.WriteString(const Ident, Value: string);
begin
StorageNeeded(False);
try
DoWriteString(RootSection, Ident, Value);
finally
FreeStorage;
end;
end;
function TCustomPropertyStorage.ReadInteger(const Ident: string; DefaultValue: Longint): Longint;
begin
StorageNeeded(True);
try
Result := DoReadInteger(RootSection, Ident, DefaultValue);
finally
FreeStorage;
end;
end;
function TCustomPropertyStorage.ReadBoolean(const Ident: string; DefaultValue: Boolean): Boolean;
begin
Result := ReadInteger(Ident, Ord(DefaultValue)) <> Ord(False);
end;
procedure TCustomPropertyStorage.ReadRect(const Ident: string;
out ARect: TRect; const Default: TRect);
begin
ARect.Left:=ReadInteger(Ident+'Left',Default.Left);
ARect.Top:=ReadInteger(Ident+'Top',Default.Top);
ARect.Right:=ReadInteger(Ident+'Right',Default.Right);
ARect.Bottom:=ReadInteger(Ident+'Bottom',Default.Bottom);
end;
procedure TCustomPropertyStorage.ReadStrings(const Ident: string;
const List: TStrings; const DefaultList: TStrings);
var
sl: TStringList;
NewCount: LongInt;
i: Integer;
begin
if ReadString(Ident+'Count','')='' then begin
// use default
if DefaultList<>nil then
List.Assign(DefaultList)
else
List.Clear;
exit;
end;
// read list into a temporary list and then use Assign to copy in one step
sl:=TStringList.Create;
try
NewCount:=ReadInteger(Ident+'Count',0);
for i:=0 to NewCount-1 do
sl.Add(ReadString(Ident+'Item'+IntToStr(i+1),''));
List.Assign(sl);
finally
sl.Free;
end;
end;
procedure TCustomPropertyStorage.WriteInteger(const Ident: string; Value: Longint);
begin
StorageNeeded(False);
try
DoWriteInteger(RootSection, Ident, Value);
finally
FreeStorage;
end;
end;
procedure TCustomPropertyStorage.WriteBoolean(const Ident: string; Value: Boolean);
begin
WriteInteger(Ident, Ord(Value));
end;
procedure TCustomPropertyStorage.WriteRect(const Ident: string;
const Value: TRect);
begin
WriteInteger(Ident+'Left',Value.Left);
WriteInteger(Ident+'Top',Value.Top);
WriteInteger(Ident+'Right',Value.Right);
WriteInteger(Ident+'Bottom',Value.Bottom);
end;
procedure TCustomPropertyStorage.WriteStrings(const Ident: string;
const List: TStrings);
var
i: Integer;
begin
WriteInteger(Ident+'Count',List.Count);
for i:=0 to List.Count-1 do
WriteString(Ident+'Item'+IntToStr(i+1),List[i]);
end;
procedure TCustomPropertyStorage.EraseSections;
begin
StorageNeeded(False);
try
DoEraseSections(RootSection);
finally
FreeStorage;
end;
end;
procedure TCustomPropertyStorage.SetStoredValues(Value: TStoredValues);
begin
FStoredValues.Assign(Value);
end;
function TCustomPropertyStorage.GetStoredValue(const AName: string): TStoredType;
begin
Result := StoredValues.StoredValue[AName];
end;
procedure TCustomPropertyStorage.SetStoredValue(const AName: string; Value: TStoredType);
begin
StoredValues.StoredValue[AName] := Value;
end;
end.