unit GvFmTree;

{ Graphics Vision 2.20 File Manager -- Tree module
  Copr. 1998 Matthias K"oppe

  Derived from units INFOVIEW, DRAGDROP (part), FILEVIEW, TREEWIN
  of the Turbo Vision File Manager Demo, which is
  copr. 1992 Borland International
}

{$X+,V-}

interface

uses {$ifdef Windows} WinDos, {$endif} Dos, Drivers, Objects, Views, {Dialogs,}
  GvViews, GvDialog, GvStdDlg, GvApp, GvFmGlob, GvTag, GvRedir;

type
  PCntView = ^TCntView;
  TCntView = object(TStaticText)
    Bytes: LongInt;
    Count: LongInt;
    constructor Init(var Bounds: TRect);
    procedure HandleEvent(var Event: TEvent); virtual;
    procedure GetText(var S: String); virtual;
  end;

  PTagView = ^TTagView;
  TTagView = object(TCntView)
    procedure HandleEvent(var Event: TEvent); virtual;
    procedure GetText(var S: String); virtual;
  end;

type
  { TListViewer that supports grabbing an item with the mouse }
  PDDList = ^TDDList;
  TDDList = object(TListViewer)
    procedure HandleEvent(var Event: TEvent); virtual;
    procedure PickUpItem(Item: Integer; Where: TPoint); virtual;
  end;

type
  PDirView = ^TDirView;
  TDirView = object(TDirListBox)
    constructor Init(var Bounds: TRect; AScrollBar: PScrollBar;
      ARoot: PString);
    procedure FocusItem(Item: Integer); virtual;
    function Pointed(Where: TPoint): PDirEntry;
    procedure HandleEvent(var Event: TEvent); virtual;
    procedure SetState(AState: Word; Enable: Boolean); virtual;
    {$ifdef HAVE_REDIR}
    procedure NewDirectory(var ADir: DirStr); virtual;
    {$endif}
    function GoodItem(Item: Integer): Boolean;
  end;

type
  PFileViewTagger = ^TFileViewTagger;
  TFileViewTagger = object(TListTagger)
    function Taggable(Item: Integer): Boolean; virtual;
    function Tagged(Item: Integer): Boolean; virtual;
    procedure SetTag(Item: Integer; Enable: Boolean); virtual;
  end;

type
  PFileView = ^TFileView;
  TFileView = object(TDDList)
    Foc: Integer;
    Dir: PathStr;
    List: PFileList;
    DoneScanning: Boolean;
    Search: TRedirSearchRec;
    Tagger: PListTagger;
    constructor Init(var Bounds: TRect; {AHScrollBar, }AVScrollBar: PScrollBar);
    procedure HandleEvent(var Event: TEvent); virtual;
    destructor Done; virtual;
    function SearchForFiles(First: Boolean): Boolean; virtual;
    {procedure ScanSingleFile(FileName: PathStr);}
    {function GetPalette : PPalette; virtual;}
    procedure Draw; virtual;
    procedure DrawItemText(Item: Integer; R: TRect); virtual;
    function GetText(Item: Integer; MaxLen: Integer): String; virtual;
    procedure SetState(AState: Word; Enable: Boolean); virtual;
    procedure PickUpItem(Item: Integer; Where: TPoint); virtual;
  end;

type
  PTreeWindow = ^TTreeWindow;
  TTreeWindow = object(TWindow)
    CntView, TagView: PGView;
    DirView: PDirView; {PDirectoryViewer;}
    FileView: PFileView;
    procedure SizeLimits(var Min, Max: TPoint); virtual;
    procedure HandleEvent(var Event: TEvent); virtual;
    function GetTitle(Width: Integer): TTitleStr; virtual;
    procedure SetMode(Mode: Word);
  end;

procedure InsertTreeWindow(Drive: string);

implementation

uses Memory, Misc, GvMsgBox,
  {$ifdef Windows} WinGr, {$else} MyFonts, {$endif} GvDriver, ExtGraph,
  GvFmEqu, GvFmTool, GvTexts;

{ TCntView }

constructor TCntView.Init(var Bounds: TRect);
begin
  inherited Init(Bounds, '');
  Count := 0;
  Bytes := 0;
  GrowMode := gfGrowHiY + gfGrowLoY + gfGrowHiX;
  EventMask := evBroadcast;
end;

procedure TCntView.GetText(var S: string);
begin
  FormatStr(S, '%d bytes in %d files', Bytes);
end;

procedure TCntView.HandleEvent(var Event: TEvent);
begin
  inherited HandleEvent(Event);
  if (Event.What = evBroadcast) and (Event.Command = cmScanComplete) then
  begin
    with PScanInfo(Event.InfoPtr)^ do
    begin
      Bytes := ScanBytes;
      Count := ScanCount;
      DrawView;
    end;
  end;
end;

{ TTagView }

procedure TTagView.GetText(var S: String);
begin
  FormatStr(S, '%d bytes in %d tagged files', Bytes);
end;

procedure TTagView.HandleEvent(var Event: TEvent);
var
  F: PFileRec;
begin
  { don't execute the directly inherited HandleEvent }
  TStaticText.HandleEvent(Event);
  if Event.What = evBroadcast then
  begin
    case Event.Command of
      cmNewDir,
      cmRescan :
        begin
          Bytes := 0;
          Count := 0;
          DrawView;
        end;
      cmTagChanged :
        begin
          F := Event.InfoPtr;
          if F^.Tagged then
          begin
            Inc(Count);
            Inc(Bytes, F^.Size);
          end
          else
          begin
	    Dec(Count);
            Dec(Bytes, F^.Size);
          end;
	  DrawView;
          ClearEvent(Event);
        end;
    end;
  end;
end;

{ TDDList }

procedure TDDList.PickUpItem(Item: Integer; Where: TPoint);
begin
end;

procedure TDDList.HandleEvent(var Event: TEvent);
var
  Mouse: TPoint;
  NewItem, OldItem: Integer;
  I: Integer;
  R: TRect;

function HasMoved(const P1, P2: TPoint): Boolean;
begin
  HasMoved := Abs(P1.X - P2.X) + Abs(P1.Y - P2.Y) > 5;
end;

begin
  if (Event.What = evMouseDown) and (Event.Buttons = mbLeftButton) then
  begin
    TGView.HandleEvent(Event);
    if Event.What = evNothing then Exit;
    OldItem := Focused;
    MakeLocal(Event.Where, Mouse);

    For I := TopItem to TopItem + GetPageSize + (Flags and lfPartialLines) * NumCols do
    begin
      GetItemRect(I, R);
      If R.Contains(Mouse) then Begin
	NewItem := I;
	Break;
      End;
    End;

    if NewItem <> OldItem then
    begin
      if NewItem < 0 then NewItem := 0
      else if (NewItem >= Range) and (Range > 0) then NewItem := Range - 1;
      if Range <> 0 then FocusItem(NewItem);
      {DrawView;}
    end;

    if Event.Double and (Range > Focused) then SelectItem(Focused);

    { possibly a drag/drop operation }
    if (Mouse.X > 1*8) and (Mouse.X <= 13*8) then
    begin
      while MouseEvent(Event, evMouseMove) and (Event.Buttons = mbLeftButton) do
      begin
	MakeLocal(Event.Where, Event.Where);
	if HasMoved(Event.Where, Mouse) then
	begin
	  MakeGlobal(Event.Where, Mouse);
	  PickUpItem(Focused, Mouse);
	  ClearEvent(Event);
	  Break;
	end;
      end;
    end;

    ClearEvent(Event);
  end;
  inherited HandleEvent(Event);
end;

{ TDirView }

constructor TDirView.Init(var Bounds: TRect; AScrollBar: PScrollBar;
  ARoot: PString);
var
  S: DirStr;
begin
  inherited Init(Bounds, AScrollBar);
  HelpCtx := hcDirView;
  if ARoot^[2] = '@'
  then begin
    S := ARoot^;
    if S[Length(S)] <> '\' then S := S + '\'
  end
  else GetDir(Ord(ARoot^[1]) - Ord('@'), S);
  NewDirectory(S);
  {FocusItem(1);}
end;

function TDirView.GoodItem(Item: Integer): Boolean;
begin
  GoodItem := (Item > 0) and (Item < Range) and (PDirEntry(List^.At(Item))^.DisplayOpt >= doDrive)
end;

procedure TDirView.FocusItem(Item: Integer);
begin
  inherited FocusItem(Item);
  if GoodItem(Item) and (GOwner <> nil)
  then begin
    Message({Desktop}GOwner, evBroadcast, cmNewDir,
      PDirEntry(List^.At(Item))^.Directory);
  end
end;

function TDirView.Pointed(Where: TPoint): PDirEntry;
var
  I: Integer;
  R: TRect;
begin
  Pointed := nil;
  For I := TopItem to TopItem + GetPageSize + (Flags and lfPartialLines) * NumCols do
  begin
    GetItemRect(I, R);
    If R.Contains(Where) then Begin
      if GoodItem(I)
      then Pointed := List^.At(I);
      Exit;
    End;
  End;
end;

procedure TDirView.HandleEvent(var Event: TEvent);
var
  S: DirStr;
  Mover: PFileMover;
  Where: TPoint;
  D: PDirEntry;
begin
  inherited HandleEvent(Event);
  if Event.What = evBroadcast then
  begin
    case Event.Command of
      cmGetCurrentDir:
	begin
	  if GoodItem(Focused)
	  then begin
	    S := PDirEntry(List^.At(Focused))^.Directory^;
	    if S[Length(S)] = '\' then Dec(S[0]);
	    PString(Event.InfoPtr)^ := S;
	    ClearEvent(Event);
	  end
	end;
      cmItemDropped:
	begin
	  Mover := Event.InfoPtr;
	  Desktop^.MakeGlobal(Mover^.Origin, Where);
	  if MouseInView(Where) then
	  begin
	    ClearEvent(Event);
	    MakeLocal(Where, Where);
	    D := Pointed(Where);
            if D <> nil
            then begin
	      S := D^.Directory^;
	      if S[Length(S)] = '\' then Dec(S[0]);
	      DragDropCopy(Mover, S);
            end
	  end;
	end;
      cmVisitDir:
	if GoodItem(Focused)
	then begin
	  S := PDirEntry(List^.At(Focused))^.Directory^;
	  if S[Length(S)] = '\' then Dec(S[0]);
	  with PFileRec(Event.InfoPtr)^ do
	    S := S + '\' + Name + Ext;
	  S := FileSystem^.FExpand(S);
	  NewDirectory(S);
	end;
      else
	Exit;
    end;
  end
  else if (Event.What = evCommand) and (Event.Command = GvStdDlg.cmChangeDir)
  then begin
    S := PDirEntry(List^.At(Focused))^.Directory^;
    NewDirectory(S)
  end
end;

procedure TDirView.SetState(AState: Word; Enable: Boolean);
begin
  { dont call directly inherited method }
  TListBox.SetState(AState, Enable)
end;

{$ifdef HAVE_REDIR}
procedure LoStr(var S: String);
var I: Integer;
begin
  For I:=1 to Length (S) do
    if S[I] in ['A'..'Z'] then S[I] := Chr(Ord(S[I]) + 32);
End;

procedure TDirView.NewDirectory(var ADir: DirStr);
const
  IndentSize         = '  ';
var AList: PCollection;
    NewDir, Dirct: DirStr;
    C, OldC: Char;
    S: String[80];
    P: PString;
    isFirst: Boolean;
    SR: TRedirSearchRec;
    I: Integer;
    DirEntry: PDirEntry;
    Index: Integer;
    Opt: Word;
    Indent: Byte;

 function NewDirEntry (DisplayOpt: Word; Directory: String): PDirEntry; {$ifndef FPK}near;{$endif}
 var DirEntry: PDirEntry;
 Begin
   New (DirEntry);
   DirEntry^.DisplayOpt:=DisplayOpt;
   DirEntry^.Directory:=NewStr (Directory);
   NewDirEntry:=DirEntry;
 End;

 procedure InsertDrives(DriveSep: Char);
 var
   C: Char;
 begin
    for C:='A' to 'Z' do Begin
      If (FileSystem^.GetDriveType(C + DriveSep + '\') <> dtInvalid)
      then Begin
        Opt := doDrive;
        AList^.Insert (NewDirEntry (Opt + 2, C + DriveSep + '\'));
        If C = 'C' {GetCurDrive} then Cur := AList^.Count - 1;
      End;
    End;
  End;

Begin
  Dir:=ADir;
  AList:=New (PDirCollection, Init(5,5));
  AList^.Insert(NewDirEntry(doDrivesText, 'Systems'));
  if Dir = 'Systems'
  then begin
    AList^.Insert(NewDirEntry(doDrivesText + 1, 'Local Drives'));
    AList^.Insert(NewDirEntry(doDrivesText + 1, 'Remote Drives'))
  end
  else if Dir = 'Local Drives'
  then begin
    AList^.Insert(NewDirEntry(doDrivesText + 1, 'Local Drives'));
    InsertDrives(':')
  end
  else if Dir = 'Remote Drives'
  then begin
    AList^.Insert(NewDirEntry(doDrivesText + 1, 'Remote Drives'));
    InsertDrives('@')
  end
  Else Begin
    if Dir[2] = '@'
    then AList^.Insert(NewDirEntry(doDrivesText + 1, 'Remote Drives'))
    else AList^.Insert(NewDirEntry(doDrivesText + 1, 'Local Drives'));
    Indent:=2;
    NewDir:=Dir;
    if Flags and dfLowerCase <> 0 then LoStr(NewDir);
    Dirct:=Copy (NewDir,1,3);
    AList^.Insert (NewDirEntry(doPathDir + Indent, Dirct));
    Inc(Indent);
    NewDir:=Copy(NewDir,4,255);
    While NewDir <> '' do Begin
      I:=Pos (PathSeparator,NewDir);
      If I <> 0 then Begin
        S:=Copy (NewDir,1,I-1);
	Dirct := Dirct + S;
        AList^.Insert (NewDirEntry (doPathDir + Indent , Dirct));
        NewDir:=Copy(NewDir,I+1,255);
      End
      Else Begin
        Dirct:=Dirct + NewDir;
        AList^.Insert (NewDirEntry (doPathDir + Indent, Dirct));
	NewDir:='';
      End;
      Inc(Indent);
      Dirct:=Dirct + PathSeparator;
    End;
    Cur:=AList^.Count-1;
    isFirst:=True;
    NewDir:=Dirct + AllFiles;
    FileSystem^.FindFirst (NewDir, Directory, SR);
    While DosError = 0 do Begin
      If (SR.Attr and Directory <> 0) and (SR.Name[1] <> '.') then Begin
        if Flags and dfLowerCase <> 0 then LoStr(SR.Name);
        If isFirst then	Begin
	  Opt:=doFirstDir;
	  isFirst:=False;
	End
        Else Opt:=doMiddleDir;
        Index := AList^.Count;
        if Flags and dfSorted <> 0
        then begin
          while (PDirEntry(AList^.At(Index-1))^.DisplayOpt and 255 = Indent)
                and (PDirEntry(AList^.At(Index-1))^.Directory^ > Dirct + SR.Name)
          do Dec(Index);
        end;
        AList^.AtInsert(Index, NewDirEntry (Indent + Opt, Dirct + SR.Name));
      End;
      FileSystem^.FindNext (SR);
    End;
    FileSystem^.FindEnd(SR);
  End;
  NewFocus := Cur;
  If TopItem <> 0 then TopItem := 0;
  NewList (AList);
End;
{$endif}

{ TFileViewTagger }

function TFileViewTagger.Taggable(Item: Integer): Boolean;
begin
  Taggable := PFileRec(PFileView(Viewer)^.List^.At(Item))^.Attr and Dos.Directory = 0
end;

function TFileViewTagger.Tagged(Item: Integer): Boolean;
begin
  Tagged := PFileRec(PFileView(Viewer)^.List^.At(Item))^.Tagged
end;

procedure TFileViewTagger.SetTag(Item: Integer; Enable: Boolean);
begin
  if PFileRec(PFileView(Viewer)^.List^.At(Item))^.Tagged <> Enable
  then begin
    PFileRec(PFileView(Viewer)^.List^.At(Item))^.Toggle;
    Message(Viewer^.GOwner, evBroadcast, cmTagChanged,
      PFileView(Viewer)^.List^.At(Item));
  end
end;

{ TFileView }

constructor TFileView.Init(var Bounds: TRect; {AHScrollBar,}
  AVScrollBar: PScrollBar);
begin
  inherited Init(Bounds, {1, AHScrollBar,} AVScrollBar);
  Tagger := New(PFileViewTagger, Init(@Self));
  HelpCtx := hcFileView;
  List := New(PFileList, Init(30,10));
  Dir := '';
  DoneScanning := True;
  EventMask := EventMask or evBroadcast;
  Foc := 0;
end;

{function TFileView.GetPalette: PPalette;
const
  MyPal : String[length(CListViewer)] = #6#6#7#6#1;
begin
  GetPalette := @MyPal;
end;}

procedure TFileView.Draw;
begin
  inherited Draw;
{  if List^.Count = 0 then
  begin
    C := GetColor(1);
    MoveChar(B, ' ', C, Size.X);
    MoveStr(B, GetStr(sNoFiles), C);
    WriteLine(0, 0, Size.X, 1, B);
  end; }
end;

procedure TFileView.DrawItemText(Item: Integer; R: TRect);
begin
  SetTextParams(ftMonospace, 0, sColor, false);
  inherited DrawItemText(Item, R);
end;

function TFileView.GetText(Item: Integer; MaxLen: Integer): String;
var
  F: PFileRec;
  S: String;
  Params: array[0..3] of Pointer;
  DOpt: Word;
  D: string;
begin
  if Item < List^.Count then
  begin
    F := List^.At(Item);
    Params[0] := @F^.Name;
    Params[1] := @F^.Ext;
    Params[2] := Pointer(F^.Size);

    with ConfigRec do
    begin
      if F^.Attr and Directory <> 0
      then begin
	if DisplayFields and $1 <> 0
	then begin
	  D := '<DIR>';
	  Params[2] := @D;
	  FormatStr(S, ' %-8s%-4s %-7s', Params)
	end
	else
	  FormatStr(S, ' %-8s%-4s', Params);
       end
      else begin
	if DisplayFields and $1 <> 0
	then FormatStr(S, ' %-8s%-4s %7d', Params)
	else FormatStr(S, ' %-8s%-4s', Params);
      end;

      if F^.Tagged
      then S[1] := TagChar;

      DOpt := (DisplayFields and $6) shr 1;  { change 0xx0 -> 0,1,2,3 }

      { 0=none, 1=Date, 2=Time, 3=Date and Time }
      if DOpt > 0 then S := S + ' ' + FormatDateTime(F^.Time, DOpt);

      if (DisplayFields and $8) <> 0 then S := S + ' ' + FormatAttr(F^.Attr);
    end;

    if Length(S) > MaxLen then S[0] := Char(MaxLen);

    if ConfigRec.DisplayCase = 0 then LowerCase(S);
    GetText := S;

  end else GetText := '';
end;

type
  PSearchRec = ^SearchRec;

function TFileView.SearchForFiles(First: Boolean): Boolean;
var
  F: PFileRec;
begin
  SearchForFiles := False;
  if First then FileSystem^.FindFirst(Dir + '\' + ConfigRec.FileMask, AnyFile, Search)
  else FileSystem^.FindNext(Search);
  if FileSystemError = 0 then
  begin
    if (Search.Attr and UnwantedFiles = 0) and (Search.Name <> '.') then
    begin
      F := New(PFileRec, Init(PSearchRec(@Search)^));
      List^.Insert(F);
    end;
  end
  else begin
    FileSystem^.FindEnd(Search);
    SearchForFiles := True;  { done searching }
  end
end;

{procedure TFileView.ScanSingleFile(FileName: PathStr);
var
  F: PFileRec;
begin
  FindFirst(FileName, AnyFile, Search);
  if DosError = 0 then
  begin
    if (Search.Attr and UnwantedFiles = 0) and (Search.Name <> '.') then
    begin
      F := New(PFileRec, Init(Search));
      List^.Insert(F);
      SetRange(List^.Count);
      DrawView;
    end;
  end;
end;}

procedure TFileView.HandleEvent(var Event: TEvent);
var
  F: PFileRec;
  P: PFileNameRec;
  ScanInfo: TScanInfo;
  Where: TPoint;
  Mover: PFileMover;
  I: Integer;
  WildCard: string[12];
  R: TRect;
  PointedTagged: Boolean;

  procedure ReverseTags(F: PFileRec); {$ifndef FPK}far;{$endif}
  begin
    if (F^.Attr and Directory = 0)
    then begin
      F^.Toggle;
      Message(Owner, evBroadcast, cmTagChanged, F);
    end
  end;

  procedure ClearTags(F: PFileRec); {$ifndef FPK}far;{$endif}
  begin
    if F^.Tagged then
    begin
      F^.Toggle;
      Message(Owner, evBroadcast, cmTagChanged, F);
    end;
  end;

  procedure TagPerCard(F: PFileRec); {$ifndef FPK}far;{$endif}
  begin
    if (F^.Attr and Directory = 0)
       and WildCardMatch(F^.Name + F^.Ext, WildCard) then
    begin
      if not F^.Tagged
      then begin
        F^.Tagged := True;
        Message(Owner, evBroadcast, cmTagChanged, F);
      end
    end;
  end;

  procedure UntagPerCard(F: PFileRec); {$ifndef FPK}far;{$endif}
  begin
    if (F^.Attr and Directory = 0)
       and WildCardMatch(F^.Name + F^.Ext, WildCard) then
    begin
      if F^.Tagged
      then begin
        F^.Tagged := false;
        Message(Owner, evBroadcast, cmTagChanged, F);
      end
    end;
  end;

  function MatchFile(F: PFileRec): Boolean; {$ifndef FPK}far;{$endif}
  begin
    P := Event.InfoPtr;
    MatchFile := (P^.Dir = Dir + '\') and (P^.Name = F^.Name) and
      (P^.Ext = F^.Ext);
  end;

  procedure CountBytes(F: PFileRec); {$ifndef FPK}far;{$endif}
  begin
    Inc(ScanInfo.ScanBytes, F^.Size);
  end;

  function Pointed(Mouse: TPoint): Integer;
  var
    I: Integer;
  begin
    MakeLocal(Mouse, Mouse);
    For I := TopItem to TopItem + GetPageSize + (Flags and lfPartialLines) * NumCols do
    begin
      GetItemRect(I, R);
      If R.Contains(Mouse) then Begin
	Pointed := I;
	Exit;
      End;
    End;
    Pointed := -1
  end;

begin
  Tagger^.HandleEvent(Event);
  inherited HandleEvent(Event);

  if Event.What = evKeyDown then
  begin
    case Event.KeyCode of
      kbEnter:
	if List^.Count > 0 then
	begin
	  F := List^.At(Focused);
	  if F^.Attr and Directory <> 0
	  then Message(Owner, evBroadcast, cmVisitDir, F);
	end;
    end;
  end;

  if Event.What = evBroadcast then
  begin
    case Event.Command of

      { Scan a new directory, or rescan current directory }
      cmNewDir,
      cmRescan :
        begin
	  if Event.Command = cmNewDir
	  then begin
	    Dir := PString(Event.InfoPtr)^;
	    if Dir[Length(Dir)] = '\' then Dec(Dir[0])
	  end;
          GOwner^.Last^.DrawView; {Force the frame to redraw }
          DoneScanning := False;
          List^.FreeAll;
          DoneScanning := SearchForFiles(True); { search for the first file }
          if (not DoneScanning) and LowMemory then
          begin
            DoneScanning := True;
	    Application^.OutOfMemory;
          end;
          if DoneScanning then
          begin
	    SetRange(List^.Count);
	    DrawView;
	    ScanInfo.ScanCount := List^.Count;
	    ScanInfo.ScanBytes := 0;
	    List^.ForEach(@CountBytes);
	    Message(Owner, evBroadcast, cmScanComplete, @ScanInfo);
	  end;
	  if Event.Command = cmNewDir then ClearEvent(Event);
	end;

      { Mark the current file as tagged if an ordinary file;
	enter directory if directory. }
      cmListItemSelected :
	begin
	  if List^.Count > 0 then
	  begin
	    F := List^.At(Focused);
	    if F^.Attr and Directory <> 0
	    then Message(Owner, evBroadcast, cmVisitDir, F);
	  end;
        end;

      { Reorder and redraw the list since the sort order may have changed }
      cmRefreshDisplay :
	begin
	  PFileList(List)^.Reorder;
	  DrawView;
	end;

      cmItemDropped :
	begin
	  Mover := Event.InfoPtr;
	  Desktop^.MakeGlobal(Mover^.Origin, Where);
	  if MouseInView(Where) then
	  begin
	    ClearEvent(Event);
	    I := Pointed(Where);
	    if (I >= 0) and (I < List^.Count)
	    then begin
	      with PFileRec(List^.At(I))^ do
		if Attr and Directory <> 0
		then begin
		  DragDropCopy(Mover, Dir + '\' + Name + Ext);
		  Exit
		end
	    end;
	    DragDropCopy(Mover, Dir);
	  end;
	end;
    end; { case }
  end;

  if (Event.What = evBroadcast) and (Event.Command = cmIdle) then
  begin
    if not DoneScanning then
    begin
      DoneScanning := SearchForFiles(False);
      if DoneScanning then
      begin
        SetRange(List^.Count);
        DrawView;
        ScanInfo.ScanCount := List^.Count;
        ScanInfo.ScanBytes := 0;
        List^.ForEach(@CountBytes);
        Message(Owner, evBroadcast, cmScanComplete, @ScanInfo);
      end;
    end;
  end;

  if Event.What = evCommand then
  begin
    case Event.Command of
      cmReverseTags:
        List^.ForEach(@ReverseTags);
      cmClearTags:
        List^.ForEach(@ClearTags);
      cmTagPerCard:
	begin
	  R.Assign(0,0,35*8,8*16);
          R.Move((Desktop^.Size.X - R.B.X) div 2, (Desktop^.Size.Y - R.B.Y) div 2);
	  WildCard := '*.*';
	  if InputBoxRect(R, 'Tag per wildcard', 'Wildcard', WildCard, 12) = cmOK then
	  begin
	    UpperCase(WildCard);
	    List^.ForEach(@TagPerCard);
	  end;
	end;
      cmUntagPerCard:
	begin
	  R.Assign(0,0,35*8,8*16);
	  R.Move((Desktop^.Size.X - R.B.X) div 2, (Desktop^.Size.Y - R.B.Y) div 2);
	  WildCard := '*.*';
	  if InputBoxRect(R, 'Untag per wildcard', 'Wildcard', WildCard, 12) = cmOK then
	  begin
	    UpperCase(WildCard);
	    List^.ForEach(@UntagPerCard);
	  end;
	end;
      else Exit;
    end;
    DrawView;
    ClearEvent(Event);
  end;
end;

procedure TFileView.PickUpItem(Item: Integer; Where: TPoint);
{begin end;}
var
  R: TRect;
  Mover: PMover;
  E: TEvent;
  Min, Max: TPoint;
  F: PFileRec;
  NewList: PCollection;
  S: SearchRec;

  function CloneFileRec(Orig: PFileRec): PFileRec;
  begin
    S.Name := Orig^.Name + Orig^.Ext;
    S.Attr := Orig^.Attr;
    S.Size := Orig^.Size;
    S.Time := Orig^.Time;
    CloneFileRec := New(PFileRec, Init(S));
  end;

  procedure AddIfTagged(FileRec: PFileRec); {$ifndef FPK}far;{$endif}
  begin
    if FileRec^.Tagged then
      NewList^.Insert(CloneFileRec(FileRec));
  end;

begin
  NewList := New(PCollection, Init(10, 5));

  F := List^.At(Item);  { are we dragging the tagged files? }
  if F^.Tagged then List^.ForEach(@AddIfTagged)
  else NewList^.Insert(CloneFileRec(F));

  {Dec(Where.Y);}
  Desktop^.MakeLocal(Where, Where);
  Mover := New(PFileMover, Init(Where, Dir, NewList));
  {Inc(Where.Y);}
  Desktop^.MakeGlobal(Where, Where);
  Desktop^.Insert(Mover);
  Desktop^.GetExtent(R);

  E.What := evMouseDown;
  E.Where := Where;
  Min := Mover^.Size;
  Max := Min;
  Mover^.DragView(E, dmDragMove, R, Min, Max, 0);
  Message(Desktop, evBroadcast, cmItemDropped, Mover);
  Dispose(Mover, Done);
  Dispose(NewList, Done);
end;

procedure TFileView.SetState(AState: Word; Enable: Boolean);

  procedure ShowScrollBar(SBar: PScrollBar);
  begin
{    if (SBar <> nil) then
      if GetState(sfActive + sfSelected) then SBar^.Show
      else SBar^.Hide;}
  end;

begin
  inherited SetState(AState, Enable);
  if AState and (sfActive + sfSelected) <> 0 then
  begin
    {ShowScrollBar(HScrollBar);}
    ShowScrollBar(ScrollBar);
  end;
end;

destructor TFileView.Done;
begin
  if List <> nil then Dispose(List, Done);
  inherited Done;
end;

{ TTreeWindow }

procedure TTreeWindow.SizeLimits(var Min, Max: TPoint);
begin
  inherited SizeLimits(Min, Max);
  Min.X := 48*8;
end;

function TTreeWindow.GetTitle(Width: Integer): TTitleStr;
begin
  GetTitle := FileView^.Dir + '\' + ConfigRec.FileMask;
end;

procedure TTreeWindow.HandleEvent(var Event: TEvent);
var
  F: PFileRec;
  D: PDirEntry;
  S: DirStr;
  CurrentFile: PathStr;
begin
  inherited HandleEvent(Event);

  if Event.What = evBroadcast then
  begin
    case Event.Command of
      cmInvalidDir :
	begin
	  {D := DirView^.GetNode(DirView^.Foc);}
	  D := DirView^.List^.At(DirView^.Focused);
          S := D^.Directory^;
          if S[Length(S)] = '\' then Dec(S[0]);
	  if PString( Event.InfoPtr )^ = S then
	    Message(FileView, evBroadcast, cmRescan, nil);
	end;
      cmTopWindow:
	ClearEvent(Event);
      cmFileListFocused :
	if (Current = PGView(FileView)) and (FileView^.List^.Count > 0) then
	  ClearEvent(Event);
      cmCloseAll :
	Close;
    end;
  end;

  if Event.What = evCommand then
  begin
    case Event.Command of
      cmTreeMode+1..cmTreeMode+3:
	begin
	  SetMode(Event.Command - cmTreeMode);
	  ClearEvent(Event);
	end;
    end;
    { Get the full filename of the current file }
    if (Current = PGView(FileView)) and (FileView^.List^.Count > 0) then
    begin
      D := DirView^.List^.At(DirView^.Focused);
      F := FileView^.List^.At(FileView^.Focused);
      S := D^.Directory^;
      if S[Length(S)] = '\' then Dec(S[0]);
      CurrentFile := S + '\' + F^.Name + F^.Ext;

      case Event.Command of
	{cmViewAsHex : ViewAsHex(CurrentFile);
	cmViewAsText : ViewAsText(CurrentFile);
	cmViewCustom : ViewCustom(CurrentFile);
	cmExecute : ExecuteFile(CurrentFile);}
	{cmAssociate: Associate(F^.Ext);}
	cmCopy : HandleFileCopy(S, FileView^.List, FileView^.Focused);
	cmDelete : HandleFileDelete(S, FileView^.List, FileView^.Focused);
	cmRename : RenameFile(S, F);
	cmChangeAttr : ChangeAttr(S, F);
	else Exit;
      end; { case }
      ClearEvent(Event);
    end;
  end;
end;

procedure TTreeWindow.SetMode(Mode: Word);
var
  Bounds, DirRect, FileRect, R: TRect;
begin
  GetClientRect(Bounds);
  Bounds.Grow(1, 1);
  DirRect.Copy(Bounds);
  FileRect.Copy(Bounds);

  DirView^.SetState(sfVisible, Mode and 1 <> 0);
  DirView^.Scrollbar^.SetState(sfVisible, Mode and 1 <> 0);
  FileView^.SetState(sfVisible, Mode and 2 <> 0);
  FileView^.Scrollbar^.SetState(sfVisible, Mode and 2 <> 0);
  CntView^.SetState(sfVisible, Mode and 2 <> 0);
  TagView^.SetState(sfVisible, Mode and 2 <> 0);

  if Mode and 3 = 3
  then begin
    DirRect.B.X := Bounds.A.X + (Bounds.B.X - Bounds.A.X) div 3;
    FileRect.A.X := DirRect.B.X + 3;
  end;

  if Mode and 3 = 1
  then begin
    DirView^.GrowMode := gfGrowHiX + gfGrowHiY;
    DirView^.Scrollbar^.GrowMode := gfGrowHiY + gfGrowLoX + gfGrowHiX;
  end
  else begin
    DirView^.GrowMode := gfGrowHiY;
    DirView^.Scrollbar^.GrowMode := gfGrowHiY;
  end;

  R.Assign(DirRect.B.X-18, DirRect.A.Y, DirRect.B.X, DirRect.B.Y);
  DirView^.Scrollbar^.Locate(R);
  Dec(DirRect.B.X, 17);
  DirView^.Locate(DirRect);

  R.Assign(FileRect.A.X + 1, FileRect.B.Y - 32,
    FileRect.B.X - 1, FileRect.B.Y - 16);
  CntView^.Locate(R);
  R.Move(0, 16);
  Dec(R.B.Y);
  TagView^.Locate(R);

  Dec(FileRect.B.Y, 32);
  R.Assign(FileRect.B.X-18, FileRect.A.Y, FileRect.B.X, FileRect.B.Y);
  FileView^.Scrollbar^.Locate(R);
  Dec(FileRect.B.X, 17);
  FileView^.Locate(FileRect);

end;

procedure InsertTreeWindow(Drive: string);
var
  B, Bounds, R: TRect;
  W, TopWin: PTreeWindow;
  vSB, hSB: PScrollBar;
  PRoot: PString;
  Root: String[2];
  S: string[40];
  D: PDialog;
  Width: Integer;
  {Gate: PGGate;
  usb: PUpScrollbar;}
begin
  if Length(Drive) = 1
  then Root := Drive + ':'
  else Root := Drive;
  PRoot := @Root;

  FormatStr(S, GetStr(sScanning), PRoot);
  D := WaitDialog(S);
  Desktop^.Insert(D);

  Bounds.Assign(0,0,75*8,16*16);
  W := New(PTreeWindow, Init(Bounds, Root, wnNoNumber));
  with W^ do
  begin
    Options := Options or ofTileable;
    GetClientRect(Bounds);
    Bounds.Grow(1, 1);

    Width := Bounds.B.X - Bounds.A.X;
    Bounds.B.X := Width div 3;

    R.Assign(Bounds.B.X-18, Bounds.A.Y, Bounds.B.X, Bounds.B.Y);
    vSB := New(PScrollBar, Init(R));
    vSB^.Options := vSB^.Options or ofPostProcess;
    vSB^.GrowMode :=  gfGrowHiY;
    Insert(vSB);

{    R.Assign(Bounds.A.X+2, Bounds.B.Y-1, Bounds.B.X-2, Bounds.B.Y);
    hSB := New(PScrollBar, Init(R));
    hSB^.Options := hSB^.Options or ofPostProcess;
    hSB^.GrowMode := gfGrowHiY + gfGrowLoY;
    Insert(hSB);}

    {Bounds.Grow(-1,-1);}

(*    usb := New(PUpScrollbar, Init(vSB));
    B.Assign(Bounds.A.X div 8, Bounds.A.Y div 8, Bounds.B.X div 8, Bounds.B.Y div 8);
    DirView := New(PDirectoryViewer, Init(B, nil{hSB}, uSB,
      New(PDirectory, Init(Root))));
    Gate := NewGGate(DirView);
    Gate^.TGate^.InsertBefore(usb, nil);

    with DirView^ do
    begin
      Options := Options or (ofFramed or ofFirstClick);
      Adjust(GetRoot, True);
      GrowMode := gfGrowHiY;
      Update;
    end;
    Gate^.MoveTo(Bounds.A.X, Bounds.A.Y);
    Insert(Gate {DirView}); *)

    Dec(Bounds.B.X, 17);
    DirView := New(PDirView, Init(Bounds, vSB, PRoot));
    DirView^.GrowMode := gfGrowHiY;
    Insert(DirView);

    { Create the file viewer }
    GetClientRect(Bounds);
    Bounds.Grow(1, 1);

    Bounds.A.X := Bounds.A.X + (Width div 3) - 1;
    Dec(Bounds.B.Y, 34);

    R.Assign(Bounds.B.X-18, Bounds.A.Y, Bounds.B.X, Bounds.B.Y);
    vSB := New(PScrollBar, Init(R));
    vSB^.Options := vSB^.Options or ofPostProcess;
    vSB^.GrowMode :=  gfGrowHiY + gfGrowLoX + gfGrowHiX;
    Insert(vSB);

    {R.Assign(Bounds.A.X+2, Bounds.B.Y-1, Bounds.B.X-2, Bounds.B.Y);
    hSB := New(PScrollBar, Init(R));
    hSB^.Options := hSB^.Options or ofPostProcess;
    hSB^.GrowMode := gfGrowHiY + gfGrowLoY + gfGrowHiX;
    hSB^.SetRange(0, 40);
    Insert(hSB);}

    {Bounds.Grow(-1,-1);}
    Dec(Bounds.B.X, 17);
    FileView := New(PFileView, Init(Bounds, {hSB, }vSB));
    FileView^.GrowMode := gfGrowHiY + gfGrowHiX;
    {FileView^.Options := FileView^.Options or ofFramed;}
    Insert(FileView);

    GetClientRect(Bounds);
    Bounds.A.X := Bounds.A.X + (Width div 3) - 1;
    Bounds.A.Y := Bounds.B.Y - 32;
    Bounds.B.Y := (Bounds.B.Y + Bounds.A.Y) div 2;
    CntView := Insert(New(PCntView, Init(Bounds)));

    Bounds.Move(0, Bounds.B.Y - Bounds.A.Y);
    TagView := Insert(New(PTagView, Init(Bounds)));

    SelectNext(False);
    DirView^.FocusItem(DirView^.Focused);
  end;

  { decide where to place this window }
  TopWin := Message(Desktop, evBroadcast, cmTopWindow, nil);
  if TopWin <> nil then
  begin
    W^.MoveTo(TopWin^.Origin.X + IconSize, TopWin^.Origin.Y + IconSize);
    W^.GrowTo(TopWin^.Size.X, TopWin^.Size.Y);
  end
  else
    W^.MoveTo(IconSize, IconSize);

  if Application^.ValidView(W) <> nil then
  begin
    Desktop^.Insert(W);
    {Message(Desktop, evBroadcast, cmNewDir, PRoot);}
  end;

  Dispose(D, Done);
end;

end.
