{  $Id: inipropstorage.pas 18002 2008-12-31 03:07:17Z paul $  }
{
 *****************************************************************************
 *                                                                           *
 *  This file is part of the Lazarus Component Library (LCL)                 *
 *                                                                           *
 *  See the file COPYING.modifiedLGPL.txt, included in this distribution,    *
 *  for details about the copyright.                                         *
 *                                                                           *
 *  This program is distributed in the hope that it will be useful,          *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                     *
 *                                                                           *
 *****************************************************************************
}
unit IniPropStorage;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, IniFiles, PropertyStorage;

type
  { TCustomIniPropStorage }

  TIniFileClass = class of TCustomIniFile;
  
  TCustomIniPropStorage = class(TFormPropertyStorage)
  private
    FCount : Integer;
    FReadOnly : Boolean;
    FIniFile: TCustomIniFile;
    FIniFileName: string;
    FIniSection: string;
  protected
    function IniFileClass: TIniFileClass; virtual;
    function GetIniFileName: string; virtual;
    function RootSection: string; override;
    property IniFile: TCustomIniFile read FIniFile;
  public
    procedure StorageNeeded(ReadOnly: Boolean); override;
    procedure FreeStorage; override;
    function  DoReadString(const Section, Ident, default: string): string; override;
    procedure DoWriteString(const Section, Ident, Value: string); override;
    procedure DoEraseSections(const ARootSection : string);override;
  public
    property IniFileName: string read FIniFileName write FIniFileName;
    property IniSection: string read FIniSection write FIniSection;
  end;
  
  { TIniPropStorage }
  
  TIniPropStorage = class(TCustomIniPropStorage)
  published
    Property StoredValues;
    property IniFileName;
    property IniSection;
    property Active;
    property OnSavingProperties;
    property OnSaveProperties;
    property OnRestoringProperties;
    property OnRestoreProperties;
  end;


procedure Register;


implementation


procedure Register;
begin
  RegisterComponents('Misc',[TIniPropStorage]);
end;

{ Should move to strutils when 1.9.6 is out. }

function FindPart(const HelpWilds, InputStr: string): Integer;

var
  I, J: Integer;
  Diff: Integer;

begin
  I := Pos('?', HelpWilds);
  if I = 0 then begin
    { if no '?' in HelpWilds }
    Result := Pos(HelpWilds, InputStr);
    Exit;
  end;
  { '?' in HelpWilds }
  Diff := Length(InputStr) - Length(HelpWilds);
  if Diff < 0 then begin
    Result := 0;
    Exit;
  end;
  { now move HelpWilds over InputStr }
  for I := 0 to Diff do begin
    for J := 1 to Length(HelpWilds) do begin
      if (InputStr[I + J] = HelpWilds[J]) or
        (HelpWilds[J] = '?') then
      begin
        if J = Length(HelpWilds) then begin
          Result := I + 1;
          Exit;
        end;
      end
      else Break;
    end;
  end;
  Result := 0;
end;



function IsWild(InputStr, Wilds: string; IgnoreCase: Boolean): Boolean;

 function SearchNext(var Wilds: string): Integer;
 { looking for next *, returns position and string until position }
 begin
   Result := Pos('*', Wilds);
   if Result > 0 then Wilds := Copy(Wilds, 1, Result - 1);
 end;

var
  CWild, CInputWord: Integer; { counter for positions }
  I, LenHelpWilds: Integer;
  MaxInputWord, MaxWilds: Integer; { Length of InputStr and Wilds }
  HelpWilds: string;
begin
  if Wilds = InputStr then begin
    Result := True;
    Exit;
  end;
  repeat { delete '**', because '**' = '*' }
    I := Pos('**', Wilds);
    if I > 0 then
      Wilds := Copy(Wilds, 1, I - 1) + '*' + Copy(Wilds, I + 2, MaxInt);
  until I = 0;
  if Wilds = '*' then begin { for fast end, if Wilds only '*' }
    Result := True;
    Exit;
  end;
  MaxInputWord := Length(InputStr);
  MaxWilds := Length(Wilds);
  if IgnoreCase then begin { upcase all letters }
    InputStr := AnsiUpperCase(InputStr);
    Wilds := AnsiUpperCase(Wilds);
  end;
  if (MaxWilds = 0) or (MaxInputWord = 0) then begin
    Result := False;
    Exit;
  end;
  CInputWord := 1;
  CWild := 1;
  Result := True;
  repeat
    if InputStr[CInputWord] = Wilds[CWild] then begin { equal letters }
      { goto next letter }
      Inc(CWild);
      Inc(CInputWord);
      Continue;
    end;
    if Wilds[CWild] = '?' then begin { equal to '?' }
      { goto next letter }
      Inc(CWild);
      Inc(CInputWord);
      Continue;
    end;
    if Wilds[CWild] = '*' then begin { handling of '*' }
      HelpWilds := Copy(Wilds, CWild + 1, MaxWilds);
      I := SearchNext(HelpWilds);
      LenHelpWilds := Length(HelpWilds);
      if I = 0 then begin
        { no '*' in the rest, compare the ends }
        if HelpWilds = '' then Exit; { '*' is the last letter }
        { check the rest for equal Length and no '?' }
        for I := 0 to LenHelpWilds - 1 do begin
          if (HelpWilds[LenHelpWilds - I] <> InputStr[MaxInputWord - I]) and
            (HelpWilds[LenHelpWilds - I]<> '?') then
          begin
            Result := False;
            Exit;
          end;
        end;
        Exit;
      end;
      { handle all to the next '*' }
      Inc(CWild, 1 + LenHelpWilds);
      I := FindPart(HelpWilds, Copy(InputStr, CInputWord, MaxInt));
      if I= 0 then begin
        Result := False;
        Exit;
      end;
      CInputWord := I + LenHelpWilds;
      Continue;
    end;
    Result := False;
    Exit;
  until (CInputWord > MaxInputWord) or (CWild > MaxWilds);
  { no completed evaluation }
  if CInputWord <= MaxInputWord then Result := False;
  if (CWild <= MaxWilds) and (Wilds[MaxWilds] <> '*') then Result := False;
end;



{ TCustomIniPropStorage }

function TCustomIniPropStorage.IniFileClass: TIniFileClass;
begin
  Result:=TIniFile;
end;

procedure TCustomIniPropStorage.StorageNeeded(ReadOnly: Boolean);
begin
  If (FIniFile=Nil) or (ReadOnly<>FReadOnly) then
    begin
    If (FiniFile<>Nil) then
      begin
      // Force free.
      FCount:=0;
      FreeStorage;
      end;
    FReadOnly:=ReadOnly;
    FInifile:=IniFileClass.Create(GetIniFileName);
    end;
  Inc(FCount);
end;

procedure TCustomIniPropStorage.FreeStorage;
begin
  Dec(FCount);
  If FCount<=0 then
    begin
    FCount:=0;
    FreeAndNil(FIniFile);
    end;
end;

function TCustomIniPropStorage.GetIniFileName: string;
begin
  If (FIniFileName<>'') then
    Result:=FIniFileName
  else
{$ifdef unix}
    Result:=IncludeTrailingPathDelimiter(GetEnvironmentVariableUTF8('HOME'))
            +'.'+ExtractFileName(Application.ExeName);

{$else}
    Result:=ChangeFileExt(Application.ExeName,'.ini');
{$endif}
end;

function TCustomIniPropStorage.RootSection: String;
begin
  if (FIniSection='') then
    Result:=inherited RootSection
  else
    Result:=FIniSection;
end;

function TCustomIniPropStorage.DoReadString(const Section, Ident, Default: string): string;
begin
  Result:=FIniFile.ReadString(Section, Ident, Default);
end;

procedure TCustomIniPropStorage.DoWriteString(const Section, Ident, Value: string);
begin
  FIniFile.WriteString(Section, Ident, Value);
end;

procedure TCustomIniPropStorage.DoEraseSections(const ARootSection: String);

var
  Lines: TStrings;
  I: Integer;
begin
  Lines := TStringList.Create;
  try
    FInifile.ReadSections(Lines);
    for I := 0 to Lines.Count - 1 do begin
      if (Lines[I] = IniSection) or
        (IsWild(Lines[I], IniSection + '.*', False) or
        IsWild(Lines[I], IniSection + '\*', False)) then
        FInifile.EraseSection(Lines[I]);
    end;
  finally
    Lines.Free;
  end;
end;

end.
