{$I-,X+,F+,O+}
unit FindFile;

interface

uses Dos, Objects, Drivers, Views, Dialogs, DirPrim;

type
  TFindData = record
    FindMatch: FNameStr;
    TextMatch: String[255];
    FileOptions: Word;
    TxtOptions: Word;
  end;

  PFindFileRec = ^TFindFileRec;
  TFindFileRec = record
    Attr: Byte;
    Time: Longint;
    Size: Longint;
    Name: String[8];
    Ext: String[3];
    Selected: Boolean;
    Info: Byte;
    ThePath: PString;
  end;

  PFindFileCollection = ^TFindFileCollection;
  TFindFileCollection = object(TCollection)
    procedure FreeItem(Item: Pointer); virtual;
    function GetItem(var S: TStream): Pointer; virtual;
    procedure PutItem(var S: TStream; Item: Pointer); virtual;
    function NewItem(SR: SearchRec; const ADir, ALongName: FNameStr;
      Info: Byte): Boolean;
    function CanInsert: Boolean;
    procedure GetItemName(Index: Integer; var S: FNameStr);
  end;

  PMatchCollection = ^TMatchCollection;
  TMatchCollection = object(TCollection)
    Dir: FNameStr;
    procedure FreeItem(Item: Pointer); virtual;
  end;

  PFindFileList = ^TFindFileList;
  TFindFileList = object(TListViewer)
    List: PFindFileCollection;
    Searching: Boolean;
    Selected: Integer;
    SelCount: Integer;
    SelSize: Longint;
    procedure Draw; virtual;
    function GetText(Item: Integer; MaxLen: Integer): String; virtual;
    function IsSelected(Item: Integer): Boolean; virtual;
    procedure HandleEvent(var Event: TEvent); virtual;
    procedure NewItem(SR: SearchRec;
      const Dir, LongName: FNameStr; Info: Byte;
      var CannotFindFlag, OutOfMemFlag: Boolean);
    procedure Search;
    procedure SetSelect(Item: Pointer; On: Boolean);
    procedure SelectFocused;
    procedure Invert;
    procedure Unselect;
    procedure CalcSelSize;
    procedure SelectGroup(On: Boolean);
    procedure Clear;
    procedure DeleteSelected;
    procedure GetSearchDrives;
    procedure GetSearchOptions;
    procedure Update;
    procedure UpdateInfo;
    procedure CountSelTotals(Optimized: Boolean);
    procedure FCopy;
    procedure FRenMov(One: Boolean);
    procedure FDelete(One: Boolean);
    procedure FOpen(ACommand: Word);
    procedure ClipCopy;
    procedure ClipPaste(Unsel: Boolean);
    procedure CopyToArchive(RenMov: Boolean; const Dest: FNameStr);
    procedure WriteToList(const Name: FNameStr; ForPanel: Boolean);
  end;

  PFindFileInfoPane = ^TFindFileInfoPane;
  TFindFileInfoPane = object(TView)
    Name: FNameStr;
    procedure Draw; virtual;
    function GetPalette: PPalette; virtual;
    procedure HandleEvent(var Event: TEvent); virtual;
  end;

  PFindFileDialog = ^TFindFileDialog;
  TFindFileDialog = object(TDialog)
    FileInput, TextInput: PInputLine;
    FindFileList: PFindFileList;
    UNIXCheckBox, TextCheckBox: PCheckBoxes;
    constructor Init(AList: Pointer);
    procedure HandleEvent(var Event: TEvent); virtual;
  end;

const
  saAnyFile = $0040;

  opFromCurrent = 1;
  opScanSubDirs = 2;
  opScanSelDirs = 4;
  opInArchives  = 8;
  opTaggedOnly  = $10;
  opSelectFound = $20;

  fsSaveOptions = $01;
  fsClearText = $02;
  fsTextSearch = $80;

const
  SearchDrives: TDrivesArray =
    (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0);

  SearchAttr: Word = saAnyFile;

  CaseFlag: Boolean = False;
  RegExpFlag: Boolean = False;

  FindOptions: Word = opScanSubDirs;

  FindSaveOptions: Word = 0;

  ArcNameFilter: FNameStr = '';
  ArcNamePipePos: Byte = 0;

function SizeStrToSize(const SizeStr: String; var Code: Integer): Longint;

procedure DrawSelGroupStatus(const Mask: FNameStr);

implementation

uses Memory, MsgBox, HistList, App, Gauges, StrCol, FMPrim, ArcV,
  ArcRec, Archives, ExecPrim, Spooler, DiskBase, DiskPrim, FEditor,
  HEditor, TxtView, HexView, DataView, HTMLView, EditFile, EditMsg,
  FindDlg, Swaps, ClipPrim, RevPos, PanelRec, RegExp, UnixMask,
  FMAbort, Tags, History, HeapUMB, Keyboard, LFN, DosTools, Timer,
  TPString, Consts, Commands, Defaults;

type
  TMatchFunc = (mfLess, mfEqual, mfGreater, mfAround);

  PMatchRec = ^TMatchRec;
  TMatchRec = record
    Mask: PString;
    States: Pointer;
    MatchSize: Longint;
    MatchDate: Longint;
    MatchTime: Longint;
    SizeFunc: TMatchFunc;
    DateFunc: TMatchFunc;
    TimeFunc: TMatchFunc;
  end;

  PArcMatchInfo = ^TArcMatchInfo;
  TArcMatchInfo = record
    List: PFindFileList;
    Match: PMatchCollection;
    ArcName: PString;
    CannotFindPtr: ^Boolean;
    OutOfMemPtr: ^Boolean;
  end;

const
  MaxFoundFiles = $2000;

var
  InsIndex: Integer;

function SizeStrToSize(const SizeStr: String; var Code: Integer): Longint;
var
  C: Char;
  L, M: Longint;
  R: Real;
  S: String;
begin
  SizeStrToSize := -1;
  S := DelChar(' ', SizeStr);
  UpcaseSt(S);
  C := S[Length(S)];
  if C in ['K', 'M'] then
  begin
    Dec(S[0]);
    if S = '' then S := '1';
    M := 1024;
    if C = 'M' then M := M * 1024;
  end
  else M := 1;
  Val(S, L, Code);
  if (Code <> 0) and (S[1] <> '$') then
  begin
    Val('$' + S, L, Code);
    if Code > 1 then Dec(Code);
  end;
  if Code <> 0 then Exit;
  R := L;
  R := R * M;
  if (R >= 0) and (R - 1 < MaxLongint) then
    SizeStrToSize := Trunc(R);
end;

procedure DrawSelGroupStatus(const Mask: FNameStr);
begin
  LogEvent := 0;
  DrawStatusLine(
    'Searching for file names to match the wildcard ' + Mask + '.');
end;

function SearchRecMatches(SR: SearchRec; const ALongName: FNameStr;
  AMatch: PMatchCollection): Boolean;
var
  I: Integer;
  P: PMatchRec;

function NameMatches: Boolean;
var
  B: Byte;
  AExt: String[3];
  AName: String[12];
begin
  if ALongName = '' then
  begin
    B := Pos('.', SR.Name);
    if B = 0 then
    begin
      AName := SR.Name;
      TrimSt(AName);
      AExt := '';
    end
    else
    begin
      AName := System.Copy(SR.Name, 1, B - 1);
      TrimSt(AName);
      AExt := System.Copy(SR.Name, B + 1, 3);
      TrimSt(AExt);
    end;
    AName := Concat(AName, '.', AExt);
  end;
  if UnixFlag then
    if ALongName <> '' then NameMatches := IsAcceptable(
      ALongName, PState(P^.States)^)
    else NameMatches := IsAcceptable(AName, PState(P^.States)^)
  else if ALongName <> '' then NameMatches :=
    WildCardMatch(ALongName, P^.Mask^)
  else NameMatches := WildCardMatch(AName, P^.Mask^);
end;

function AttrMatches: Boolean;
begin
  AttrMatches :=
    (SR.Attr and VolumeID = 0) and
    ((SearchAttr and saAnyFile <> 0) or
     (((SearchAttr and Directory <> 0) or
       (SR.Attr and Directory = 0)) and
      (SearchAttr and SR.Attr <> 0)));
end;

function SizeMatches: Boolean;
begin
  case P^.SizeFunc of
    mfLess: SizeMatches := SR.Size < P^.MatchSize;
    mfEqual: SizeMatches := SR.Size = P^.MatchSize;
    mfGreater: SizeMatches := SR.Size > P^.MatchSize;
    else SizeMatches := Abs(SR.Size - P^.MatchSize) <= P^.MatchSize div 10;
  end;
end;

function DatesAlmostEqual(M1, M2, D1, D2: Word): Boolean;
begin
  if D2 = 0 then
    if M2 > M1 then DatesAlmostEqual := M2 - M1 <= 1
    else DatesAlmostEqual := M1 - M2 <= 1
  else if M1 <> M2 then DatesAlmostEqual := False
  else if D2 > D1 then DatesAlmostEqual := D2 - D1 <= 1
  else DatesAlmostEqual := D1 - D2 <= 1;
end;

function DateMatches: Boolean;
var
  DT, ADT: DateTime;
begin
  UnpackTime(P^.MatchDate, DT);
  UnpackTime(SR.Time, ADT);
  case P^.DateFunc of
    mfLess: DateMatches := (ADT.Year < DT.Year) or
      ((ADT.Year = DT.Year) and (ADT.Month < DT.Month)) or
      ((ADT.Year = DT.Year) and (ADT.Month = DT.Month) and
       (ADT.Day < DT.Day));
    mfEqual: DateMatches := (ADT.Year = DT.Year) and
      (ADT.Month = DT.Month) and ((ADT.Day = DT.Day) or (DT.Day = 0));
    mfGreater: DateMatches := (ADT.Year > DT.Year) or
      ((ADT.Year = DT.Year) and (ADT.Month > DT.Month)) or
      ((ADT.Year = DT.Year) and (ADT.Month = DT.Month) and
       (ADT.Day > DT.Day));
    else DateMatches := (ADT.Year = DT.Year) and
      DatesAlmostEqual(ADT.Month, DT.Month, ADT.Day, DT.Day);
  end;
end;

function TimesAlmostEqual(H1, H2, M1, M2: Word): Boolean;
begin
  if M2 = 0 then TimesAlmostEqual := (H1 = H2) or (H2 - H1 = 1)
  else if H1 <> H2 then TimesAlmostEqual := False
  else if M2 mod 10 <> 0 then
    if M2 > M1 then TimesAlmostEqual := M2 - M1 <= 1
    else TimesAlmostEqual := M1 - M2 <= 1
  else if M2 > M1 then TimesAlmostEqual := M2 - M1 <= 10
  else TimesAlmostEqual := M1 - M2 <= 10;
end;

function TimeMatches: Boolean;
var
  DT, ADT: DateTime;
begin
  UnpackTime(P^.MatchTime, DT);
  UnpackTime(SR.Time, ADT);
  case P^.TimeFunc of
    mfLess: TimeMatches := (ADT.Hour < DT.Hour) or
      ((ADT.Hour = DT.Hour) and (ADT.Min < DT.Min));
    mfEqual: TimeMatches := (ADT.Hour = DT.Hour) and (ADT.Min = DT.Min);
    mfGreater: TimeMatches := (ADT.Hour > DT.Hour) or
      ((ADT.Hour = DT.Hour) and (ADT.Min > DT.Min));
    else TimeMatches := TimesAlmostEqual(ADT.Hour, DT.Hour, ADT.Min, DT.Min);
  end;
end;

begin
  for I := 0 to AMatch^.Count - 1 do
  begin
    P := AMatch^.At(I);
    if NameMatches and AttrMatches and
      ((P^.MatchSize = -1) or SizeMatches) and
      ((P^.MatchDate = -1) or DateMatches) and
      ((P^.MatchTime = -1) or TimeMatches) then
    begin
      SearchRecMatches := True;
      Exit;
    end;
  end;
  SearchRecMatches := False;
end;

function TagMatches(const Path, Name: FNameStr): Boolean;
var
  S: FNameStr;
begin
  if TagList^.Count = 0 then TagMatches := False
  else
  begin
    S := Concat(Path, Name);
    TagMatches := TagList^.NameFound(S);
  end;
end;

procedure TestArchiveItem(ALine: FNameStr; Time, OrgSize, Size: Longint);
var
  Long: Boolean;
  Attr: Byte;
  SR: SearchRec;
  Path, Name: FNameStr;
begin
  with PArcMatchInfo(ArchiveInfoPtr)^ do
  begin
    if (ALine = '') or (TPString.Upcase(ALine[1]) in ['D', 'U']) or
       (ALine[Length(ALine)] = '\') or OutOfMemPtr^ then Exit;
    if ALine[1] = '*' then Attr := Hidden
    else Attr := Archive;
    ALine[1] := '\';
    if (Length(ALine) >= 3) and (ALine[2] = '.') and (ALine[3] = '\') then
      Delete(ALine, 2, 2);
    ALine := ArcName^ + ALine;
    Path := AddedSlash(JustPathName(ALine));
    SetSearchRecName(JustFileName(ALine), SR, Long);
    if Long then Name := JustFileName(ALine) else Name := '';
    SR.Size := OrgSize;
    SR.Time := Time;
    SR.Attr := SearchAttr and not (VolumeID + Directory);
    if SearchRecMatches(SR, '', Match) and
      ((FindOptions and opTaggedOnly = 0) or
       TagMatches(Path, SR.Name)) then
    begin
      SR.Attr := Attr;
      List^.NewItem(SR, Path, Name, Length(ArcName^),
	CannotFindPtr^, OutOfMemPtr^);
    end;
  end;
end;

procedure SetSearchData(const Data: TFindData);
begin
  SearchData.MatchStr := Data.TextMatch;
  if Data.TxtOptions and 1 = 0 then SearchData.Options := 0
  else SearchData.Options := frCaseSensitive;
  if Data.TxtOptions and 2 <> 0 then
    SearchData.Options := SearchData.Options or frRegExp;
end;

procedure UpdateCommands(AList: PCollection);
begin
  with Application^ do
  begin
    EnableCommands(ModifyCommands);
    if AList^.Count = 0 then
      DisableCommands(NewCommands + RunItemCommands + DeleteCommands)
    else EnableCommands(NewCommands + RunItemCommands + DeleteCommands);
  end;
end;

const
  SafetyPool = 8192;

function TFindFileCollection.CanInsert: Boolean;
begin
  if Count < Limit then CanInsert := MaxAvail > SafetyPool
  else CanInsert := MaxAvail > LongMul(Limit + Delta, 4) + SafetyPool;
end;

procedure TFindFileCollection.FreeItem(Item: Pointer);
begin
  if Item <> nil then
  begin
{
    DisposeStr(PFindFileRec(Item)^.ThePath);
    Dispose(PFindFileRec(Item));
}
    DisposeStrUMB(PFindFileRec(Item)^.ThePath);
    FreeMemUMB(Item, SizeOf(TFindFileRec));
  end;
end;

function TFindFileCollection.GetItem(var S: TStream): Pointer;
var
  Item: PFindFileRec;
begin
{  GetMem(Item, SizeOf(TFindFileRec));}
  GetMemUMB(Item, SizeOf(TFindFileRec));
  S.Read(Item^, SizeOf(TFindFileRec) - SizeOf(PString));
{  Item^.ThePath := S.ReadStr;}
  Item^.ThePath := ReadStrUMB(@S);
  if not CanInsert then
  begin
    FreeItem(Item);
    Item := nil;
  end
  else if Item^.Attr and Directory <> 0 then
  begin    { For compatibility with previous version }
    Item^.Name := '';
    Item^.Ext := '';
  end;
  GetItem := Item;
end;

procedure TFindFileCollection.PutItem(var S: TStream; Item: Pointer);
begin
  S.Write(PFindFileRec(Item)^, SizeOf(TFindFileRec) - SizeOf(PString));
  S.WriteStr(PFindFileRec(Item)^.ThePath);
end;

function TFindFileCollection.NewItem(SR: SearchRec;
  const ADir, ALongName: FNameStr; Info: Byte): Boolean;
var
  Found, IsDir: Boolean;
  B: Byte;
  Item, P: PFindFileRec;

procedure SmartInsert;
var
  I: Integer;
label
  Free, Ins;
begin
  Found := False;
  I := Count - 1;
  while I >= 0 do
  begin
    P := At(I);
    if (P^.Attr and Directory <> 0) and
       ((IsDir and (CompUCString(P^.ThePath^, Item^.ThePath^) = Equal)) or
	(not IsDir and (CompUCString(P^.ThePath^, ADir) = Equal))) then
    begin
      Found := True;
      if IsDir then Goto Free;
      repeat
	Inc(I);
	P := At(I);
	if (P = nil) or (P^.Attr and Directory <> 0) then Goto Ins;
	if (P^.Name = Item^.Name) and (P^.Ext = Item^.Ext) then
	begin
	  Item^.Selected := P^.Selected;
	  Goto Free;
	end;
      until I >= Count;
      Break;
    end;
    Dec(I);
  end;
  if not Found and not IsDir then
  begin
{    GetMem(P, SizeOf(TFindFileRec));}
    GetMemUMB(P, SizeOf(TFindFileRec));
    P^.Name := '';
    P^.Ext := '';
    P^.Attr := Directory;
    P^.Selected := False;
    P^.Info := Info;
{    P^.ThePath := NewStr(ADir);}
    P^.ThePath := NewStrUMB(ADir);
    Insert(P);
  end;
  I := Count;
Free:  if I < Count then AtFree(I);
Ins:
  if not IsDir and
    ((FindOptions and opSelectFound <> 0) or
     ((FMOptions and foSaveSelection <> 0) and
       TagMatches(ADir, SR.Name))) then Item^.Selected := True;
  AtInsert(I, Item);
  InsIndex := I;
end;

function AbortIfTooMany: Boolean;
begin
  if Count = 0 then AbortIfTooMany := True
  else if MessageBox(^C'Too many files match the specified mask. ' +
    'Clear some items off the top of the list and continue ?',
    nil, mfConfirmation + mfOkCancel) = cmCancel then
  begin
    FreeItem(Item);
    AbortIfTooMany := True;
  end
  else
  begin
    AtFree(0);
    while (Count > 0) and
      (PFindFileRec(At(0))^.Attr and Directory = 0) do AtFree(0);
    AbortIfTooMany := False;
  end;
end;

label
  ANew;
begin
  NewItem := False;
ANew:
  GetMemUMB(Item, SizeOf(TFindFileRec));
  Item^.ThePath := nil;
  if not CanInsert then
  begin
    if AbortIfTooMany then Exit;
    FreeItem(Item);
    Goto ANew;
  end;
  IsDir := SR.Attr and Directory <> 0;
  if IsDir then
  begin
    Item^.Name := '';
    Item^.Ext := '';
  end
  else
  begin
    B := Pos('.', SR.Name);
    if B <> 0 then
    begin
      Item^.Name := System.Copy(SR.Name, 1, B - 1);
      Item^.Ext  := System.Copy(SR.Name, B + 1, 3);
    end
    else
    begin
      Item^.Name := SR.Name;
      Item^.Ext  := '';
    end;
  end;
  Item^.Attr := SR.Attr;
  Item^.Time := SR.Time;
  Item^.Size := SR.Size;
  Item^.Selected := False;
  Item^.Info := Info;
  if ALongName <> '' then
    if IsDir then
{      Item^.ThePath := NewStr(Concat(ADir, ALongName))}
      Item^.ThePath := NewStrUMB(Concat(ADir, ALongName, '\'))
    else
{    Item^.ThePath := NewStr(Concat(#0, ALongName))}
      Item^.ThePath := NewStrUMB(Concat(#0, ALongName))
  else if IsDir then
{    Item^.ThePath := NewStr(Concat(ADir, SR.Name, '\'))}
    Item^.ThePath := NewStrUMB(Concat(ADir, SR.Name, '\'));
  if (Count > MaxFoundFiles) and AbortIfTooMany then Exit;
  SmartInsert;
  NewItem := True;
end;

procedure TFindFileCollection.GetItemName(Index: Integer; var S: FNameStr);
var
  P: PFindFileRec;
begin
  P := At(Index);
  if P = nil then S := ''
  else if P^.Attr and Directory <> 0 then S := P^.ThePath^
  else
  begin
    if (P^.ThePath <> nil) and (P^.ThePath^[1] = #0) then
      S := Copy(P^.ThePath^, 2, 255)
    else
    begin
      S := P^.Name;
      if P^.Ext <> '' then S := Concat(S, '.', P^.Ext);
    end;
    while Index >= 0 do
    begin
      Dec(Index);
      P := At(Index);
      if P^.Attr and Directory <> 0 then
      begin
	S := Concat(P^.ThePath^, S);
	Exit;
      end;
    end;
  end;
end;

procedure TMatchCollection.FreeItem(Item: Pointer);
begin
  if Item <> nil then
  begin
{
    DisposeStr(PMatchRec(Item)^.Mask);
    if PMatchRec(Item)^.States <> nil then
      FreeMem(PMatchRec(Item)^.States, SizeOf(TState));
    FreeMem(Item, SizeOf(TMatchRec));
}
    DisposeStrUMB(PMatchRec(Item)^.Mask);
    if PMatchRec(Item)^.States <> nil then
      FreeMemUMB(PMatchRec(Item)^.States, SizeOf(TState));
    FreeMemUMB(Item, SizeOf(TMatchRec));
  end;
end;

procedure TFindFileList.Draw;
begin
  TListViewer.Draw;
  UpdateInfo;
end;

function TFindFileList.GetText(Item: Integer; MaxLen: Integer): String;
var
  Time: DateTime;
  Params: Array[0..4] of LongInt;
  DS, TS: String[15];
  SName: String[12];
  FullName: String[213];
  FR: TFindFileRec;
  S: String;
begin
  FR := PFindFileRec(List^.At(Item))^;
  if FR.Attr and Directory <> 0 then
  begin
    S := FR.ThePath^;
    if LFNOptions and loConvertNames <> 0 then UpcaseSt(S);
    if FR.Info <> 0 then S := Concat(S, ' (ARCHIVE)');
  end
  else
  begin
    SName := FR.Name;
    if FR.Ext <> '' then SName := Concat(SName, '.', FR.Ext);
    LocaseSt(SName);
    Params[0] := Longint(@SName);
    Params[1] := FR.Size;
    UnpackTime(FR.Time, Time);
    with Time do
    begin
      DS := FormatDateStr(Year, Month, Day);
      TS := FormatTimeStr(Hour, Min, Sec);
      System.Delete(TS, 6, 3);
    end;
    Params[2] := Longint(@DS);
    Params[3] := Longint(@TS);
    if (FR.ThePath <> nil) and (FR.ThePath^[1] = #0) then
    begin
      FullName := Copy(FR.ThePath^, 2, 255);
      if LFNOptions and loConvertNames <> 0 then LocaseSt(FullName);
    end
    else FullName := '';
    Params[4] := Longint(@FullName);
    FormatStr(S, '  %-12s  %9d %s %s %s', Params);
    if Item = InsIndex then S[1] := ''
    else if FR.Selected then
      if TagChar in [#0, #32] then S[1] := 'û'
      else S[1] := TagChar;
    if FR.Info <> 0 then
      if FR.Attr and Hidden <> 0 then S[16] := 'Å'
      else S[16] := '³'
    else if FR.Attr and (Hidden + SysFile) <> 0 then S[16] := '°';
  end;
  GetText := S;
end;

function TFindFileList.IsSelected(Item: Integer): Boolean;
begin
  IsSelected := PFindFileRec(List^.At(Item))^.Selected;
end;

procedure TFindFileList.HandleEvent(var Event: TEvent);
var
  Item: PFindFileRec;
begin
  if (Event.What = evKeyDown) and (Event.KeyCode = kbSpace) and
     (FMOptions and foSpaceSelects <> 0) then Event.KeyCode := kbIns;
  if (((Event.What = evMouseDown) and Event.Double) or
     ((Event.What = evKeyDown) and (Event.CharCode = ' '))) then
  begin
    Pending.What := evCommand;
    Pending.Command := cmNewItem;
    Event.What := evNothing;
  end
  else if (Event.What = evMouseDown) and
    (Event.Buttons = mbRightButton) then
  begin
    TListViewer.HandleEvent(Event);
    SelectFocused;
  end
  else if Event.What = evKeyDown then
    if Event.KeyCode = kbCtrlIns then
    begin
      Event.What := evCommand;
      Event.Command := cmCopy;
    end
    else if Event.KeyCode = kbShiftIns then
    begin
      Event.What := evCommand;
      Event.Command := cmPaste;
    end
    else if Event.KeyCode = kbShiftDel then
    begin
      Event.What := evCommand;
      Event.Command := cmCut;
    end
    else if Event.KeyCode = kbIns then
    begin
      SelectFocused;
      Event.What := evNothing;
    end
    else if Event.KeyCode = kbGrayMul then
    begin
      Invert;
      Event.What := evNothing;
    end
    else if Event.KeyCode = kbGrayPlus then
    begin
      SelectGroup(True);
      Event.What := evNothing;
    end
    else if Event.KeyCode = kbGrayMinus then
    begin
      SelectGroup(False);
      Event.What := evNothing;
    end
    else if Event.CharCode = ^D then
    begin
      Pending.What := evCommand;
      Pending.Command := cmSelectLeftDrive;
      Event.What := evNothing;
    end
    else if GetShiftState and (kbLeftShift + kbRightShift) <> 0 then
    case Event.KeyCode of
      kbUp:
        begin
	  Item := List^.At(Focused);
	  if Item^.Attr and Directory = 0 then SetSelect(Item, False);
	end;
      kbDown:
	begin
	  Item := List^.At(Focused);
	  if Item^.Attr and Directory = 0 then
	  begin
	    SetSelect(Item, True);
	    if Focused = List^.Count - 1 then DrawView;
	  end;
	end;
    end;
  if Event.What = evCommand then
  begin
    case Event.Command of
      cmFCopy: FCopy;
      cmFRenMov, cmFCtrlRenMov: FRenMov(Event.Command = cmFCtrlRenMov);
      cmFDelete, cmFCtrlDelete: FDelete(Event.Command = cmFCtrlDelete);
      cmIsASCII, cmIsHex, cmFView, cmFAltView: FOpen(Event.Command);
      cmCopy: ClipCopy;
      cmPaste: ClipPaste(False);
      cmCut: ClipPaste(True);
      else Exit;
    end;
    Event.What := evNothing;
  end;
  TListViewer.HandleEvent(Event);
end;

procedure TFindFileList.NewItem(SR: SearchRec;
  const Dir, LongName: FNameStr; Info: Byte;
  var CannotFindFlag, OutOfMemFlag: Boolean);
begin
  OutOfMemFlag := not List^.NewItem(SR, Dir, LongName, Info);
  if OutOfMemFlag then Exit;
  SetRange(List^.Count);
  if (State and sfSelected <> 0) or
     (InsIndex = Focused) then DrawView
  else FocusItem(InsIndex);
  InsIndex := -1;
  CannotFindFlag := False;
end;

procedure NoMatchMsgBox(const Mask: FNameStr);
begin
  MessageBox(^C'Could not find a match:'#13#13^C'"' +
    System.Copy(Mask, 1, 32) + '".', nil, mfError + mfOkButton);
end;

procedure TFindFileList.SelectGroup(On: Boolean);
type
  TSelData = record
    SelMask: FNameStr;
    UnixFlag: Word;
  end;
const
  ALabel = '~M~ask ';
var
  Found, RestoreUnixFlag, SaveUnixFlag: Boolean;
  PipePos: Byte;
  C: Word;
  D: PDialog;
  Control: PView;
  H: PHistory;
  FR: PFindFileRec;
  R: TRect;
  SelData: TSelData;
  S, Dir, Name, Ext: FNameStr;
  AHeader: String;
  States: TState;

procedure DoFindMatch(P: PFindFileRec); far;
var
  Name: FNameStr;
begin
  if P^.Attr and Directory = 0 then
  begin
    if (P^.ThePath <> nil) and (P^.ThePath^[1] = #0) then
      Name := System.Copy(P^.ThePath^, 2, 255)
    else Name := Concat(P^.Name, '.', P^.Ext);
    if (UnixFlag and IsAcceptable(Name, States)) or
       (not UnixFlag and NameMatchesMultiMask(
	 Name, SelData.SelMask, PipePos)) then
    begin
      Found := True;
      SetSelect(P, On);
    end;
  end;
end;

label
  DoIt;
begin
  if List^.Count = 0 then Exit;
  if GetShiftState and (kbAltShift + kbCtrlShift) <> 0 then
  begin
    FR := List^.At(Focused);
    if FR^.Attr and Directory = 0 then
    begin
      List^.GetItemName(Focused, S);
      lFSplit(S, Dir, Name, Ext);
      if Ext <> '' then System.Delete(Ext, 1, 1);
      if GetShiftState and kbAltShift <> 0 then
	SelData.SelMask := Name + '.*'
      else SelData.SelMask := '*.' + Ext;
      SaveUnixFlag := UnixFlag;
      UnixFlag := False;
      RestoreUnixFlag := True;
      Goto DoIt;
    end;
  end;
  if On then AHeader := 'Select group'
  else AHeader := 'Unselect group';
  R.Assign(0, 0, 28, 9);
  if On then C := hcSelectGroup
  else C := hcUnselectGroup;
  D := InitSimpleDialog(R, AHeader, C);
  with D^ do
  begin
    Options := Options or ofCentered;
    R.Assign(3 + CStrLen(ALabel), 2, 17 + CStrLen(ALabel), 3);
    Control := New(PInputLine, Init(R, 255));
    Insert(Control);
    H := StandardHistory(Control, MaskHistoryId);
    H^.HistoryPutFunc := UNIXPutHistoryFunc;
    H^.HistoryGetFunc := UNIXGetHistoryFunc;
    R.Assign(2, 2, 3 + CStrLen(ALabel), 3);
    Insert(New(PLabel, Init(R, ALabel, Control)));
    R.Assign(3, 4, 25, 5);
    Insert(New(PCheckBoxes, Init(R,
      NewSItem('~U~NIX style mask', nil))));
    Control^.Select;
  end;

  SelData.SelMask := HistoryStr(MaskHistoryID, 0);
  if SelData.SelMask = '' then
    if UnixFlag then SelData.SelMask := '*'
    else SelData.SelMask := '*.*';
  SelData.UnixFlag := Byte(UnixFlag);
  if (Application^.ExecuteDialog(D, @SelData, False) = cmCancel) or
     (SelData.SelMask = '') then Exit;
  UnixFlag := SelData.UnixFlag <> 0;
  RestoreUnixFlag := False;
DoIt:
  DrawSelGroupStatus(SelData.SelMask);
  if UnixFlag then MakeAutomaton(SelData.SelMask, States);
  PipePos := Pos('|', SelData.SelMask);
  Found := False;
  List^.ForEach(@DoFindMatch);
  if RestoreUnixFlag then UnixFlag := SaveUnixFlag;
  if Found then DrawView
  else NoMatchMsgBox(SelData.SelMask);
end;

procedure TFindFileList.Search;
var
  P: Byte;
  Ch: Char;
  CannotFind, NoDrives, OutOfMem, Invalid, ScanSelOnly: Boolean;
  SizeFunc, DateFunc, TimeFunc: TMatchFunc;
  MatchIndex, ClipIndex: Integer;
  BufSize, BufPos, BytesRead, BytesSearched: Word;
  FPos, MatchSize, MatchDate, MatchTime: Longint;
  MatchList: PCollection;
  MP: PMatchCollection;
  PMatch: PMatchRec;
  Buffer: PByteArray;
  RegExpr: Pointer;
  Event: TEvent;
  SaveSR: SearchRec;
  S, Match, FMatch, StartDir, FoundName: FNameStr;
  BT: BTable;
  Data: TFindData;

procedure DrawStatus(const AName: FNameStr);
begin
  LogEvent := lcDiskRead;
  DrawStatusLine('Scanning the directory ' +
    TrimmedName(ClearedSlash(AName), 45));
end;

procedure DrawFileStatus(const AName: FNameStr);
begin
  LogEvent := lcDiskRead;
  DrawStatusLine('Searching for text in ' + TrimmedName(AName, 44) + '.');
end;

procedure DrawArchiveStatus(const AName: FNameStr);
begin
  LogEvent := lcDiskRead;
  DrawStatusLine('Searching through archive ' +
    TrimmedName(ClearedSlash(AName), 40) + '.');
end;

procedure HandleEvents;
begin
  GiveTicks;
  if Clock <> nil then Clock^.Update;
  if TheSpooler <> nil then TheSpooler^.ProcessItems;
  if not RollerInUse then InitRoller;
  UpdateRoller;
  if Pending.What <> evNothing then
  begin
    Event := Pending;
    Pending.What := evNothing;
  end
  else
  begin
    GetMouseEvent(Event);
    if Event.What = evNothing then GetKeyEvent(Event);
  end;
  if (Event.What <> evNothing) and
     (Event.What <> evMouseMove) then Owner^.HandleEvent(Event);
end;

procedure AddNameAndSlash(const ADir, AName: FNameStr; DoAddSlash: Boolean;
  var S: FNameStr);
begin
  S := ADir + AName;
  if DoAddSlash then S := S + '\';
end;

procedure SetFoundName(const Path, Name: FNameStr);
begin
  FoundName := Path + Name;
end;

function Contains(const Path, Name: FNameStr): Boolean;
var
  LineCount: Byte;
  T: lText;
  F: lFile;
  S: String;
begin
  Contains := False;
  SetFoundName(Path, Name);
  DrawFileStatus(FoundName);
  if RegExpFlag then
  begin
    lAssignText(T, FoundName);
    Reset(T.T);
    if IOResult <> 0 then Exit;
    LineCount := $FF;
    while not Eof(T.T) do
    begin
      Inc(LineCount);
      if LineCount = 0 then
      begin
	HandleEvents;
	if not Searching then Break;
      end;
      Readln(T.T, S);
      S := Detab(S, 8);
      if not CaseFlag then UpcaseSt(S);
      if RegSearch(RegExpr, S) <> '' then
      begin
	Contains := True;
	Break;
      end;
    end;
    Close(T.T);
  end
  else
  begin
    lAssignFile(F, FoundName);
    lResetReadOnly(F, 1);
    if IOResult <> 0 then Exit;
    FPos := 0;
    repeat
      HandleEvents;
      if not Searching then Break;
      BufPos := 0;
      Seek(F.F, FPos);
      BlockRead(F.F, Buffer^, BufSize, BytesRead);
      InOutRes := 0;
      if BytesRead <> 0 then
      begin
	if CaseFlag then BytesSearched :=
	  BMSearch(Buffer^, BytesRead, BT, Data.TextMatch)
	else BytesSearched :=
	  BMSearchUC(Buffer^, BytesRead, BT, Data.TextMatch);
	if BytesSearched <> $FFFF then
	begin
	  Contains := True;
	  Break;
	end;
      end;
      Inc(FPos, BytesRead - Length(Data.TextMatch) + 1);
    until BytesRead <> BufSize;
    Close(F.F);
  end;
  InOutRes := 0;
end;

procedure ScanArchive(const Path, Name: FNameStr);
var
  Id, Status: Byte;
  P: PArcMatchInfo;
  R: SearchRec;
begin
  HandleEvents;
  if not Searching then Exit;
  SetFoundName(Path, Name);
  ArchiveLineProc := TestArchiveItem;
  ArchiveCommentList := nil;
  GetMem(P, SizeOf(TArcMatchInfo));
  P^.List := @Self;
  P^.Match := MP;
  P^.ArcName := @FoundName;
  P^.CannotFindPtr := @CannotFind;
  P^.OutOfMemPtr := @OutOfMem;
  ArchiveInfoPtr := P;
  DrawArchiveStatus(FoundName);
  ReadArchive(FoundName, Id, R, Status);
  FreeMem(P, SizeOf(TArcMatchInfo));
end;

procedure ScanDir(ADir: PString);
var
  I: Integer;
  SR: ^lSearchRec;
  Dir: PString;
begin
  if StackOverflow then
  begin
    Searching := False;
    Exit;
  end;
  Dir := NewStr(ADir^);
  GetMem(SR, SizeOf(lSearchRec));
  DrawStatus(Dir^);
  HandleEvents;
  AddNameAndSlash(Dir^, '*.*', False, S);
  if ScanSelOnly then FoundName := ClearedSlash(Dir^);
  if ScanSelOnly and not TagList^.NameFound(FoundName) then {}
  else
  begin
    lFindFirst(S, AnyFile and not VolumeID, SR^);
    while (DosError = 0) and Searching and not OutOfMem do
    begin
      if (SR^.SR.Attr and VolumeID = 0) and (SR^.SR.Name[1] <> '.') then
      begin
	if SR^.FullName <> '' then FoundName := SR^.FullName
	else FoundName := SR^.SR.Name;
	if SearchRecMatches(SR^.SR, SR^.FullName, MP) and
	  ((FindOptions and opTaggedOnly = 0) or
	   TagMatches(Dir^, FoundName)) and
	  ((Data.TextMatch = '') or
	   ((SR^.SR.Attr and Directory = 0) and
	    Contains(Dir^, FoundName))) then NewItem(
	      SR^.SR, Dir^, SR^.FullName, 0, CannotFind, OutOfMem);
	if not OutOfMem and (FindOptions and opInArchives <> 0) and
	  ((ArcNameFilter = '') or NameMatchesMultiMask(
	    FoundName, ArcNameFilter, ArcNamePipePos)) then
	      ScanArchive(Dir^, FoundName);
      end;
      lFindNext(SR^);
      if KbdStatus = 1 then
      begin
	ClearKbd;
	Searching := False;
      end;
    end;
  end;
  lFindClose(SR^);
  if FindOptions and opScanSubDirs <> 0 then
  begin
    lFindFirst(S, $17, SR^);
    while (DosError = 0) and Searching and not OutOfMem do
    begin
      if (SR^.SR.Attr and Directory <> 0) and (SR^.SR.Name[1] <> '.') then
      begin
	if SR^.FullName <> '' then
	  AddNameAndSlash(Dir^, SR^.FullName, True, S)
	else AddNameAndSlash(Dir^, SR^.SR.Name, True, S);
	ScanDir(@S);
      end;
      lFindNext(SR^);
      if KbdStatus = 1 then
      begin
	ClearKbd;
	Searching := False;
      end;
    end;
    lFindClose(SR^);
  end;
  FreeMem(SR, SizeOf(lSearchRec));
  DisposeStr(Dir);
end;

function DelQuotesOrSpaces(S: String): String;
var
  Q: Boolean;
  I: Integer;
begin
  Q := False;
  I := 1;
  while I <= Length(S) do
  begin
    if S[I] = '"' then
    begin
      Delete(S, I, 1);
      Q := not Q;
    end
    else if (S[I] = ' ') and not Q then Delete(S, I, 1)
    else Inc(I);
  end;
  DelQuotesOrSpaces := S;
end;

function JustFName(const Name: FNameStr): FNameStr;
var
  B, C: Byte;
begin
  C := 0;
  for B := 1 to Length(Name) do
  begin
    if Name[B] = '[' then Break;
    if Name[B] in [':', '\'] then C := B;
  end;
  JustFName := System.Copy(Name, C + 1, 255);
end;

function JustPName(const Name: FNameStr): FNameStr;
var
  B, C: Byte;
begin
  C := 0;
  for B := 1 to Length(Name) do
  begin
    if Name[B] = '[' then Break;
    if Name[B] in [':', '\'] then C := B;
  end;
  if C <> 0 then
    if (C > 2) and (Name[C - 1] <> ':') then Dec(C);
  JustPName := System.Copy(Name, 1, C);
end;

function IsUnixMask(const Mask: FNameStr): Boolean;
var
  B: Byte;
begin
  for B := 1 to Length(Mask) do
    if Mask[B] in ['*', '.', '?', '['] then
    begin
      IsUnixMask := True;
      Exit;
    end;
  IsUnixMask := False;
end;

function ParseMatch: Boolean;
var
  B: Byte;
  C: Char;
  Func: TMatchFunc;
  Code: Integer;
  L: Longint;
  DT: DateTime;
  Params, Param: FNameStr;
begin
  ParseMatch := False;
  MatchSize := -1;
  MatchDate := -1;
  MatchTime := -1;
  B := 1;
  while (B < P) and not (FMatch[B] in ['<', '>', '=', '|']) do Inc(B);
  Params := System.Copy(FMatch, 1, B - 1);
  Match := JustFName(Params);
  if UnixFlag then
    if not IsUnixMask(Match) then Match := Match + '*' else
  else if Pos('.', Match) = 0 then Match := Match + '*.*';
{  else if Match[Length(Match)] = '.' then Dec(Match[0]);}
  StartDir := JustPName(Params);
  if (FindOptions and opFromCurrent <> 0) or (StartDir <> '') then
    StartDir := lFExpand(StartDir);
  if B < P then
  begin
    Params := System.Copy(FMatch, B, P - B);
    while Params <> '' do
    begin
      B := 2;
      while (B <= Length(Params)) and
        not (Params[B] in ['<', '>', '=', '|']) do Inc(B);
      Param := System.Copy(Params, 1, B - 1);
      System.Delete(Params, 1, B - 1);
      case Param[1] of
	'<': Func := mfLess;
	'=': Func := mfEqual;
	'>': Func := mfGreater;
	else Func := mfAround;
      end;
      System.Delete(Param, 1, 1);
      L := SizeStrToSize(Param, Code);
      if L <> -1 then
      begin
	if MatchSize <> -1 then Exit;
	MatchSize := L;
	SizeFunc := Func;
      end
      else
      begin
	B := Pos('-', Param);
	if B <> 0 then
	begin
	  if MatchDate <> -1 then Exit;
	  Val(System.Copy(Param, 1, B - 1), DT.Month, Code);
	  if (DT.Month = 0) or (DT.Month > 12) then Exit;
	  System.Delete(Param, 1, B);
	  B := Pos('-', Param);
	  if B <> 0 then
	  begin
	    Val(System.Copy(Param, 1, B - 1), DT.Day, Code);
	    if (DT.Day = 0) or (DT.Day > 31) then Exit;
	    System.Delete(Param, 1, B);
	  end
	  else if Func = mfGreater then DT.Day := 31
	  else DT.Day := 0;
	  Val(System.Copy(Param, 1, 255), DT.Year, Code);
	  if (Code <> 0) or (DT.Year > 9999) then Exit;
	  if DT.Year < 80 then Inc(DT.Year, 2000)
	  else if DT.Year < 100 then Inc(DT.Year, 1900);
	  PackTime(DT, MatchDate);
	  DateFunc := Func;
	end
	else
	begin
	  B := Pos(':', Param);
	  if B <> 0 then
	  begin
	    if MatchTime <> -1 then Exit;
	    Val(System.Copy(Param, 1, B - 1), DT.Hour, Code);
	    if (Code <> 0) or (DT.Hour > 23) then Exit;
	    System.Delete(Param, 1, B);
	    Val(System.Copy(Param, 1, 2), DT.Min, Code);
	    if (Code <> 0) or (DT.Min > 59) then Exit;
	    DT.Sec := 0;
	    PackTime(DT, MatchTime);
	    TimeFunc := Func;
	  end
	  else Exit;
	end;
      end;
    end;
  end;
  ParseMatch := True;
end;

procedure NewMatch;
var
  I: Integer;
label
  Ins;
begin
{  GetMem(PMatch, SizeOf(TMatchRec));}
  GetMemUMB(PMatch, SizeOf(TMatchRec));
  if UnixFlag then
  begin
    PMatch^.Mask := nil;
{    GetMem(PMatch^.States, SizeOf(TState));}
    GetMemUMB(PMatch^.States, SizeOf(TState));
    MakeAutomaton(Match, PState(PMatch^.States)^);
  end
  else
  begin
{    PMatch^.Mask := NewStr(Match);}
    PMatch^.Mask := NewStrUMB(Match);
    PMatch^.States := nil;
  end;
  PMatch^.MatchSize := MatchSize;
  PMatch^.MatchDate := MatchDate;
  PMatch^.MatchTime := MatchTime;
  PMatch^.SizeFunc := SizeFunc;
  PMatch^.DateFunc := DateFunc;
  PMatch^.TimeFunc := TimeFunc;
  if (Length(StartDir) = 3) and (StartDir[1] in ['A'..'Z']) and
     (StartDir[2] = ':') and (StartDir[3] = '\') then
  begin
    if SearchDrives[StartDir[1]] = 0 then
      SearchDrives[StartDir[1]] := 2;
    StartDir := '';
  end;
  for I := 0 to MatchList^.Count - 1 do
  begin
    MP := MatchList^.At(I);
    if MP^.Dir = StartDir then Goto Ins;
  end;
  MP := New(PMatchCollection, Init(4, 4));
  MP^.Dir := StartDir;
  MatchList^.Insert(MP);
Ins:
  MP^.Insert(PMatch);
end;

procedure GetNextClipMatch;
begin
  if ClipIndex = ClipArea^.KB then
  begin
    DrawStatusLine('Parsing selected text in the Clipboard.');
    Message(Desktop, evBroadcast, cmUpdateViews, nil);
  end;
  GetClipLine(ClipIndex, S);
  TrimSt(S);
  P := Pos(' ', S);
  if P = 0 then P := 255;
  FMatch := Copy(S, 1, P) + ';';
  Inc(ClipIndex);
end;

label
  Clip, Quit;
begin
  if Searching then Exit;
  Owner^.GetData(Data);
  UnixFlag := Data.FileOptions and 1 <> 0;
  CaseFlag := Data.TxtOptions and 1 <> 0;
  RegExpFlag := Data.TxtOptions and 2 <> 0;
  ScanSelOnly := (FindOptions and opScanSelDirs <> 0) and
    (TagList^.Count <> 0);
  ArcNamePipePos := Pos('|', ArcNameFilter);
  if (Data.FindMatch <> '') or
     ((DiskClipStream <> nil) and ClipAreaSelected) then
  begin
    MatchList := New(PCollection, Init(4, 4));
    Buffer := nil;
    RegExpr := nil;
    if Data.TextMatch <> '' then
    begin
      FindSaveOptions := FindSaveOptions or fsTextSearch;
      SetSearchData(Data);
      if FindOptions and opInArchives <> 0 then
      begin
	MessageBox(^C'Cannot search for text in archives.',
	  nil, mfWarning + mfOkButton);
	FindOptions := FindOptions and not opInArchives;
      end;
      if not CaseFlag then UpcaseSt(Data.TextMatch);
      if RegExpFlag then
      begin
	RegExpr := RegCompile(Data.TextMatch);
	if RegExpr = nil then
	begin
	  PFindFileDialog(Owner)^.TextInput^.Select;
	  InvalidRegExpMsgBox;
	  Invalid := True;
	  Goto Quit;
	end;
      end
      else
      begin
	FPos := MaxAvail;
	if FPos > 100000 then BufSize := $FF00
	else if FPos > 65520 then BufSize := $8000
	else if FPos > 32760 then BufSize := $4000
	else BufSize := $1000;
	GetMem(Buffer, BufSize);
	BMMakeTable(Data.TextMatch, BT);
      end;
    end
    else FindSaveOptions := FindSaveOptions and not fsTextSearch;
    ClipIndex := ClipArea^.KB;
    CannotFind := True;
    NoDrives := True;
    OutOfMem := False;
    Searching := True;
    Invalid := False;
Clip:
    if Data.FindMatch = '' then GetNextClipMatch
    else FMatch := DelQuotesOrSpaces(Data.FindMatch) + ';';
    while (FMatch[0] > #0) and (FMatch[1] = ';') do
      System.Delete(FMatch, 1, 1);
    P := Pos(';', FMatch);
    while P <> 0 do
    begin
      if not ParseMatch then
      begin
	PFindFileDialog(Owner)^.FileInput^.Select;
	MessageBox(^C'Invalid Time/Size parameter.',
	  nil, mfError + mfOkButton);
	Invalid := True;
	Goto Quit;
      end;
      NewMatch;
      if LowMemory then
      begin
	PFindFileDialog(Owner)^.FileInput^.Select;
	Application^.OutOfMemory;
	Invalid := True;
	Goto Quit;
      end;
      System.Delete(FMatch, 1, P);
      while (FMatch[0] > #0) and (FMatch[1] = ';') do
	System.Delete(FMatch, 1, 1);
      P := Pos(';', FMatch);
    end;
    if (Data.FindMatch = '') and (ClipIndex <= ClipArea^.KK) then Goto Clip;
    for MatchIndex := 0 to MatchList^.Count - 1 do
    begin
      MP := MatchList^.At(MatchIndex);
      S := AddedSlash(MP^.Dir);
      if MP^.Dir <> '' then ScanDir(@S)
      else
      begin
	for Ch := 'A' to 'Z' do if SearchDrives[Ch] <> 0 then
	begin
	  S := Concat(Ch, ':\');
	  ScanDir(@S);
	  NoDrives := False;
	end;
	if NoDrives then
	begin
	  S := Concat(GetCurDrive, ':\');
	  ScanDir(@S);
	end;
      end;
    end;
Quit:
    DoneRoller;
    Searching := False;
    for Ch := 'A' to 'Z' do
      if SearchDrives[Ch] > 1 then SearchDrives[Ch] := 0;
    if Buffer <> nil then FreeMem(Buffer, BufSize);
    RegFree(RegExpr);
    Dispose(MatchList, Done);
    if not Invalid then
      if CannotFind then with PFindFileDialog(Owner)^ do
      begin
	if Data.TextMatch <> '' then S := Data.TextMatch
	else if Data.FindMatch <> '' then S := Data.FindMatch
	else S := 'File names in the Clipboard';
	NoMatchMsgBox(S);
      end
      else
      begin
        CalcSelSize;
        Select;
        FocusItem(Focused);
      end;
    UpdateCommands(List);
  end;
end;

procedure TFindFileList.SetSelect(Item: Pointer; On: Boolean);
begin
  if Item = nil then Exit;
  with PFindFileRec(Item)^ do
  begin
    if Selected = On then Exit;
    Selected := On;
    if On then
    begin
      Inc(SelCount);
      Inc(SelSize, Size);
    end
    else
    begin
      Dec(SelCount);
      Dec(SelSize, Size);
    end;
  end;
end;

procedure TFindFileList.SelectFocused;
var
  I: Integer;
  FR: PFindFileRec;
begin
  if List^.Count = 0 then Exit;
  FR := List^.At(Focused);
  if FR^.Attr and Directory = 0 then
{  begin}
    SetSelect(FR, not FR^.Selected);
    I := Focused + Byte(FMOptions and foInsMovesDown <> 0);
    while (I < List^.Count - 1) and
      (PFindFileRec(List^.At(I))^.Attr and Directory <> 0) do Inc(I);
    if I >= List^.Count then Dec(I);
    if I = Focused then DrawView
    else FocusItem(I);
{  end;}
end;

procedure TFindFileList.Invert;
var
  I: Integer;
  FR: PFindFileRec;
begin
  if List^.Count = 0 then Exit;
  for I := 0 to List^.Count - 1 do
  begin
    FR := List^.At(I);
    if FR^.Attr and Directory = 0 then
      SetSelect(FR, not FR^.Selected);
  end;
  DrawView;
end;

procedure TFindFileList.Unselect;
var
  I: Integer;
  FR: PFindFileRec;
begin
  for I := 0 to List^.Count - 1 do
  begin
    FR := List^.At(I);
    if FR <> nil then FR^.Selected := False;
  end;
  SelCount := 0;
  SelSize := 0;
end;

procedure TFindFileList.CalcSelSize;
var
  I: Integer;
  FR: PFindFileRec;
begin
  SelCount := 0;
  SelSize := 0;
  for I := 0 to List^.Count - 1 do
  begin
    FR := List^.At(I);
    if FR^.Selected then
    begin
      Inc(SelCount);
      Inc(SelSize, FR^.Size);
    end;
  end;
end;

procedure TFindFileList.Clear;
begin
  Owner^.Lock;
  SelCount := 0;
  SelSize := 0;
  List^.FreeAll;
  List^.SetLimit(List^.Delta);
  SetRange(0);
  FocusItem(0);
  PFindFileDialog(Owner)^.FileInput^.Select;
  DrawView;
  UpdateCommands(List);
  Owner^.Unlock;
end;

procedure TFindFileList.DeleteSelected;
var
  I, II: Integer;
  FR: PFindFileRec;
begin
  II := Focused;
  I := List^.Count - 1;
  while I >= 0 do
  begin
    FR := List^.At(I);
    if FR^.Selected then
    begin
      Dec(SelCount);
      Dec(SelSize, FR^.Size);
      List^.AtFree(I);
      if I < II then Dec(II);
      if (PFindFileRec(List^.At(I - 1))^.Attr and Directory <> 0) and
	 ((I = List^.Count) or
	  (PFindFileRec(List^.At(I))^.Attr and Directory <> 0)) then
      begin
	Dec(I);
	List^.AtFree(I);
	if I < II then Dec(II);
      end;
    end;
    Dec(I);
  end;
  if List^.Count = 0 then Clear
  else
  begin
    SetRange(List^.Count);
    if II >= Range then II := Range - 1;
    FocusItem(II);
    DrawView;
    UpdateCommands(List);
  end;
end;

procedure TFindFileList.GetSearchDrives;
var
  NumDrives, NumRows: Byte;
  Ch, CurDrive: Char;
  Data, L: Longint;
  D: PDialog;
  Control: PView;
  Item: PSItem;
  R: TRect;
  Drives: TDrivesArray;
begin
  GetDrives(NumDrives, Drives);
  CurDrive := GetCurDrive;
  if NumDrives > 20 then NumRows := 3
  else if NumDrives > 10 then NumRows := 2
  else NumRows := 1;
  if NumRows > 1 then R.Assign(0, 0, 27, 16)
  else R.Assign(0, 0, 27, 6 + NumDrives);
  D := InitSimpleDialog(R, 'Search drives', hcFindFileDlg);
  with D^ do
  begin
    Options := Options or ofCentered;
    Item := nil;
    for Ch := 'Z' downto 'A' do
      if Drives[Ch] = 1 then Item := NewSItem('~' + Ch + '~', Item);
    if NumRows = 3 then R.Assign(3, 2, Size.X - 3, Size.Y - 4)
    else if NumRows = 2 then R.Assign(6, 2, Size.X - 7, Size.Y - 4)
    else R.Assign(10, 2, 17, Size.Y - 4);
    Control := New(PMultiCheckBoxes, Init(R, Item, 2, cfOneBit, ' X'));
    Insert(Control);
    Control^.Select;
  end;
  Data := 0;
  L := 1;
  for Ch := 'A' to 'Z' do
  begin
    if (Drives[Ch] = 1) and (SearchDrives[Ch] = 1) then Data := Data or L;
    if Drives[Ch] = 1 then L := L shl 1;
  end;
  if Application^.ExecuteDialog(D, @Data, True) <> cmCancel then
  begin
    L := 1;
    for Ch := 'A' to 'Z' do
    begin
      if (Drives[Ch] = 1) and (Data and L <> 0) then
	SearchDrives[Ch] := 1
      else SearchDrives[Ch] := 0;
      if Drives[Ch] = 1 then L := L shl 1;
    end;
  end;
end;

procedure TFindFileList.GetSearchOptions;
type
  TData = record
    Opt: Word;
    Attr: Word;
    Filt: FNameStr;
    Sav: Word;
  end;
var
  SaveAttr: Word;
  D: PDialog;
  Control: PView;
  R: TRect;
  Data: TData;
begin
  R.Assign(0, 0, 60, 19);
  D := New(PDialog, Init(R, 'Find file options'));
  with D^ do
  begin
    Options := Options or ofCentered;
    HelpCtx := hcFindFileOptions;

    R.Assign(3, 3, 39, 9);
    Control := New(PCheckBoxes, Init(R,
      NewSItem('Start from ~c~urrent directory',
      NewSItem('Scan su~b~directories',
      NewSItem('Scan se~l~ected directories only',
      NewSItem('Search ~i~n archives',
      NewSItem('Find ta~g~ged files only',
      NewSItem('Select ~f~ound files', nil))))))));
    Insert(Control);
    R.Assign(2, 2, 39, 3);
    Insert(New(PLabel, Init(R, 'File search options', Control)));

    R.Assign(41, 3, 57, 9);
    Control := New(PCheckBoxes, Init(R,
      NewSItem('~R~ead only',
      NewSItem('~H~idden',
      NewSItem('~S~ystem',
      NewSItem('~D~irectory',
      NewSItem('~A~rchive',
      NewSItem('A~n~y file', nil))))))));
    Insert(Control);
    R.Assign(40, 2, 57, 3);
    Insert(New(PLabel, Init(R, 'File attributes', Control)));

    R.Assign(23, 10, 54, 11);
    Control := New(PInputLine, Init(R, 255));
    Insert(Control);
    StandardHistory(Control, AdvancedFilterHistoryID);
    R.Assign(2, 10, 23, 11);
    Insert(New(PLabel, Init(R, 'Archiv~e~ name filter', Control)));

    R.Assign(3, 13, 57, 15);
    Control := New(PCheckBoxes, Init(R,
      NewSItem('Sa~v~e/Restore Find file options and search drives',
      NewSItem('Clear <Containing text> in~p~ut line data on open',
	nil))));
    Insert(Control);
    R.Assign(2, 12, 57, 13);
    Insert(New(PLabel, Init(R, 'Search settings', Control)));

    StandardButtonRow(D, True);
  end;
  Data.Opt := FindOptions;
  SaveAttr := SearchAttr and 7;
  Data.Attr := ((SearchAttr and $F0) shr 1) or SaveAttr;
  Data.Filt := ArcNameFilter;
  Data.Sav := FindSaveOptions;
  if Application^.ExecuteDialog(D, @Data, True) <> cmCancel then
  begin
    FindOptions := Data.Opt;
    SaveAttr := Data.Attr and 7;
    SearchAttr := ((Data.Attr and $F8) shl 1) or SaveAttr;
    ArcNameFilter := Data.Filt;
    FindSaveOptions := Data.Sav;
  end;
end;

procedure TFindFileList.Update;
var
  I: Integer;
  FR: PFindFileRec;
  SR: lSearchRec;
  S: FNameStr;
begin
  FailSysErrors := True;
  I := 0;
  while I < List^.Count do
  begin
    FR := List^.At(I);
    if FR^.Info <> 0 then Inc(I)
    else if FR^.Attr and Directory <> 0 then
    begin
      if not DirPresent(FR^.ThePath^) then
      begin
	List^.AtFree(I);
	while I < List^.Count do
	begin
	  FR := List^.At(I);
	  if FR^.Attr and Directory <> 0 then Break;
	  SetSelect(FR, False);
	  List^.AtFree(I);
	end;
      end
      else Inc(I);
    end
    else
    begin
      List^.GetItemName(I, S);
      lFindFirst(S, AnyFile and not VolumeID, SR);
      lFindClose(SR);
      if DosError <> 0 then
      begin
	SetSelect(FR, False);
	List^.AtFree(I);
	if (PFindFileRec(List^.At(I - 1))^.Attr and Directory <> 0) and
	   ((I = List^.Count) or
	    (PFindFileRec(List^.At(I))^.Attr and Directory <> 0)) then
	begin
	  Dec(I);
	  List^.AtFree(I);
	end;
      end
      else Inc(I);
    end;
  end;
  FailSysErrors := False;
  if List^.Count = 0 then Clear
  else
  begin
    SetRange(List^.Count);
    DrawView;
    UpdateCommands(List);
  end;
end;

procedure TFindFileList.UpdateInfo;
var
  Params: Array[0..2] of Longint;
  S, C: FNameStr;
begin
  if SelCount > 0 then
  begin
    FormatBytes(C, SelSize, 100000000);
    Params[0] := Longint(@C);
    Params[1] := SelCount;
    if SelCount > 1 then Params[2] := Byte('s')
    else Params[2] := Byte(' ');
    FormatStr(S, '~%s~ bytes in ~%d~ selected file%c', Params);
  end
  else List^.GetItemName(Focused, S);
  Message(Owner, evBroadcast, cmShow, @S);
end;

procedure TFindFileList.CountSelTotals(Optimized: Boolean);
var
  I: Integer;
  FR: PFindFileRec;
begin
  I := 0;
  while I < List^.Count do
  begin
    FR := List^.At(I);
    if (FR^.Attr and Directory = 0) and (FR^.Info = 0) and
       ((not Optimized and FR^.Selected) or
	(Optimized and (Byte(FR^.Selected) = 255))) then
    begin
      BytesTotal := BytesTotal + FR^.Size;
      Inc(FilesTotal);
    end;
    Inc(I);
  end;
end;

procedure TFindFileList.FCopy;
var
  Optimized, Aborted, AppendSel: Boolean;
  I: Integer;
  L: Longint;
  Control: PView;
  FR: PFindFileRec;
  R: TRect;
  S, FromName, ToName, SaveName, ALabel: FNameStr;
label
  Start, Quit;
begin
  Optimized := False;
  AppendSel := False;
Start:
  if List^.Count = 0 then Exit;
  CopyStatus := dsOverwrite;
  TryAll := False;
  if SelCount > 0 then
  begin
    L := SelCount;
    if L = 1 then S := 'Copy 1 selected file to'
    else S := 'Copy %d selected files to';
    FormatStr(ALabel, S, L);
  end
  else
  begin
    FR := List^.At(Focused);
    if (FR^.Attr and Directory <> 0) or (FR^.Info <> 0) then Exit;
    List^.GetItemName(Focused, FromName);
    ALabel := 'Copy "' + TrimmedName(JustFileName(FromName), 39) + '" to'
  end;

  ToName := '';
  R.Assign(0, 0, 58, 8);
  if Application^.ExecuteDialog(InitCopyDialog(
    R, ALabel, False, False), @ToName, True) <> cmCancel then
  begin
    TrimSt(ToName);
    if (Pos(':', ToName) > 2) and (ToName[1] <> '+') then
    begin
      if UCPos('LIST:', ToName) = 1 then WriteToList(
	Trim(System.Copy(ToName, 6, 255)), False)
      else CopyToArchive(False, ToName);
      Goto Quit;
    end;

    ResetTotals;
    if ToName = '' then GetCurDir(0, ToName);
    if SelCount > 0 then
    begin
      if (Length(ToName) > 1) and (ToName[1] = '+') then
      begin
        System.Delete(ToName, 1, 1);
        TrimSt(ToName);
        AppendSel := True;
      end
      else if not ((ToName[0] = #2) and (ToName[2] = ':')) and
        not IsWild(ToName) then ToName := AddedSlash(ToName);

      if (FMOptions and foCheckFreeSpace <> 0) and not AppendSel then
      begin
	SaveName := lFExpand(ToName);
	if DeviceDest(SaveName) = '' then
	begin
	  OptimalFill(List, SaveName[1], 'copy', Optimized, Aborted);
	  if Aborted then Exit;
	end;
      end;

      if FileCopyOptions and fsDisableCopyStat = 0 then
	CountSelTotals(Optimized);
      SaveName := ToName;
      I := 0;
      while (I < List^.Count) and (CopyStatus <> dsCancel) do
      begin
	if AppendSel then CopyStatus := dsAppend
	else if CopyStatus = dsSkip then CopyStatus := dsOverwrite;
	FR := List^.At(I);
	if (FR^.Attr and Directory = 0) and (FR^.Info = 0) and
	   ((not Optimized and FR^.Selected) or
	    (Optimized and (Byte(FR^.Selected) = 255))) then
	begin
	  List^.GetItemName(I, FromName);
	  ToName := SaveName;
	  CancelSysErrors := False;
	  ValidNames(FromName, ToName);
	  if CancelSysErrors then Break;
	  FCopyPrim(FromName, ToName);
	  if (CopyStatus <> dsCancel) and (not Skipped or
	     (FileCopyOptions and fsUnselectSkipped <> 0)) then
	  begin
	    SetSelect(FR, False);
	    if FMOptions and foUnsel <> 0 then DrawView;
	  end;
	end;
	Inc(I);
      end;
      if Optimized and (CopyStatus <> dsCancel) then
      begin
	Message(Desktop, evBroadcast, cmUpdateDisk, nil);
	DrawView;
	Goto Start;
      end;
    end
    else
    begin
      if (Length(ToName) > 1) and (ToName[1] = '+') then
      begin
	System.Delete(ToName, 1, 1);
	TrimSt(ToName);
	CopyStatus := dsAppend;
      end;
      CancelSysErrors := False;
      ValidNames(FromName, ToName);
      if CancelSysErrors then Exit;
      if FileCopyOptions and fsDisableCopyStat = 0 then
      begin
	BytesTotal := FR^.Size;
	FilesTotal := 1;
      end;
      FCopyPrim(FromName, ToName);
    end;
    Message(Desktop, evBroadcast, cmUpdateDisk, nil);
Quit:
    DrawView;
  end;
end;

procedure TFindFileList.FRenMov(One: Boolean);
var
  Optimized, Aborted, AppendSel: Boolean;
  I: Integer;
  L: Longint;
  Control: PView;
  FR: PFindFileRec;
  R: TRect;
  S, FromName, ToName, SaveName, ALabel: FNameStr;
label
  Start, Quit;
begin
  Optimized := False;
  AppendSel := False;
Start:
  if List^.Count = 0 then Exit;
  CopyStatus := dsOverwrite;
  DeleteStatus := dsDelete;
  TryAll := False;
  if not One and (SelCount > 0) then
  begin
    L := SelCount;
    if L = 1 then S := 'Rename or move 1 selected file to'
    else S := 'Rename or move %d selected files to';
    FormatStr(ALabel, S, L);
  end
  else
  begin
    FR := List^.At(Focused);
    if (FR^.Attr and Directory <> 0) or (FR^.Info <> 0) then Exit;
    List^.GetItemName(Focused, FromName);
    ALabel := 'Rename or move "' +
      TrimmedName(JustFileName(FromName), 29) + '" to'
  end;

  if One then ToName := FromName else ToName := '';
  R.Assign(0, 0, 58, 8);
  if Application^.ExecuteDialog(InitRenMovDialog(
    R, ALabel, False, False), @ToName, True) <> cmCancel then
  begin
    TrimSt(ToName);
    if (Pos(':', ToName) > 2) and (ToName[1] <> '+') then
    begin
      CopyToArchive(True, ToName);
      Goto Quit;
    end;

    ResetTotals;
    if ToName = '' then GetCurDir(0, ToName);
    if not One and (SelCount > 0) then
    begin
      if (Length(ToName) > 1) and (ToName[1] = '+') then
      begin
	System.Delete(ToName, 1, 1);
	TrimSt(ToName);
        AppendSel := True;
      end
      else if not ((ToName[0] = #2) and (ToName[2] = ':')) and
	not IsWild(ToName) then ToName := AddedSlash(ToName);

      if (FMOptions and foCheckFreeSpace <> 0) and not AppendSel then
      begin
	SaveName := lFExpand(ToName);
	if DeviceDest(SaveName) = '' then
	begin
	  OptimalFill(List, SaveName[1], 'move', Optimized, Aborted);
	  if Aborted then Exit;
	end;
      end;

      if FileCopyOptions and fsDisableCopyStat = 0 then
	CountSelTotals(Optimized);
      SaveName := ToName;
      I := 0;
      while (I < List^.Count) and
	(CopyStatus <> dsCancel) and
	(DeleteStatus <> dsCancel) do
      begin
	if AppendSel then CopyStatus := dsAppend
	else if CopyStatus = dsSkip then CopyStatus := dsOverwrite;
	if DeleteStatus = dsSkip then DeleteStatus := dsDelete;
	FR := List^.At(I);
	if (FR^.Attr and Directory = 0) and (FR^.Info = 0) and
	   ((not Optimized and FR^.Selected) or
	    (Optimized and (Byte(FR^.Selected) = 255))) then
	begin
	  List^.GetItemName(I, FromName);
	  ToName := SaveName;
	  CancelSysErrors := False;
	  ValidNames(FromName, ToName);
	  if CancelSysErrors then Break;
	  FRenMovPrim(FromName, ToName);
	  if (CopyStatus <> dsCancel) and (not Skipped or
	     (FileCopyOptions and fsUnselectSkipped <> 0)) then
	  begin
	    SetSelect(FR, False);
	    if FMOptions and foUnsel <> 0 then DrawView;
	  end;
	end;
	Inc(I);
      end;
      if Optimized and (CopyStatus <> dsCancel) and
	 (DeleteStatus <> dsCancel) then
      begin
	Message(Desktop, evBroadcast, cmUpdateDisk, nil);
	DrawView;
	Goto Start;
      end;
    end
    else
    begin
      if (Length(ToName) > 1) and (ToName[1] = '+') then
      begin
	System.Delete(ToName, 1, 1);
	TrimSt(ToName);
	CopyStatus := dsAppend;
      end;
      CancelSysErrors := False;
      ValidNames(FromName, ToName);
      if CancelSysErrors then Exit;
      if FileCopyOptions and fsDisableCopyStat = 0 then
      begin
	BytesTotal := FR^.Size;
	FilesTotal := 1;
      end;
      FRenMovPrim(FromName, ToName);
    end;
    Message(Desktop, evBroadcast, cmUpdateDisk, nil);
Quit:
    Update;
  end;
end;

procedure TFindFileList.FDelete(One: Boolean);
var
  I: Integer;
  L: Longint;
  FR: PFindFileRec;
  S, SS: String;
begin
  if List^.Count = 0 then Exit;
  DeleteStatus := dsDelete;
  if not One and (SelCount > 0) then
  begin
    L := SelCount;
    if L = 1 then S := 'file' else S := 'files';
    if MessageBox(^C'OK to delete %d selected ' + S + ' ?', @L,
      mfConfirmation + mfOkCancel) = cmCancel then Exit;
    I := 0;
    while (I < List^.Count) and (DeleteStatus <> dsCancel) do
    begin
      FR := List^.At(I);
      if FR^.Selected and
	(FR^.Attr and Directory = 0) and
	(FR^.Info = 0) then
      begin
	List^.GetItemName(I, S);
	FDeletePrim(S);
	if not (DeleteStatus in [dsSkip, dsSkipAll, dsCancel]) then
	begin
	  SetSelect(FR, False);
	  if FMOptions and foUnsel <> 0 then DrawView;
	end;
      end;
      Inc(I);
    end;
  end
  else
  begin
    FR := List^.At(Focused);
    if (FR^.Attr and Directory <> 0) or
       (FR^.Info <> 0) then Exit;
    List^.GetItemName(Focused, S);
    if MessageBox(^C'OK to delete the file'#13^C +
      TrimmedName(S, 32) + ' ?', nil,
      mfConfirmation + mfOkCancel) = cmCancel then Exit;
    FDeletePrim(S);
    if DeleteStatus = dsCancel then Exit;
  end;
  Message(Desktop, evBroadcast, cmUpdateDisk, nil);
  Update;
end;

procedure TFindFileList.FOpen(ACommand: Word);
var
  I, IHi: Integer;
  Command, IsOpenCommand, C, OC: Word;
  P: Pointer;
  FR: PFindFileRec;
  V: PView;
  S: String;
  Data: TFindData;

procedure DoSearch;
begin
  if (TypeOf(V^) = TypeOf(TFileEditor)) or
     (TypeOf(V^) = TypeOf(THexEditor)) then with PFileEditor(V)^ do
  begin
    IsReplacing := True;
    Search(SearchData.MatchStr, SearchData.Options);
    IsReplacing := False;
  end
  else if TypeOf(V^) = TypeOf(TTextViewer) then with PTextViewer(V)^ do
  begin
    NoSearchMsg := True;
    Search(SearchData.MatchStr, SearchData.Options);
    NoSearchMsg := False;
  end
  else if TypeOf(V^) = TypeOf(THexViewer) then with PHexViewer(V)^ do
  begin
    NoSearchMsg := True;
    Search(SearchData.MatchStr, SearchData.Options);
    NoSearchMsg := False;
  end
  else if TypeOf(V^) = TypeOf(TDataViewer) then with PDataViewer(V)^ do
  begin
    Replacing := True;
    Find(SearchData.MatchStr, SearchData.Options);
    Replacing := False;
  end
  else if TypeOf(V^) = TypeOf(THTMLViewer) then with PHTMLViewer(V)^ do
  begin
    NoSearchMsg := True;
    Search(SearchData.MatchStr, SearchData.Options);
    NoSearchMsg := False;
  end;
end;

begin
  if List^.Count = 0 then Exit;
  Owner^.GetData(Data);
  case ACommand of
    cmIsASCII:
      begin
	Command := cmOpenASCII;
	IsOpenCommand := cmASCIIOpened;
      end;
   cmIsHex:
     begin
       Command := cmOpenHex;
       IsOpenCommand := cmHexOpened;
     end;
   cmFView:
     begin
       Command := cmOpenTextViewer;
       IsOpenCommand := cmOpenTextViewer;
     end;
   else
     begin
       Command := cmOpenHexViewer;
       IsOpenCommand := cmOpenHexViewer;
     end;
  end;
  if (SelCount > 0) and (FMOptions and foOpenFocused = 0) then
    IHi := List^.Count else IHi := 0;
  I := 0;
  CancelSysErrors := False;
  EditorAborted := False;
  repeat
    if IHi = 0 then FR := List^.At(Focused)
    else FR := List^.At(I);
    if (FR^.Attr and Directory = 0) and (FR^.Info = 0) and
       ((IHi = 0) or FR^.Selected) then
    begin
      if ((ACommand = cmIsASCII) or (ACommand = cmFView)) and
	 (CompUCString(FR^.Ext, 'DBF') = Equal) then
      begin
	C := cmOpenDataViewer;
	OC := cmOpenDataViewer;
      end
      else if (ACommand = cmFView) and (UCPos('HTM', FR^.Ext) = 1) then
      begin
	C := cmOpenHTML;
	OC := cmOpenHTML;
      end
      else
      begin
	C := Command;
	OC := IsOpenCommand;
      end;
      if IHi = 0 then List^.GetItemName(Focused, S)
      else List^.GetItemName(I, S);
      if C = cmOpenHTML then
      begin
	FillChar(ExecRec, SizeOf(ExecRec), 0);
	ExecRec.Path := S;
	P := @ExecRec;
      end
      else P := @S;
      Message(Application, evCommand, C, P);
      if CancelSysErrors or EditorAborted or LowMemory then Break;
      if Data.TextMatch <> '' then
      begin
	SetSearchData(Data);
	V := Message(Desktop, evBroadcast, OC, P);
	if V <> nil then DoSearch;
      end;
    end;
    Inc(I);
  until I >= IHi;
  UpdateCommands(List);
end;

procedure TFindFileList.ClipCopy;
var
  I, SaveCount, MaxLen: Integer;
  FR: PFindFileRec;
  S: String;
begin
  if (DiskClipStream = nil) or (List^.Count = 0) then Exit;
  DrawFMClipCopyStatus;
  if SelCount > 0 then
  begin
    SetExpandClipAreaFlag;
    MaxLen := 0;
    SaveCount := ClipLines^.Count;
    for I := 0 to List^.Count - 1 do
    begin
      FR := List^.At(I);
      if (FR^.Attr and Directory = 0) and FR^.Selected then
      begin
	List^.GetItemName(I, S);
	PutClipLine(S);
	if not MarkColumns or (Length(S) > MaxLen) then
	  MaxLen := Length(S);
	if ClipOverflow then Break;
      end;
      if (KbdStatus = 1) and FMAbortAction then Break;
    end;
    SetOrExpandClipArea(SaveCount, MaxLen + 1);
    Message(Desktop, evBroadcast, cmUpdateViews, nil);
  end
  else
  begin
    FR := List^.At(Focused);
    if FR^.Attr and Directory <> 0 then
    begin
      S := FR^.ThePath^;
      if Length(S) > 3 then S := ClearedSlash(S);
    end
    else List^.GetItemName(Focused, S);
    Message(Application, evBroadcast, cmCopy, @S);
  end;
end;

procedure TFindFileList.ClipPaste(Unsel: Boolean);
var
  B: Byte;
  I, N: Integer;
  P: PFindFileRec;
  S: String;

procedure FindByName(const AName: FNameStr; var Index: Integer);
var
  FR: PFindFileRec;
  Dir, Name, Ext: String;
begin
  lFSplit(AName, Dir, Name, Ext);
  if Ext <> '' then System.Delete(Ext, 1, 1);
  Index := 0;
  repeat
    FR := List^.At(Index);
    if FR = nil then Break;
    if FR^.Attr and Directory = 0 then
      if (FR^.ThePath <> nil) and (FR^.ThePath^[1] = #0) then
	if CompUCString(System.Copy(FR^.ThePath^, 2, 255),
	  JustFileName(AName)) = Equal then Exit;
      if (CompUCString(FR^.Name, Name) = Equal) and
	 (CompUCString(FR^.Ext, Ext) = Equal) then Exit;
    Inc(Index);
  until Index >= List^.Count;
  Index := -1;
end;

procedure ScanLine;
begin
  B := 1;
  repeat
    TrimSt(S);
    if S = '' then Exit;
    while (B <= Length(S)) and (S[B] <> ' ') do Inc(B);
    FindByName(Copy(S, 1, B - 1), N);
    if N <> -1 then
    begin
      P := List^.At(N);
      SetSelect(P, not Unsel);
    end;
    System.Delete(S, 1, B);
  until False;
end;

begin
  if (DiskClipStream = nil) or not ClipAreaSelected or
     (List^.Count = 0) then Exit;
  DrawStatusLine('Searching for the selected file names.');
  Message(Desktop, evBroadcast, cmUpdateViews, nil);
  for I := ClipArea^.KB to ClipArea^.KK do
  begin
    if EscPressed then Break;
    GetClipLine(I, S);
    ScanLine;
  end;
  DrawView;
end;

procedure TFindFileList.CopyToArchive(RenMov: Boolean; const Dest: FNameStr);
var
  P: PArchiveRec;
  PS: PString;
  DestName: FNameStr;

procedure PrepareParams(var Params: TParamStr);
var
  I: Integer;
  FR: PFindFileRec;
  S: FNameStr;
  F: Text;
begin
  if SelCount = 0 then
  begin
    List^.GetItemName(Focused, S);
    if P^.Main and amEnableLFN = 0 then lGetShortFileName(S, S);
    Params := Params + ' ' + S;
    Exit;
  end;
  if P^.ListChar = #0 then
  begin
    Params := '';
    Exit;
  end;
  GetValidSwapDir(S, False);
  S := S + SelListName;
  Params := Params + ' ' + P^.ListChar + S;
  Assign(F, S);
  Rewrite(F);
  if IOResult <> 0 then Exit;
  for I := 0 to List^.Count - 1 do
  begin
    FR := List^.At(I);
    if (FR^.Attr and Directory = 0) and FR^.Selected then
    begin
      List^.GetItemName(I, S);
      if P^.Main and amEnableLFN = 0 then lGetShortFileName(S, S);
      Writeln(F, S);
    end;
  end;
  Close(F);
  InOutRes := 0;
end;

begin
  GetArchiveRec(Dest, P);
  if P = nil then Exit;
  if P^.PackerName = nil then
  begin
    DisposeArchiveRec(P);
    Exit;
  end;
  with ExecRec do
  begin
    Path := P^.PackerName^;
    if RenMov then PS := P^.Move
    else PS := P^.Add;
    if PS = nil then Params := ''
    else Params := PS^ + ' ';
    DestName := Trim(Copy(Dest, Pos(':', Dest) + 1, 255));
    if P^.Main and amEnableLFN = 0 then
      lGetShortFileName(DestName, DestName);
    Params := Params + DestName;
    if (JustExtension(DestName) = '') and (P^.Extension <> nil) then
      DestName := ForceExtension(DestName, P^.Extension^);
    DestName := lFExpand(DestName);
    if (P^.Main and amPrompt <> 0) and ThisFileExists(DestName) and
       (OverwriteArcMsgBox(DestName) <> cmOk) then Params := ''
    else PrepareParams(Params);
    if Params = '' then
    begin
      DisposeArchiveRec(P);
      Exit;
    end;
  end;
  if SelCount <> 0 then Unselect;
  RunPacker(P);
end;

procedure TFindFileList.WriteToList(const Name: FNameStr; ForPanel: Boolean);
var
  I, ILo, IHi: Integer;
  FR: PFindFileRec;
  S: FNameStr;
  F: lText;
begin
  LogEvent := lcDiskWrite;
  DrawStatusLine('Creating list file ' + TrimmedName(Name, 48) + '.');
  lAssignText(F, Name);
  if not ForPanel and ThisFileExists(Name) then Append(F.T)
  else Rewrite(F.T);
  if IOResult <> 0 then
  begin
    CouldNotCreateMsgBox(Name);
    Exit;
  end;
  if not ForPanel and (SelCount = 0) then
  begin
    ILo := Focused;
    IHi := Focused;
  end
  else
  begin
    ILo := 0;
    IHi := List^.Count - 1;
  end;
  for I := ILo to IHi do
  begin
    FR := List^.At(I);
    if not ForPanel then
      if ((SelCount <> 0) and not FR^.Selected) or
	 ((SelCount = 0) and (I <> Focused)) then Continue;
    if ((FR^.Attr and Directory = 0) and (FR^.Info = 0)) or
       ((FR^.Attr and Directory <> 0) and (FR^.Info <> 0)) then
    begin
      List^.GetItemName(I, S);
      if FR^.Info <> 0 then S := ClearedSlash(S);
      Writeln(F.T, S);
    end;
  end;
  Close(F.T);
  InOutRes := 0;
  UpdateFiles(Name);
end;

procedure TFindFileInfoPane.Draw;
var
  C: Byte;
  B: TDrawBuffer;
begin
  C := GetColor(1);
  MoveChar(B, ' ', C, Size.X);
  if Name = '' then MoveStr(B[1], 'No files found', C)
  else if (Length(Name) > 1) and (Name[2] = ':') then
    MoveStr(B[1], Name, C)
  else MoveCStr(B[1], Name, GetColor($0201));
  WriteLine(0, 0, Size.X, 1, B);
end;

function TFindFileInfoPane.GetPalette: PPalette;
const
  P: String[2] = #7#9;
begin
  GetPalette := @P;
end;

procedure TFindFileInfoPane.HandleEvent(var Event: TEvent);
begin
  TView.HandleEvent(Event);
  if (Event.What = evBroadcast) and (Event.Command = cmShow) then
  begin
    if Event.InfoPtr = nil then Name := ''
    else Name := TrimmedName(PString(Event.InfoPtr)^, Size.X - 2);
    DrawView;
  end;
end;

{ Added 02.03.99 }
function REGEXPPutHistoryFunc(Link: PInputLine;
  const S: String): String;
var
  P: PCheckBoxes;
begin
  P := PFindFileDialog(Link^.Owner)^.TextCheckBox;
  if (S = '') or (P^.Value and 2 = 0) then REGEXPPutHistoryFunc := S
  else REGEXPPutHistoryFunc := S + REGEXPHistStr;
end;

function REGEXPGetHistoryFunc(Link: PInputLine;
  const S: String): String;
var
  I: Integer;
  P: PCheckBoxes;
begin
  I := Pos(REGEXPHistStr, S);
  P := PFindFileDialog(Link^.Owner)^.TextCheckBox;
  P^.PreserveBits := 2;
  if I <> 0 then P^.Value := P^.Value or 2
  else P^.Value := P^.Value and not 2;
  P^.DrawView;
  if I <> 0 then REGEXPGetHistoryFunc := Copy(S, 1, I - 1)
  else REGEXPGetHistoryFunc := S;
end;

constructor TFindFileDialog.Init(AList: Pointer);
var
  VBar, HBar: PScrollBar;
  Subview: PView;
  R: TRect;
  S: String;
begin
  R.Assign(0, 0, 80, ScreenHeight - 3);
  TDialog.Init(R, 'Find file');
  Options := Options or ofCentered;
  GrowMode := GrowMode + gfGrowHiY;
  HelpCtx := hcFindFileDlg;

  R.Assign(2, Size.Y - 8, Size.X - 14, Size.Y - 7);
  HBar := New(PScrollBar, Init(R));
  HBar^.Options := HBar^.Options or ofPostProcess;
  HBar^.SetRange(0, 255);
  Insert(HBar);
  R.Assign(Size.X - 14, 2, Size.X - 13, Size.Y - 8);
  VBar := New(PScrollBar, Init(R));
  VBar^.Options := VBar^.Options or ofPostProcess;
  Insert(VBar);

  R.Assign(2, 2, Size.X - 14, Size.Y - 8);
  FindFileList := New(PFindFileList, Init(R, 1, HBar, VBar));
  FindFileList^.GrowMode := gfGrowHiY;
  FindFileList^.List := PFindFileCollection(AList);
  FindFileList^.SetRange(PCollection(AList)^.Count);
  FindFileList^.CalcSelSize;
  Insert(FindFileList);

  R.Assign(2, 1, Size.X - 13, 2);
  Insert(New(PLabel, Init(R,
    '~F~ound files          Size   Date    Time             Full name',
    FindFileList)));

  R.Assign(2, Size.Y - 7, Size.X - 14, Size.Y - 6);
  Subview := New(PFindFileInfoPane, Init(R));
  Subview^.GrowMode := gfGrowLoY + gfGrowHiY;
  Subview^.EventMask := Subview^.EventMask or evBroadcast;
  Insert(Subview);

  R.Assign(18, Size.Y - 5, Size.X - 26, Size.Y - 4);
  FileInput := New(PInputLine, Init(R, 255));
  FileInput^.GrowMode := gfGrowLoY + gfGrowHiY;
  Insert(FileInput);
  R.Assign(1, Size.Y - 5, 18, Size.Y - 4);
  Subview := New(PLabel, Init(R, 'File(s) ~t~o find', FileInput));
  Subview^.GrowMode := gfGrowLoY + gfGrowHiY;
  Insert(Subview);
  Subview := StandardHistory(FileInput, FindFileHistoryId);
  Subview^.GrowMode := gfGrowLoY + gfGrowHiY;
  PHistory(Subview)^.HistoryPutFunc := UNIXPutHistoryFunc;
  PHistory(Subview)^.HistoryGetFunc := UNIXGetHistoryFunc;

  R.Assign(18, Size.Y - 3, Size.X - 29, Size.Y - 2);
  TextInput := New(PInputLine, Init(R, 255));
  TextInput^.Options := TextInput^.Options or ofValidate;
  TextInput^.GrowMode := gfGrowLoY + gfGrowHiY;
  Insert(TextInput);
  R.Assign(1, Size.Y - 3, 18, Size.Y - 2);
  Subview := New(PLabel, Init(R, 'Conta~i~ning text', TextInput));
  Subview^.GrowMode := gfGrowLoY + gfGrowHiY;
  Insert(Subview);
  Subview := StandardHistory(TextInput, FindHistoryId);
  Subview^.GrowMode := gfGrowLoY + gfGrowHiY;
  PHistory(Subview)^.HistoryPutFunc := REGEXPPutHistoryFunc;
  PHistory(Subview)^.HistoryGetFunc := REGEXPGetHistoryFunc;

  R.Assign(Size.X - 23, Size.Y - 5, Size.X - 2, Size.Y - 4);
  UNIXCheckBox := New(PCheckBoxes, Init(R,
    NewSItem('~U~NIX style mask', nil)));
  UNIXCheckBox^.GrowMode := gfGrowLoY + gfGrowHiY;
  UNIXCheckBox^.Value := Byte(UnixFlag);
  Insert(UNIXCheckBox);

  R.Assign(Size.X - 26, Size.Y - 3, Size.X - 2, Size.Y - 1);
  TextCheckBox := New(PCheckBoxes, Init(R,
    NewSItem('Case se~n~sitive',
    NewSItem('Regular e~x~pression', nil))));
  TextCheckBox^.GrowMode := gfGrowLoY + gfGrowHiY;
  TextCheckBox^.Value := Byte(CaseFlag) + Byte(RegExpFlag) shl 1;
  Insert(TextCheckBox);

  R.Assign(Size.X - 13, 2, Size.X - 2, 4);
  Insert(New(PButton, Init(R, '~S~earch', cmModifyItem, bfDefault)));
  Inc(R.A.Y, 2); Inc(R.B.Y, 2);
  Insert(New(PButton, Init(R, '~G~o to', cmNewItem, bfNormal)));
  Inc(R.A.Y, 2); Inc(R.B.Y, 2);
  Insert(New(PButton, Init(R, '~P~anel', cmRunItem, bfNormal)));
  Inc(R.A.Y, 2); Inc(R.B.Y, 2);
  Insert(New(PButton, Init(R, '~C~lear', cmDeleteItem, bfNormal)));
  Inc(R.A.Y, 2); Inc(R.B.Y, 2);
  Insert(New(PButton, Init(R, '~D~rives', cmSelectLeftDrive, bfNormal)));
  Inc(R.A.Y, 2); Inc(R.B.Y, 2);
  Insert(New(PButton, Init(R, '~O~ptions', cmSelectRightDrive, bfNormal)));
  Inc(R.A.Y, 2); Inc(R.B.Y, 2);
  StandardCancelButton(R, @Self);

  S := HistoryStr(FindFileHistoryID, 0);
  FileInput^.SetData(S);
  if (FindSaveOptions and fsClearText = 0) and
     (FindSaveOptions and fsTextSearch <> 0) then
  begin
    S := HistoryStr(FindHistoryID, 0);
    TextInput^.SetData(S);
  end;

  if FindFileList^.Range = 0 then FileInput^.Select
  else FindFileList^.Select;
  UpdateCommands(AList);
end;

procedure TFindFileDialog.HandleEvent(var Event: TEvent);
begin
  if FindFileList^.Searching and
    (((Event.What = evKeyDown) and (Event.KeyCode = kbEsc)) or
     ((Event.What = evCommand) and (Event.Command = cmCancel))) then
  begin
    FindFileList^.Searching := False;
    Event.What := evNothing;
  end;
  if (Event.What = evKeyDown) and
     (FindFileList^.State and sfSelected <> 0) then
    if (Event.KeyCode = kbEnter) and (FindFileList^.List^.Count <> 0) then
    begin
      Event.What := evCommand;
      Event.Command := cmNewItem;
    end
    else if Event.KeyCode = kbDel then
    begin
      Event.What := evCommand;
      Event.Command := cmDeleteItem;
      if FindFileList^.SelCount <> 0 then
	Event.InfoPtr := FindFileList^.List;
    end;
  TDialog.HandleEvent(Event);
  if Event.What = evCommand then
  begin
    case Event.Command of
      cmModifyItem: FindFileList^.Search;
      cmNewItem, cmRunItem:
	with FindFileList^ do if List^.Count > 0 then
	begin
	  FindFileList^.Searching := False;
	  Selected := Focused;
	  EndModal(Event.Command);
	end;
      cmDeleteItem:
	if Event.InfoPtr = FindFileList^.List then
	  FindFileList^.DeleteSelected
	else FindFileList^.Clear;
      cmSelectLeftDrive:
	begin
	  FindFileList^.GetSearchDrives;
	  FileInput^.Select;
	end;
      cmSelectRightDrive:
	begin
	  FindFileList^.GetSearchOptions;
	  FileInput^.Select;
	end;
      else Exit;
    end;
    Event.What := evNothing;
  end;
end;

end.
