{************************************************}
{                                                }
{   Turbo Vision File Manager Demo               }
{   Copyright (c) 1992 by Borland International  }
{                                                }
{************************************************}

{$V-}

unit Assoc;  { Association list manager }

interface

uses Objects, Dos;

type
  PAssociation = ^TAssociation;
  TAssociation = object(TObject)
    Ext: ExtStr;
    Cmd: PString;
    Prompt: Boolean;
    constructor Init(AExt: ExtStr; const ACmd: String; APrompt: Boolean);
    constructor Load(var S: TStream);
    destructor Done; virtual;
    procedure Store(var S: TStream);
  end;

procedure InitAssociations;
procedure DoneAssociations;

procedure Associate(DefExt: ExtStr);
function GetAssociatedCommand(Ext: ExtStr): PAssociation;
procedure WriteAssociationList(var S: TStream);
procedure ReadAssociationList(var S: TStream);

procedure RegisterAssociations;

implementation

uses Drivers, Views, Dialogs, App, MsgBox, Validate, Tools;

const
  cmAddAssoc   = 100;
  cmEditAssoc  = cmAddAssoc + 1;
  cmDelAssoc   = cmEditAssoc + 1;

type
  { transfer record for a list box }
  TListBoxRec = record
    List: PCollection;
    Selection: Word;
  end;

  TAssocRec = record
    Extension: ExtStr;
    Command: String;
    Prompt: Word;
  end;

  PAssociateList = ^TAssociateList;
  TAssociateList = object(TCollection)
    procedure FillCloneList(P: PCollection);
    procedure UseCloneList(P: PCollection);
  end;

  PAssocBox = ^TAssocBox;
  TAssocBox = object(TListBox)
    function GetText(Item: Integer; MaxLen: Integer): String; virtual;
  end;

  PAssocDialog = ^TAssocDialog;
  TAssocDialog = object(TDialog)
    DefExt: ExtStr;
    ListBox: PAssocBox;
    constructor Init(ADefExt: ExtStr);
    procedure HandleEvent(var Event: TEvent); virtual;
  end;

  PExtValidator = ^TExtValidator;
  TExtValidator = object(TValidator)
    function IsValid(const S: string): Boolean; virtual;
    procedure Error; virtual;
  end;

  PNonBlankValidator = ^TNonBlankValidator;
  TNonBlankValidator = object(TPXPictureValidator)
    procedure Error; virtual;
  end;

const
  RAssociation : TStreamRec = (
    ObjType : 1001;
    VmtLink : Ofs(TypeOf(TAssociation)^);
    Load    : @TAssociation.Load;
    Store   : @TAssociation.Store
  );
  RAssociateList : TStreamRec = (
    ObjType : 1002;
    VmtLink : Ofs(TypeOf(TAssociateList)^);
    Load    : @TAssociateList.Load;
    Store   : @TAssociateList.Store
  );

const
  AssociateList: PAssociateList = nil;

{ TAssociateList }
procedure TAssociateList.FillCloneList(P: PCollection);

  procedure AddCloneItem(Item: PAssociation); far;
  begin
    P^.Insert(New(PAssociation, Init(Item^.Ext, Item^.Cmd^, Item^.Prompt)));
  end;

begin
  ForEach(@AddCloneItem);
end;

procedure TAssociateList.UseCloneList(P: PCollection);

  procedure UseCloneItem(Item: PAssociation); far;
  begin
    Insert(New(PAssociation, Init(Item^.Ext, Item^.Cmd^, Item^.Prompt)));
  end;

begin
  FreeAll;
  P^.ForEach(@UseCloneItem);
end;


{ TAssociation }
constructor TAssociation.Init(AExt: ExtStr; const ACmd: String;
  APrompt: Boolean);
begin
  inherited Init;
  Ext := AExt;
  Cmd := NewStr(ACmd);
  Prompt := APrompt;
end;

constructor TAssociation.Load(var S: TStream);
begin
  inherited Init;
  S.Read(Ext, SizeOf(Ext));
  Cmd := S.ReadStr;
  S.Read(Prompt, SizeOf(Prompt));
end;

destructor TAssociation.Done;
begin
  DisposeStr(Cmd);
  inherited Done;
end;

procedure TAssociation.Store(var S: TStream);
begin
  S.Write(Ext, SizeOf(Ext));
  S.WriteStr(Cmd);
  S.Write(Prompt, SizeOf(Prompt));
end;

{ TAssocBox }
function TAssocBox.GetText(Item: Integer; MaxLen: Integer): String;
var
  T: PAssociation;
  Params: array[0..1] of Longint;
  S: String;
begin
  T := List^.At(Item);
  Params[0] := Longint(@T^.Ext);
  Params[1] := Longint(T^.Cmd);
  FormatStr(S, '%-13s %s', Params);
  if Length(S) > MaxLen then
  begin
    S[0] := Char(MaxLen);
    { Fill the last three characters with an ellipses }
    FillChar(S[MaxLen - 4], 3, '.');
  end;
  GetText := S;
end;

function CreateEditDialog: PDialog;
var
  R: TRect;
  D: PDialog;
  P: PView;
begin
  R.Assign(0,0,60,9);
  D := New(PDialog, Init(R, 'Edit Association'));
  with D^ do
  begin
    Options := Options or ofCentered;
    R.Assign(17,2,58,3);
    P := New(PInputLine, Init(R, SizeOf(ExtStr) - 1));
    Insert(P);
    PInputLine(P)^.SetValidator(New(PExtValidator, Init));
    P^.Options := P^.Options or ofValidate;
    R.Assign(2,2,17,3);
    Insert(New(PLabel, Init(R, '~E~xtension', P)));

    R.Assign(17,3,58,4);
    P := New(PInputLine, Init(R, SizeOf(String) - 1));
    PInputLine(P)^.SetValidator(New(PNonBlankValidator, Init('@*[@]',False)));
    P^.Options := P^.Options or ofValidate;
    Insert(P);

    R.Assign(2,3,17,4);
    Insert(New(PLabel, Init(R, 'Co~m~mmand', P)));

    R.Assign(17,4,58,5);
    Insert(New(PCheckBoxes, Init(R, NewSItem('~P~rompt for parameters',
      nil))));

    R.Assign(2,6,12,8);
    Insert(New(PButton, Init(R, '~O~K', cmOK, bfDefault)));
    R.Move(12,0);
    Insert(New(PButton, Init(R, '~C~ancel', cmCancel, bfNormal)));

    SelectNext(False);
  end;
  CreateEditDialog := D;
end;

function AddAssociation(var ListBoxRec: TListBoxRec; DefExt: ExtStr): Word;
var
  D: PDialog;
  XFer: TAssocRec;
  Result: Word;
begin
  XFer.Extension := DefExt;
  XFer.Command := '';
  D := CreateEditDialog;
  Result := Application^.ExecuteDialog(D, @XFer);
  if Result = cmOK then with XFer do
  begin
    UpperCase(Extension);
    ListBoxRec.List^.Insert(New(PAssociation, Init(Extension, Command,
      Prompt > 0)));
  end;
  AddAssociation := Result;
end;

function EditAssociation(var ListBoxRec: TListBoxRec): Word;
var
  D: PDialog;
  XFer: TAssocRec;
  Assoc: PAssociation;
  Result: Integer;
begin
  Result := cmCancel;
  if ListBoxRec.List^.Count = 0 then Exit;
  Assoc := ListBoxRec.List^.At(ListBoxRec.Selection);
  XFer.Extension := Assoc^.Ext;
  XFer.Command := Assoc^.Cmd^;
  if Assoc^.Prompt then XFer.Prompt := 1
  else XFer.Prompt := 0;
  D := CreateEditDialog;
  Result := Application^.ExecuteDialog(D, @XFer);
  if Result = cmOK then
  begin
    UpperCase(XFer.Extension);
    Assoc^.Ext := XFer.Extension;
    DisposeStr(Assoc^.Cmd);
    Assoc^.Cmd := NewStr(XFer.Command);
    Assoc^.Prompt := XFer.Prompt > 0;
  end;
  EditAssociation := Result;
end;

function DeleteAssociation(var ListBoxRec: TListBoxRec): Word;
var
  Assoc: PAssociation;
  Result: Integer;
  P: PString;
begin
  Result := cmCancel;
  if ListBoxRec.List^.Count = 0 then Exit;
  Assoc := ListBoxRec.List^.At(ListBoxRec.Selection);
  P := @Assoc^.Ext;
  Result := MessageBox('Delete association for %s?', @P,
    mfConfirmation + mfOKButton + mfCancelButton);
  if Result = cmOK then
    ListBoxRec.List^.AtFree(ListBoxRec.Selection);
  DeleteAssociation := Result;
end;

{ TAssocDialog }
constructor TAssocDialog.Init(ADefExt: ExtStr);
var
  R: TRect;
  SB: PScrollBar;
begin
  R.Assign(0,0,65,15);
  inherited Init(R, 'File Associations');
  DefExt := ADefExt;
  Options := Options or ofCentered;

  R.Assign(62,3,63,11);
  SB := New(PScrollBar, Init(R));
  Insert(SB);
  R.Assign(2,3,62,11);
  ListBox := New(PAssocBox, Init(R, 1, SB));
  Insert(ListBox);
  R.Assign(2,2,32,3);
  Insert(New(PStaticText, Init(R, 'Extension      Command line')));

  R.Assign(2,12,12,14);
  Insert(New(PButton, Init(R, '~A~dd', cmAddAssoc, bfNormal)));
  R.Move(11, 0);
  Insert(New(PButton, Init(R, '~E~dit', cmEditAssoc, bfNormal)));
  R.Move(11, 0);
  Insert(New(PButton, Init(R, '~D~elete', cmDelAssoc, bfNormal)));

  R.Move(16, 0);
  Insert(New(PButton, Init(R, '~O~K', cmOK, bfDefault)));
  R.Move(11, 0);
  Insert(New(PButton, Init(R, '~C~ancel', cmCancel, bfNormal)));
  SelectNext(False);
end;

procedure TAssocDialog.HandleEvent(var Event: TEvent);
var
  ListBoxRec: TListBoxRec;
begin
  if ListBox^.List^.Count = 0 then
    DisableCommands([cmEditAssoc,cmDelAssoc])
  else
    EnableCommands([cmEditAssoc,cmDelAssoc]);

  inherited HandleEvent(Event);
  if Event.What = evCommand then
  begin
    ListBoxRec.List := ListBox^.List;
    ListBoxRec.Selection := ListBox^.Focused;
    case Event.Command of
      cmAddAssoc :
        if AddAssociation(ListBoxRec, DefExt) <> cmOK then Exit;
      cmEditAssoc :
        if EditAssociation(ListBoxRec) <> cmOK then Exit;
      cmDelAssoc :
        if DeleteAssociation(ListBoxRec) <> cmOK then Exit;
    end;
    ListBox^.SetRange(ListBox^.List^.Count);
    ListBox^.DrawView;
    ClearEvent(Event);
  end;
end;

{ TExtValidator }
function TExtValidator.IsValid(const S: string): Boolean;
begin
  IsValid := False;
  IsValid := (Length(S) > 0) and (S[1] = '.');
end;

procedure TExtValidator.Error;
begin
  MessageBox('Enter an valid file extension in the form ".xxx"', nil,
    mfInformation + mfOKButton);
end;

{ TNonBlankValidator }
procedure TNonBlankValidator.Error;
begin
  MessageBox('Field can not be blank.', nil,
    mfInformation + mfOKButton);
end;


procedure InitAssociations;
begin
  AssociateList := New(PAssociateList, Init(10, 5));
end;

procedure DoneAssociations;
begin
  if AssociateList <> nil then Dispose(AssociateList, Done);
end;

procedure Associate(DefExt: ExtStr);
var
  D: PDialog;
  XFer: TListBoxRec;
  Result: Word;
begin
  if AssociateList = nil then Exit;

  XFer.List := New(PAssociateList, Init(20,5));
  AssociateList^.FillCloneList(XFer.List);
  XFer.Selection := 0;

  D := New(PAssocDialog, Init(DefExt));
  if Application^.ExecuteDialog(D, @XFer) = cmOK then
    AssociateList^.UseCloneList(XFer.List);
  Dispose(XFer.List, Done);
end;

function GetAssociatedCommand(Ext: ExtStr): PAssociation;
var
  Association: PAssociation;

  function MatchExtension(P: PAssociation): Boolean; far;
  begin
    MatchExtension := (P^.Ext = Ext) or ((P^.Ext = '.') and (Ext = ''));
  end;

begin
  GetAssociatedCommand := nil;
  if AssociateList = nil then Exit;
  Association := AssociateList^.FirstThat(@MatchExtension);
  GetAssociatedCommand := Association;
end;

procedure WriteAssociationList(var S: TStream);
begin
  if AssociateList = nil then Exit;
  AssociateList^.Store(S);
end;

procedure ReadAssociationList(var S: TStream);
begin
  if AssociateList <> nil then
    Dispose(AssociateList, Done);
  AssociateList := New(PAssociateList, Load(S));
end;

procedure RegisterAssociations;
begin
  RegisterType(RAssociation);
  RegisterType(RAssociateList);
end;

end.