unit uFilter;
{$INCLUDE 'Filters.inc'}

interface

uses
  SysUtils, Classes, Generics.Collections,
  {$INCLUDE 'DelphiFarUnits.inc'} ,
  uConfig, uMessages;

type
  EFilterError = class(EFarPluginError);

  TFilterClass = class of TFilter;

  TFilter = class
  public
    type TPriority = (prNone, prLow, prNormal, prHigh);
  private
    fDescription: string;
    fFileMasks: TStringList;
    fEditBeforeUse: boolean;
    fConfigKey: string;
    function GetFileMaskStr: string;
    procedure SetFileMaskStr(Value: string);
  protected
    function CreateFilterDialog: TObject; virtual;
  public
    class function ID: string; virtual; abstract;
    class function Name: string; virtual; abstract;
    class function Priority: TPriority; virtual;
    class function CreateFromConfig(Config: TFilterConfig): TFilter;
    constructor Create; virtual;
    destructor Destroy; override;
    procedure Clear; virtual;
    procedure Assign(Source: TObject); virtual;
    function Clone: TFilter; virtual;
    procedure LoadFromConfig(Config: TFilterConfig); virtual;
    procedure SaveToConfig(Config: TFilterConfig); virtual;
    function IsSupported(const FileName, Content: string): boolean; virtual;
    function Execute(const FileName, Content: string): string; virtual; abstract;
    function Configure: boolean;
    function ConfigureBeforeUse: boolean;
    property Description: string read fDescription write fDescription;
    property FileMasks: TStringList read fFileMasks;
    property FileMaskStr: string read GetFileMaskStr write SetFileMaskStr;
    property EditBeforeUse: boolean read fEditBeforeUse write fEditBeforeUse;
    property ConfigKey: string read fConfigKey;
  end;

  TRegisteredFilters = class
  private
    class var fList: TList<TFilterClass>;
    class constructor CreateClass;
    class destructor DestroyClass;
    class function GetCount: integer; static;
    class function GetItem(Index: integer): TFilterClass; static;
  public
    class procedure Add(FilterClass: TFilterClass);
    class function Find(const ID: string; out FilterClass: TFilterClass): boolean;
    class property Count: integer read GetCount;
    class property Items[Index: integer]: TFilterClass read GetItem;
  end;

  TFilterList = class
  private
    fList: TObjectList<TFilter>;
    function GetCount: integer;
    function GetItem(Index: integer): TFilter;
  protected
    property List: TObjectList<TFilter> read fList;
  public
    constructor Create(OwnsObjects: boolean);
    destructor Destroy; override;
    procedure Clear;
    procedure Add(Item: TFilter);
    procedure Delete(Index: integer);
    procedure Insert(Index: Integer; Item: TFilter);
    procedure Exchange(Index1, Index2: integer);
    property Items[Index: integer]: TFilter read GetItem; default;
    property Count: integer read GetCount;
  end;

implementation

uses
  uConsts, uFilterUI;

type
  TStandardFilterDialog = class(TFilterDialog)
  public
    constructor Create;
  end;

{ TFilterList }

procedure TFilterList.Add(Item: TFilter);
begin
  List.Add(Item);
end;

procedure TFilterList.Clear;
begin
  List.Clear;
end;

constructor TFilterList.Create(OwnsObjects: boolean);
begin
  inherited Create;
  fList := TObjectList<TFilter>.Create(OwnsObjects);
end;

procedure TFilterList.Delete(Index: integer);
begin
  List.Delete(Index);
end;

destructor TFilterList.Destroy;
begin
  Clear;
  FreeAndNil(fList);
  inherited;
end;

procedure TFilterList.Exchange(Index1, Index2: integer);
begin
  List.Exchange(Index1, Index2);
end;

function TFilterList.GetCount: integer;
begin
  Result := List.Count;
end;

function TFilterList.GetItem(Index: integer): TFilter;
begin
  Result := List[Index];
end;

procedure TFilterList.Insert(Index: Integer; Item: TFilter);
begin
  if Index < 0 then
    List.Insert(0, Item)
  else if Index >= List.Count then
    List.Add(Item)
  else
    List.Insert(Index, Item);
end;

{ TRegisteredFilters }

class constructor TRegisteredFilters.CreateClass;
begin
  fList := TList<TFilterClass>.Create;
end;

class destructor TRegisteredFilters.DestroyClass;
begin
  FreeAndNil(fList);
end;

class procedure TRegisteredFilters.Add(FilterClass: TFilterClass);
var
  i: integer;
begin
  for i := 0 to Pred(fList.Count) do
  begin
    if fList[i] = FilterClass then
      Exit;
    if fList[i].ID = FilterClass.ID then
      Raise EFilterError.Create(FilterClass.ClassName + ': Duplicate ID');
  end;
  fList.Add(FilterClass);
end;

class function TRegisteredFilters.Find(const ID: string; out FilterClass: TFilterClass): boolean;
var
  i: integer;
begin
  Result := False;
  for i := 0 to Pred(fList.Count) do
  begin
    if fList[i].ID = ID then
    begin
      FilterClass := fList[i];
      Result := True;
      Break;
    end;
  end;
end;

class function TRegisteredFilters.GetCount: integer;
begin
  Result := fList.Count;
end;

class function TRegisteredFilters.GetItem(Index: integer): TFilterClass;
begin
  Result := fList[Index];
end;

{ TFilter }

procedure TFilter.Assign(Source: TObject);
var
  Src: TFilter;
begin
  if Source = nil then
    raise EInvalidOp.Create('Cannot assign filter from a NIL object');
  if not (Source is TFilter) then
    raise EInvalidOp.Create('Cannot assign filter from a non-filter object');
  Src := TFilter(Source);
  Self.Description := Src.Description;
  Self.FileMaskStr := Src.FileMaskStr;
  Self.EditBeforeUse := Src.EditBeforeUse;
  Self.fConfigKey := Src.fConfigKey;
end;

procedure TFilter.Clear;
begin
  Description := '';
  FileMasks.Clear;
  EditBeforeUse := False;
  fConfigKey := '';
end;

function TFilter.Clone: TFilter;
begin
  Result := TFilterClass(Self.ClassType).Create;
  try
    Result.Assign(Self);
  except
    FreeAndNil(Result);
    Raise;
  end;
end;

function TFilter.Configure: boolean;
var
  Dlg: TFilterDialog;
begin
  Dlg := CreateFilterDialog as TFilterDialog;
  try
    Dlg.Filter := Self;
    Result := Dlg.Execute;
  finally
    FreeAndNil(Dlg);
  end;
end;

function TFilter.ConfigureBeforeUse: boolean;
var
  Dlg: TFilterDialog;
  FilterConfig: TFilterConfig;
begin
  Dlg := CreateFilterDialog as TFilterDialog;
  try
    Dlg.Filter := Self;
    TStandardFilterDialog(Dlg).CheckSaveConfigBeforeUse.Visible := True;
    TStandardFilterDialog(Dlg).SaveCofigBeforeUse := False;
    Result := Dlg.Execute;
    if Result then
    begin
      if TStandardFilterDialog(Dlg).SaveCofigBeforeUse and (Self.ConfigKey <> '') then
      begin
        FilterConfig := TFilterConfig.Create;
        try
          FilterConfig.MustExist := True;
          FilterConfig.Key := Self.ConfigKey;
          Self.SaveToConfig(FilterConfig);
        finally
          FreeAndNil(FilterConfig);
        end;
      end;
    end;
  finally
    FreeAndNil(Dlg);
  end;
end;

constructor TFilter.Create;
begin
  inherited Create;
  fFileMasks := TStringList.Create;
  Clear;
end;

function TFilter.CreateFilterDialog: TObject;
begin
  Result := TStandardFilterDialog.Create;
end;

class function TFilter.CreateFromConfig(Config: TFilterConfig): TFilter;
var
  FilterClass: TFilterClass;
  s: string;
begin
  s := Config.ID;
  if (s = '') or (not TRegisteredFilters.Find(s, FilterClass)) then
    raise EFilterError.CreateFmt(GetMsg(MUnknownFilter), [s]);
  Result := FilterClass.Create;
  try
    Result.LoadFromConfig(Config);
  except
    FreeAndNil(Result);
    raise;
  end;
end;

destructor TFilter.Destroy;
begin
  FreeAndNil(fFileMasks);
  inherited;
end;

function TFilter.GetFileMaskStr: string;
var
  i: integer;
  s: string;
begin
  Result := '';
  for i := 0 to Pred(FileMasks.Count) do
  begin
    s := Trim(FileMasks[i]);
    if s <> '' then
    begin
      if Result = '' then
        Result := s
      else
        Result := Result + ',' + s;
    end;
  end;
end;

function TFilter.IsSupported(const FileName, Content: string): boolean;
var
  i: integer;
begin
  if FileName = '' then
  begin
    Result := True;
  end
  else if FileMasks.Count <= 0 then
  begin
    Result := True;
  end
  else
  begin
    Result := False;
    for i := 0 to Pred(FileMasks.Count) do
    begin
      if TFarPlugin.Instance.FileNameMatchesMask(FileName, FileMasks[i]) then
      begin
        Result := True;
        Break;
      end;
    end;
  end;
end;

procedure TFilter.LoadFromConfig(Config: TFilterConfig);
begin
  Clear;
  Self.Description := Config.Description;
  Self.FileMaskStr := Config.FileMaskStr;
  Self.EditBeforeUse := Config.EditBeforeUse;
  Self.fConfigKey := Config.Key;
end;

class function TFilter.Priority: TPriority;
begin
  Result := prNormal;
end;

procedure TFilter.SaveToConfig(Config: TFilterConfig);
begin
  Config.ID := Self.ID;
  Config.Description := Self.Description;
  Config.FileMaskStr := Self.FileMaskStr;
  Config.EditBeforeUse := Self.EditBeforeUse;
  Self.fConfigKey := Config.Key;
end;

procedure TFilter.SetFileMaskStr(Value: string);
var
  ix: integer;
  Mask: string;
begin
  FileMasks.Clear;
  while Value <> '' do
  begin
    ix := Pos(',', Value);
    if ix > 0 then
    begin
      Mask := Trim(Copy(Value, 1, Pred(ix)));
      Delete(Value, 1, ix);
    end
    else
    begin
      Mask := Trim(Value);
      Value := '';
    end;
    if Mask <> '' then
    begin
      FileMasks.Add(Mask);
    end;
  end;
end;

{ TStandardFilterDialog }

constructor TStandardFilterDialog.Create;
begin
  inherited Create(PLUGIN_FILTER_BASE_GUID);
end;

end.
