Contributor: ROBERT MARSH

unit MRUFList;

(*    Implements a Recently-Used Files List     *)

(*  Constructed by Robert R. Marsh, S.J., 1995  *)
(* Use freely, distribute widely, charge nothing*)
(* If you like it you could always give some    *)
(* money to your favorite charity.              *)

(*  Comments, bug-reports, praise and blame to: *)
(*              RobMarsh@AOL.COM                *)

interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, Menus, IniFiles;

type
  TRecentFileEvent = procedure(Sender: TObject; LatestFile: string) of object;

type
  TRecentFiles = class(TComponent)
  private
    FMenu       : TMenuItem;
    Divider     : TMenuItem;
    FMaxFiles   : integer;
    FIniFileName: string;
    FLatestFile : string;
    FOnClick    : TRecentFileEvent;
    procedure SetLatestFile(value: string);
    procedure SetMenu(value: TMenuItem);
    procedure SetMaxFiles(value: integer);
    procedure MenuOnClick(Sender: TObject);
    function DividerPlace: integer;
  protected
    procedure Click(RecentFile: string);
  public
    constructor Create(AOwner: TComponent); override;
    procedure SaveToIniFile;
    procedure LoadFromIniFile;
    property LatestFile: string read FLatestFile write SetLatestFile;
  published
    property Menu       : TMenuItem read FMenu write SetMenu;
    property MaxFiles   : integer read FMaxFiles write SetMaxFiles;
    property IniFileName: string read FIniFileName write FIniFileName;
    property OnClick    : TRecentFileEvent read FOnClick write FOnClick;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Mine', [TRecentFiles]);
end;

{we frequently need to know both if the Divider}
{has been added and, if so, where it is}
function TRecentFiles.DividerPlace: integer;
begin
Result:=-1;
if FMenu <> nil then
  begin
  Result:=Menu.IndexOf(Divider);
  end;
end;

procedure TRecentFiles.SetMenu(value: TMenuItem);
begin
FMenu:=value;
end;

procedure TRecentFiles.SetMaxFiles(value: integer);
var
  n: integer;
begin
{the Max value of MaxFiles is 9}
if value <=9 then
  begin
  FMaxFiles:=value;
  end
else
  begin
  FMaxFiles:=9;
  end;
if (FMenu <> nil) and (DividerPlace <> -1) then
  begin
  {trim off any entries more MaxFiles}
  while Menu.Count > DividerPlace + FMaxFiles + 1 do
    begin
    Menu.Delete(Menu.Count-1);
    end;
  {if neccesary delete the divider too}
  if FMaxFiles = 0 then
    begin
    Menu.Delete(Menu.Count-1);
    end;
  end;
end;

procedure TRecentFiles.SetLatestFile(value: string);
var
  NewMenuItem: TMenuItem;
  n          : integer;
  Thiscaption: string;
  OldPlace   : integer;
  DividerPos : integer;
begin
FLatestFile:=value;
if (Menu <> nil) and (MaxFiles > 0) and
   (FLatestFile <> '') then
  begin
  {special case - the divider}
  if DividerPlace < 0 then
    begin
    Menu.Add(Divider);
    end;
  {is the new Name already there?}
  DividerPos:=DividerPlace;
  n:=DividerPos+1;
  while (n < Menu.Count) do
    begin
    if FLatestFile = Copy(Menu.Items[n].Caption,4,high(string)) then
      begin
      OldPlace:=n;
      Break;
      end
    else
      begin
      inc(n);
      end;
  end;
  if n >= Menu.Count then {we add}
    begin
    NewMenuItem:=TMenuItem.Create(Self);
    NewMenuItem.Caption:='&1 '+FLatestFile;
    {what happens if we click it}
    NewMenuItem.OnClick:=MenuOnClick;
    Menu.Insert(DividerPos+1,NewMenuItem);
    end
  else                 {we insert}
    begin
    NewMenuItem:=Menu.Items[OldPlace];
    Menu.Delete(OldPlace);
    Menu.Insert(DividerPos+1,NewMenuItem);
    end;
  {now change the 'hot' keys}
  for n:=DividerPos+1 to Menu.Count - 1 do
    begin
    ThisCaption:=Menu.Items[n].Caption;
    ThisCaption[2]:=Chr(n - DividerPos + Ord('1') -1);
    Menu.Items[n].Caption:=ThisCaption;
    end;
  {delete any excess items}
  if Menu.Count > DividerPos + MaxFiles + 1 then
    begin
    Menu.Delete(Menu.Count-1);
    end;
  end;
end;

procedure TRecentFiles.Click(RecentFile: string);
begin
if Assigned(FOnClick) then FOnClick(Self,RecentFile);
end;

procedure TRecentFiles.SaveToIniFile;
var
  IniFile   : TIniFile;
  n         : integer;
  DividerPos: integer;
begin
if Menu <> nil then
  begin
  {if this property is blank we use the default}
  if IniFileName = '' then
    begin
    IniFileName:=ChangeFileExt(ExtractFileName(Application.ExeName),'.INI')
    end;
  IniFile:=TIniFile.Create(IniFileName);
  IniFile.EraseSection('FileHistory');
  IniFile.WriteInteger('FileHistory','MaxFiles',MaxFiles);
  if (Menu <> nil) and (DividerPlace <> -1) then
    begin
    DividerPos:=DividerPlace;
    n:=DividerPos+1;
    while n < Menu.Count do
      begin
      IniFile.WriteString('FileHistory','File'+Chr(n+Ord('1')-1-DividerPos),Copy(Menu.Items[n].Caption,4,high(string)));
      inc(n);
      end;
    IniFile.Free;
    end;
  end;
end;

procedure TRecentFiles.LoadFromIniFile;
var
  IniFile: TIniFile;
  n      : integer;
  Name   : string;
begin
if Menu <> nil then
  begin
  if IniFileName = '' then
    begin
    IniFileName:=ChangeFileExt(ExtractFileName(Application.ExeName),'.INI')
    end;
  IniFile:=TIniFile.Create(IniFileName);
  MaxFiles:=0;
  MaxFiles:=IniFile.ReadInteger('FileHistory','MaxFiles',MaxFiles);
  n:=1;
  while true do
    begin
    Name:=IniFile.ReadString('FileHistory','File'+Chr(n+Ord('1')-1),'');
    if Name = '' then
      begin
      Break;
      end;
    LatestFile:=Name;
    inc(n);
    end;
  end;
end;

constructor TRecentFiles.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FMaxFiles:=0;
FMenu:=nil;
Divider:=TMenuItem.Create(Self);
Divider.Caption:='-';
end;

procedure TRecentFiles.MenuOnClick(Sender: TObject);
var
  Name: string;
begin
  begin
  Name:=Copy(TMenuItem(Sender).Caption,4,high(string));
  Click(Name);
  end;
end;

end.