unit uTiFilesEdit;
{
This code is part of the TiCodEd Application, designed to develop for the
TI-99/4a Homecomputer in a modern, structured way.

BSD License
Copyright (c) 2026, Stefan Bauch, TiCodEd <at> lizardware <dot> de

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

}
{$mode ObjFPC}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ComCtrls,
  ExtCtrls, PairSplitter, Buttons, MaskEdit, Grids, ShellCtrls, Menus, StdActns,
  EditBtn, Types;

type

  { TFormTiFiles }

  TFormTiFiles = class(TForm)
    bbDSK1: TBitBtn;
    bbDSK2: TBitBtn;
    bbDSK3: TBitBtn;
    bbDSK4: TBitBtn;
    bbExit: TBitBtn;
    bbSave: TBitBtn;
    bbNew: TBitBtn;
    cbTiFilesOrg: TComboBox;
    cbTiFilesV9T9: TComboBox;
    cbTiFileType: TComboBox;
    cbProtected: TCheckBox;
    cbRecLen: TComboBox;
    EdEOF: TEdit;
    EdRecSec: TEdit;
    EdTiFileName: TEdit;
    EdNumSec: TEdit;
    edTiFileNameWin: TFileNameEdit;
    gbTiFilesButtons: TGroupBox;
    gbTiFilesHeader: TGroupBox;
    ImageListToolbar: TImageList;
    MemoTiFile: TMemo;
    MnAppendCol: TMenuItem;
    MnConfirmNr: TMenuItem;
    MnDeleteCell: TMenuItem;
    MnToRadix: TMenuItem;
    MnNoRadix: TMenuItem;
    MnDeleteLine: TMenuItem;
    MnInsertLine: TMenuItem;
    MnToggleNumStr: TMenuItem;
    PairSplitter1: TPairSplitter;
    PairSplitterSide1: TPairSplitterSide;
    PairSplitterSide2: TPairSplitterSide;
    PanelFileEditor: TPanel;
    DlgSaveTiFile: TSaveDialog;
    DirTreeView: TShellTreeView;
    PopupStrGrid: TPopupMenu;
    stFilename: TStaticText;
    stEOF: TStaticText;
    stRecSec: TStaticText;
    sgTiFile: TStringGrid;
    stTiName: TStaticText;
    stNumSec: TStaticText;
    stFileType: TStaticText;
    stRecordType: TStaticText;
    StatusBar1: TStatusBar;
    ToolBar1: TToolBar;
    tbEdit: TToolButton;
    procedure bbDSK1Click(Sender: TObject);
    procedure bbDSK1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure bbNewClick(Sender: TObject);
    procedure bbSaveClick(Sender: TObject);
    procedure cbProtectedChange(Sender: TObject);
    procedure DirTreeViewClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure FormShow(Sender: TObject);
    procedure MemoTiFileChange(Sender: TObject);
    procedure MnAppendColClick(Sender: TObject);
    procedure MnConfirmNrClick(Sender: TObject);
    procedure MnDeleteCellClick(Sender: TObject);
    procedure MnDeleteLineClick(Sender: TObject);
    procedure MnInsertLineClick(Sender: TObject);
    procedure MnNoRadixClick(Sender: TObject);
    procedure MnToggleNumStrClick(Sender: TObject);
    procedure MnToRadixClick(Sender: TObject);
    procedure PopupStrGridPopup(Sender: TObject);
    procedure sgTiFilePrepareCanvas(Sender: TObject; aCol, aRow: Integer;
      aState: TGridDrawState);
    procedure sgTiFileValidateEntry(Sender: TObject; aCol, aRow: Integer;
      const OldValue: string; var NewValue: String);
  private

  public

  end;

var
  FormTiFiles: TFormTiFiles;

implementation

{$R *.lfm}

{ TFormTiFiles }

uses math, LazUtf8,uTokenize,uDskShortcut,uMain,uPrefs,uRadix100;

Type
  TFlagBits = bitpacked record
    DataProg, DispInt, Res5, Protect, modified, Emul, Res1, fixvar : Boolean;
  end;

  TFlagByte = packed record
    case Integer of
      0: (ByteAccess: Byte);
      1: (BitAccess: TFlagBits);
  end;

  TTIFile = record case byte of
        1 : ( b   : Array[0..65535] of byte );
        2 : ( sec : Array[0..255,0..255] of byte );
            end;


var th : TTIHeader;
    th9: TV9T9Header;
    tf : TTIFile;
    fs : Longint;
    dirty  : boolean;
    currfn : shortstring; // current file
    sgc : array[0..16,1..512] of byte;
    MnCol, MnRow : Integer;

procedure LoadDV;
var i,j,l : integer;
    s : ansistring;
begin
  FormTiFiles.MemoTiFile.Lines.Clear;
  i:=0;
  while i<fs do begin
    l:=tf.b[i];
    if l=255 then begin // go to next sector
      i := ((i DIV 256)+1)*256
    end else begin
      s:='';
      for j:=1 to l do s:=s+char(tf.b[i+j]);
      inc(i,l+1);
      FormTiFiles.MemoTiFile.Append(WinCPToUTF8(s));
    end;
  end;
end;

procedure LoadDF;
var sc,r,i,j,l : integer;
      s : shortstring;
begin
  FormTiFiles.MemoTiFile.Lines.Clear;
  i:=0;
  s:='';
  l := StrToInt(trim(FormTiFiles.cbRecLen.Text));
  for sc:=0 to (fs DIV 256)+1 do begin  // for each Sector
    j:=0;
    for r:=0 to (256 DIV l)-1 do begin
      for i:=1 to l do begin
        s:=s+char(tf.sec[sc,j]);
        inc(j);
      end;
      FormTiFiles.MemoTiFile.Lines.Add(WinCPToUTF8(s));
      s:='';
    end;
  end;
end;

procedure LoadIV;
var i,j,k,l,p,r,c : integer;
    s : shortstring;
    rx : TRadix100;
begin
   FormTiFiles.sgTiFile.Clear;
   FormTiFiles.sgTiFile.ColCount:=2;
   FormTiFiles.sgTiFile.Cells[1,0]:='A';
   Initialize(sgc);
   p:=0;
   r:=1;
   while p<=fs do with FormTiFiles.sgTiFile do begin
     Cells[0,r]:=IntToStr(r);
     c:=1;
     i:=1;
     if tf.b[p]=$FF then begin
       inc(p);
       while (tf.b[p]=0) and (p<=fs) do inc(p);
     end;
     l:=tf.b[p]; // Length of record
     inc(p);
     while i<=l do begin // process record
       j:=tf.b[p]; // length of field
       s:='';
       inc(p);
       while length(s)<j do begin // process field
         s := s + Chr(tf.b[p]);
         inc(p);
       end;
       inc(i,j+1);
       if c>=ColCount then begin
          ColCount:=ColCount+1;
          Cells[c,0]:=chr(64+c);
       end;
       sgc[c,r]:=1;
       if length(s)=8 then begin   // convert Radix-100 floatingpoint number
          for k:=1 to 8 do rx[k-1] := ord(s[k]);
          if IsRadix100(rx) then begin
            s:=Radix100ToDisplay(rx);
            if IsPrintableRadix100(rx) then sgc[c,r]:=4
                                       else sgc[c,r]:=2
          end;
       end;
       if r>=RowCount-1 then RowCount:=RowCount+1;
       Cells[c,r]:=WinCPToUTF8(s);
       inc(c);
     end;
     inc(r);
   end;
end;

procedure LoadIF;
var k,l,p,r,c : integer;
    eor,reclen : integer;
    s : shortstring;
    rx : TRadix100;
begin
  FormTiFiles.sgTiFile.Clear;
  FormTiFiles.sgTiFile.ColCount:=2;
  FormTiFiles.sgTiFile.RowCount:=2;
  FormTiFiles.sgTiFile.Cells[1,0]:='A';
  p:=0;
  r:=0;
  reclen := StrToIntDef(FormTiFiles.cbRecLen.Text,80);
  eor := reclen; // end of record
  while p<=fs do with FormTiFiles.sgTiFile do begin
    c := 1;
    while p<eor do begin // process record
      l := tf.b[p];
      if l=0 then while (tf.b[p]=0) and (p<eor) do inc(p) // advance to next record
      else begin
        s:='';
        inc(p);
        while length(s)<l do begin // process field
          s := s + Chr(tf.b[p]);
          inc(p);
        end;
        if c>=ColCount then begin
            ColCount:=ColCount+1;
            Cells[c,0]:=chr(64+c);
        end;
        if c=1 then inc(r);
        sgc[c,r]:=1;
        if length(s)=8 then begin   // convert Radix-100 floatingpoint number
          for k:=1 to 8 do rx[k-1] := ord(s[k]);
          if IsRadix100(rx) then begin
             s:=Radix100ToDisplay(rx);
             if IsPrintableRadix100(rx) then sgc[c,r]:=4
                                        else sgc[c,r]:=2;
          end;
        end;
        if r>RowCount-1 then RowCount:=RowCount+1;
        Cells[0,r]:=IntToStr(r);
        Cells[c,r]:=WinCPToUTF8(s);
        inc(c);
      end;
    end;
    if ((p MOD 256) < reclen) and ((p MOD 256)<>0) then p := ((p DIV 256)+1)*256; // next sector
    eor := p + reclen;
  end;
end;


Procedure SaveDV;
var i,lb,l : Integer;
    s,t : ShortString;

begin
  Initialize(tf);
  l := StrToInt(trim(FormTiFiles.cbRecLen.Text));
  fs:=0;
  for t in FormTiFiles.MemoTiFile.Lines do begin
    s:=UTF8ToWinCP(t);
    lb:=min(byte(length(s)),l); // length-byte
    if (fs mod 256)+lb>=255 then begin // doesn't fit in current block
      tf.b[fs]:=255; // >FF Marker
      for i:=fs+1 to ((fs div 256)+1)*256-1 do tf.b[i]:=0;
      fs:=((fs div 256)+1)*256; // first byte of the new block
    end;
    tf.b[fs]:=lb;
    for i:=1 to lb do tf.b[fs+i]:=ord(s[i]);
    inc(fs,lb+1);
  end;
  tf.b[fs]:=255; // >FF Marker
end;

Procedure SaveDF;
var sc,r,i,l : Integer;
    s,t : ShortString;
begin
  Initialize(tf);
  sc:=0;
  r :=0;
  l := StrToInt(trim(FormTiFiles.cbRecLen.Text));
  for s in FormTiFiles.MemoTiFile.Lines do begin
    t := leftstr(UTF8ToWinCP(s),l);
    while length(t)<l do t:=t+' ';
    if l>256-r then begin // next Sector
      for i:=r to 255 do tf.sec[sc,i]:=0;
      r :=0;
      sc:=sc+1;
    end;
    for i:=0 to l-1 do tf.sec[sc,r+i]:=ord(t[i+1]);
    inc(r,l);
  end;
  fs:=sc*256+r;
end;


procedure SaveIV;
var i,lb,l,r,c,rl : Integer;
    s : ShortString;

begin
  Initialize(tf);
  rl := StrToInt(trim(FormTiFiles.cbRecLen.Text));
  fs:=0;
  for r:=1 to FormTiFiles.sgTiFile.RowCount-1 do with FormTiFiles.sgTiFile do begin
    lb:=0;
    for c:=1 to ColCount-1 do if sgc[c,r]>0 then
       if sgc[c,r]=1 then lb:=lb+1+length(Cells[c,r])
                     else lb:=lb+9;
    if (fs mod 256)+lb>=255 then begin // doesn't fit in current block
      tf.b[fs]:=255; // >FF Marker
      fs:=((fs div 256)+1)*256; // first byte of the new block
    end;
    if lb>rl then begin
       ShowMessage('Error: Record too long in line '+inttostr(r)+': '+IntToStr(lb)+' chars.');
       fs:=0;
       exit;
    end;
    if lb>0 then begin
      tf.b[fs]:=lb;
      inc(fs);
    end;
    for c:=1 to ColCount-1 do if sgc[c,r]<>0 then begin
      if sgc[c,r]=1
         then s:=UTF8ToWinCP(Cells[c,r])                           // string
         else s:=Radix100ToString(DisplayToRadix100(Cells[c,r]));  // radix-100 number
      l:=length(s);
      tf.b[fs]:=l;
      for i:=1 to l do tf.b[fs+i]:=ord(s[i]);
      inc(fs,l+1);
    end;
  end;
  if (fs MOD 256)>0 then tf.b[fs]:=255 // >FF Marker if not start of Sector
                    else dec(fs);      // don't start new Sector
end;


procedure SaveIF;
var i,lb,l,r,c,rl,nextfs : Integer;
    s : ShortString;
begin
  Initialize(tf);
  rl := StrToInt(trim(FormTiFiles.cbRecLen.Text));
  fs:=0;
  for r:=1 to FormTiFiles.sgTiFile.RowCount-1 do with FormTiFiles.sgTiFile do begin
    lb:=0;
    for c:=1 to ColCount-1 do if sgc[c,r]>0 then
       if sgc[c,r]=1 then lb:=lb+1+length(Cells[c,r])
                     else lb:=lb+9;
    if lb=0 then Continue;
    if (fs mod 256)+rl>256 then begin // doesn't fit in current block
      fs:=((fs div 256)+1)*256; // first byte of the new block
    end;
    nextfs := fs + rl;
    if lb>rl then begin
       ShowMessage('Error: Record too long in line '+inttostr(r)+': '+IntToStr(lb)+' chars.');
       fs:=0;
       exit;
    end;
    for c:=1 to ColCount-1 do if sgc[c,r]<>0 then begin
      if sgc[c,r]=1
         then s:=UTF8ToWinCP(Cells[c,r])                           // string
         else s:=Radix100ToString(DisplayToRadix100(Cells[c,r]));  // radix-100 number
      l:=length(s);
      tf.b[fs]:=l;
      for i:=1 to l do tf.b[fs+i]:=ord(s[i]);
      inc(fs,l+1);
    end;
    fs:=nextfs;
  end;

end;



Procedure LoadTiFile(fn:String);
var f:File of byte;
    i,rc : Integer;
    s : ShortString;
    flag : TFlagByte;
    zero : boolean;

begin
  if not fileexists(fn) then exit;
  zero:=true;
  AssignFile(f,fn);
  reset(f);
  BlockRead(f,th,128,rc);
  if rc=128 then with FormTiFiles do begin
    zero := true; // check if possible v9t9 header
    for i:=0 to 90 do if th.unused[i]<>0 then zero:=false;

    if th.id='TIFILES' then begin
      flag.ByteAccess := th.flags;
      s:=''; rc:=0;
      for i:=0 to 9 do s:=s+chr(th.fn[i]);
      EdTiFileName.Text:=s;
      cbRecLen.text := IntToStr(Th.RecL);
      EdEOF.Text    := IntToStr(Th.EOFoff);
      EdRecSec.text := IntToStr(th.rs);
      EdNumSec.text := IntToStr(Th.sechi*256+th.seclo);
      cbTiFilesV9T9.ItemIndex:=0;
    end else if zero then begin     // V9T9 handling ... re-read Header
      reset(f);
      BlockRead(f,th9,128,rc);
      flag.ByteAccess := th9.flags;
      s:=''; rc:=0;
      for i:=0 to 9 do s:=s+chr(th9.fn[i]);
      EdTiFileName.Text:=s;
      cbRecLen.text := IntToStr(Th9.RecL);
      EdEOF.Text    := IntToStr(Th9.EOFoff);
      EdRecSec.text := IntToStr(th9.rs);
      EdNumSec.text := IntToStr(Th9.sechi*256+th9.seclo);
      cbTiFilesV9T9.ItemIndex:=1;
    end else begin
      ShowMessage('Error: '+fn+' has no TIFILES or V9T9 header.');
      CloseFile(f);
      Exit;
    end;

    edTiFileNameWin.Text:=SetDirSeparators(fn);

    if flag.BitAccess.fixvar then cbTiFilesOrg.ItemIndex:=1  // Variable
                             else cbTiFilesOrg.ItemIndex:=0; // Fixed
    cbProtected.Checked:=flag.BitAccess.Protect;
    if flag.BitAccess.DispInt then cbTiFileType.ItemIndex:=1  // Internal
                              else cbTiFileType.ItemIndex:=0; // Display
    if flag.BitAccess.DataProg then begin
      ShowMessage('Error: '+fn+' is a program file');
      CloseFile(f);
      Exit;
    end;
    Initialize(tf);
    BlockRead(f,tf,65536,fs);
  end else ShowMessage('Error: Unknown file format.');
  close(f);
  if flag.BitAccess.DispInt then begin // INTERNAL
    FormTiFiles.MemoTiFile.Visible:=false;
    FormTiFiles.sgTiFile.Visible:=true;
    // ShowMessage('Error: Internal format not supported yet.');
    FormTiFiles.sgTiFile.BeginUpdate;
    if flag.BitAccess.fixvar     then LoadIV;
    if not flag.BitAccess.fixvar then LoadIF;
    FormTiFiles.sgTiFile.AutoSizeColumns;
    FormTiFiles.sgTiFile.EndUpdate();
  end else begin // DISPLAY
    FormTiFiles.MemoTiFile.Visible:=true;
    FormTiFiles.sgTiFile.Visible:=false;
    FormTiFiles.MemoTiFile.Lines.BeginUpdate;
    if flag.BitAccess.fixvar     then LoadDV;
    if not flag.BitAccess.fixvar then LoadDF;
    FormTiFiles.MemoTiFile.Lines.EndUpdate;
  end;
end;


procedure SaveTiFile;
var f:File of byte;
    i,rc,r,c : Integer;
    s : ShortString;
    flag : TFlagByte;
begin
  with FormTiFiles do begin
    flag.ByteAccess := 0;
    if cbTiFilesOrg.ItemIndex=1 then flag.BitAccess.fixvar :=true   // Variable
                                else flag.BitAccess.fixvar :=false; // Fixed
    if cbTiFileType.ItemIndex=1 then flag.BitAccess.DispInt:=true   // Internal
                                else flag.BitAccess.DispInt:=false; // Display
    if cbProtected.Checked then flag.BitAccess.Protect:=true;
    flag.BitAccess.DataProg:=false; // data
    flag.BitAccess.Emul:=false;

    // check for errors in "internal"
    if flag.BitAccess.DispInt then begin
      rc := 0;
      for r:=1 to sgTiFile.RowCount-1 do
        for c:=1 to 16 do if sgc[c,r]=3 then inc(rc);
      if rc>0 then begin
        ShowMessage('Can''t save with errors. Check '+inttostr(rc)+' red entries.');
        exit;
      end;
    end;

    // initialize the file array
    for i:=0 to 65535 do tf.b[i]:=0;
    fs:=0;

    if (cbTiFileType.ItemIndex=0) and (cbTiFilesOrg.ItemIndex=1) then SaveDV;
    if (cbTiFileType.ItemIndex=0) and (cbTiFilesOrg.ItemIndex=0) then SaveDF;
    if (cbTiFileType.ItemIndex=1) and (cbTiFilesOrg.ItemIndex=1) then SaveIV;
    if (cbTiFileType.ItemIndex=1) and (cbTiFilesOrg.ItemIndex=0) then SaveIF;

    if fs=0 then exit; // conversion was not successful

    th.flags:=flag.ByteAccess;
    s:=trim(EdTiFileName.text); rc:=0;
    for i:=0 to 9 do if i<length(s) then th.fn[i]:=byte(s[i+1])
                                    else th.fn[i]:=0;
    th.RecL:=StrToIntDef(cbRecLen.text,80);
    th.sechi:=(fs DIV 65536); // always zero
    th.seclo:=(fs DIV 256);   // number of sectors
    if (fs MOD 256) > 0 then inc(th.seclo);
    if cbTiFilesOrg.ItemIndex=1 then begin
       th.EOFoff:=(fs MOD 256);
       th.rs:=(254 DIV th.RecL);  // length and >FF flag
    end else begin
       th.EOFoff:=0;
       th.rs:=(256 DIV th.RecL);
    end;

    if cbTiFilesV9T9.ItemIndex=1 then begin // fill V9T9 Header
       Initialize(th9);
       th9.flags := th.flags;
       th9.fn    := th.fn;
       th9.RecL  := th.RecL;
       th9.sechi := th.sechi;
       th9.seclo := th.seclo;
       th9.EOFoff:=th.EOFoff;
       th9.rs    :=th.rs;
       for i:=0 to 9 do if th9.fn[i]=0 then th9.fn[i]:=32;
    end;

    currfn := trim(edTiFileNameWin.text);
    if FileExists(currfn) then begin
      DeleteFile(currfn+'.bak');
      RenameFile(currfn,currfn+'.bak');
    end;
    system.assign(f,currfn);
    rewrite(f);
    if cbTiFilesV9T9.ItemIndex=0 then blockwrite(f,th,128)
                                 else blockwrite(f,th9,128);
    blockwrite(f,tf,256*((fs DIV 256)+1),rc);
    system.close(f);
  end; // with FormTiFiles
end;

procedure UpdateDskLabels;
begin
  FormTiFiles.bbDSK1.Caption:=DataDSK[1].lbl;
  FormTiFiles.bbDSK2.Caption:=DataDSK[2].lbl;
  FormTiFiles.bbDSK3.Caption:=DataDSK[3].lbl;
  FormTiFiles.bbDSK4.Caption:=DataDSK[4].lbl;
end;

procedure TFormTiFiles.bbSaveClick(Sender: TObject);
begin
  SaveTiFile;
  dirty:=false;
end;

procedure TFormTiFiles.bbDSK1Click(Sender: TObject);
var dir : string;
begin
  dir := DataDSK[TBitBtn(sender).Tag].pth;
  if dir='' then bbDSK1MouseDown(sender,mbright,[],0,0)
            else DirTreeView.Path:=dir;
end;

procedure TFormTiFiles.bbDSK1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var bnr:integer;
begin
  if Button in [mbRight] then begin
    bnr := TBitBtn(sender).Tag;
    FormDSKx.edLabel.Text:=DataDsk[bnr].lbl;
    FormDSKx.edDir.Text:=DataDsk[bnr].pth;
    if FormDSKx.showmodal=mrOK then begin
      DataDsk[bnr].lbl := FormDSKx.edLabel.Text;
      DataDsk[bnr].pth := FormDSKx.edDir.Text;
      UpdateDskLabels;
    end;
  end;
end;

procedure TFormTiFiles.bbNewClick(Sender: TObject);
var mr : TModalResult;
    i  : Integer;
begin
  mr := MessageDlg('Create new File','Do you want to use the INTERNAL format?',mtConfirmation,[mbYes,mbNo,mbCancel],0);
  if mr=mrYes then begin
    cbTiFileType.ItemIndex:=1;  // Internal
    FormTiFiles.MemoTiFile.Visible:=false;
    FormTiFiles.sgTiFile.Visible:=true;
    FormTiFiles.sgTiFile.Clear;
    Initialize(sgc);
    FormTiFiles.sgTiFile.ColCount:=3;
    FormTiFiles.sgTiFile.RowCount:=5;
    for i:=1 to 4 do sgTiFile.Cells[0,i]:=IntToStr(i);
    sgTiFile.Cells[1,0]:='A';
    sgTiFile.Cells[2,0]:='B';
  end;
  if mr=mrNo then begin
    cbTiFileType.ItemIndex:=0; // Display
    FormTiFiles.MemoTiFile.Visible:=true;
    FormTiFiles.sgTiFile.Visible:=false;
    FormTiFiles.sgTiFile.Clear;
  end;
  if mr<>mrCancel then begin
    EdTiFileName.Text:='NEWFILE';
    cbTiFilesOrg.ItemIndex:=1;
    cbRecLen.text := '80';
    cbTiFilesV9T9.ItemIndex:=0;
    cbProtected.Checked:=false;
    edTiFileNameWin.InitialDir := ExtractFileDir(SetDirSeparators(DirTreeView.Selected.GetTextPath));
    edTiFileNameWin.RunDialog;
  end;
end;

procedure TFormTiFiles.cbProtectedChange(Sender: TObject);
begin
  MemoTiFile.ReadOnly:=cbProtected.Checked;
end;


procedure TFormTiFiles.DirTreeViewClick(Sender: TObject);
var s:string;
begin
  s := DirTreeView.Selected.GetTextPath;
  if DirectoryExists(s) then exit;
  if dirty then begin
    if MessageDlg('File changed','Do you want to save your changes?',mtConfirmation,[mbYes,mbNo],0)=mrYes then
       bbSaveClick(nil);
  end;
  LoadTiFile(s);
  currfn := s;
  edTiFileNameWin.Text:=SetDirSeparators(s);
  dirty  :=false;
end;

procedure TFormTiFiles.FormClose(Sender: TObject; var CloseAction: TCloseAction
  );
begin
    DataDsk[0].pth := SetDirSeparators(DirTreeView.Selected.GetTextPath);
    DataDsk[0].lbl := 'LastDir';
    if dirty then begin
      if MessageDlg('File changed','Do you want to save your changes?',mtConfirmation,[mbYes,mbNo],0)=mrYes then
        bbSaveClick(nil);
    end;
end;

procedure TFormTiFiles.FormShow(Sender: TObject);
var i : integer;
begin
  Dirty := false;
  MemoTiFile.Font.Size:=FontSize;
  sgTiFile.Font.Size:=FontSize;
  sgTiFile.DefaultRowHeight:=FontSize*2+4;
  for i:=1 to sgTiFile.ColCount-1 do sgTiFile.Cells[i,0]:=chr(i+64);
  UpdateDskLabels;
  DirTreeView.Path:= DataDsk[0].pth;
end;

procedure TFormTiFiles.MemoTiFileChange(Sender: TObject);
begin
  dirty:=true;
end;

procedure TFormTiFiles.MnAppendColClick(Sender: TObject);
begin
  sgTiFile.ColCount:=sgTiFile.ColCount+1;
  sgTiFile.Cells[MnCol+1,0]:=chr(65+MnCol);
end;

procedure TFormTiFiles.MnConfirmNrClick(Sender: TObject);
begin
  sgc[MnCol,MnRow] := 2;
  sgTiFile.Cells[MnCol,MnRow] := sgTiFile.Cells[MnCol,MnRow];
end;

procedure TFormTiFiles.MnDeleteCellClick(Sender: TObject);
begin
  sgTiFile.Cells[MnCol,MnRow] := '';
  sgc[MnCol,MnRow] := 0;
end;

procedure TFormTiFiles.MnDeleteLineClick(Sender: TObject);
var r,c : Integer;
begin
  sgTiFile.BeginUpdate;
  sgTiFile.DeleteRow(MnRow);
  for r:=MnRow to sgTiFile.RowCount-1 do begin
    for c:=1 to 16 do sgc[c,r]:=sgc[c,r+1];
    sgTiFile.Cells[0,r]:=IntToStr(r);
  end;
  sgTiFile.EndUpdate;
end;

procedure TFormTiFiles.MnInsertLineClick(Sender: TObject);
var r,c : Integer;
begin
  sgTiFile.BeginUpdate;
  sgTiFile.InsertRowWithValues(MnRow,[IntToStr(MnRow)]);
  for r:=sgTiFile.RowCount-1 downto MnRow+1 do begin
    for c:=1 to 16 do sgc[c,r]:=sgc[c,r-1];
    sgTiFile.Cells[0,r]:=IntToStr(r);
  end;
  for c:=1 to 16 do sgc[c,MnRow]:=0;
  sgTiFile.EndUpdate;
end;


procedure TFormTiFiles.MnNoRadixClick(Sender: TObject);
var s : ShortString;
    r : TRadix100;
    i : integer;
begin
  s := '';
  r := DisplayToRadix100(sgTiFile.Cells[MnCol,MnRow]);
  for i:=0 to 7 do s := s + chr(r[i]);
  sgTiFile.Cells[MnCol,MnRow] := s;
  sgc[MnCol,MnRow]:=1;
end;


procedure TFormTiFiles.MnToggleNumStrClick(Sender: TObject);
var rc : integer;
    s : string;
    v : Extended;
begin
   s := sgTiFile.Cells[MnCol,MnRow];
  if (MnCol=0) or (MnRow=0) then exit;
  if (sgc[MnCol,MnRow]=2) or (sgc[MnCol,MnRow]=3) then sgc[MnCol,MnRow]:=1
  else if (sgc[MnCol,MnRow]=4) then begin
      sgc[MnCol,MnRow]:=1;
      sgTiFile.Cells[MnCol,MnRow]:=Radix100ToString(DisplayToRadix100(sgTiFile.Cells[MnCol,MnRow]));
    end else begin
    sgc[MnCol,MnRow]:=3;  // invalid number ... except it can be converted to a number
    val(trim(s),v,rc);
    if rc=0 then sgc[MnCol,MnRow]:=2 else
      if (length(s)=8) and IsRadix100(s) then begin
        sgc[MnCol,MnRow]:=2;
        sgTiFile.Cells[MnCol,MnRow] := Radix100ToDisplay(StringToRadix100(s));
      end;
  end;
  Invalidate;
end;

procedure TFormTiFiles.MnToRadixClick(Sender: TObject);
var s : ShortString;
begin
  s:=sgTiFile.Cells[MnCol,MnRow];
  if IsRadix100(s) then begin
    sgTiFile.Cells[MnCol,MnRow] := Radix100ToDisplay(StringToRadix100(s));
    sgc[MnCol,MnRow]:=2;
  end;
end;


procedure TFormTiFiles.PopupStrGridPopup(Sender: TObject);
var  p : TPoint;
begin
  p:=sgTiFile.ScreenToClient(Mouse.CursorPos);
  sgTiFile.MouseToCell(p.x,p.y,MnCol, MnRow);
  if (MnCol=0) or (MnRow=0) then exit;
  if ((sgc[MnCol,MnRow]=2) or (sgc[MnCol,MnRow]=4))
      and (IsPrintableRadix100(DisplayToRadix100(sgTiFile.Cells[MnCol,MnRow])))
                          then MnNoRadix.Visible:=true
                          else MnNoRadix.Visible:=false;
  if (sgc[MnCol,MnRow]=1) and (length(sgTiFile.Cells[MnCol,MnRow])=8)
                          then MnToRadix.Visible:=true
                          else MnToRadix.Visible:=false;
  if (sgc[MnCol,MnRow]=4) then MnConfirmNr.Visible:=true
                          else MnConfirmNr.Visible:=false;
  if MnCol=sgTiFile.ColCount-1
                          then MnAppendCol.Visible:=true
                          else MnAppendCol.Visible:=false;
end;


procedure TFormTiFiles.sgTiFilePrepareCanvas(Sender: TObject; aCol,
  aRow: Integer; aState: TGridDrawState);
begin
   if (aCol=0) or (aCol>16) or (aRow=0) or (aRow>=512) then begin
     sgTiFile.Font.Color:=clBlack;
     exit;
   end;

   if sgc[aCol,aRow]=0 then sgTiFile.Canvas.Brush.Color := $E0E0E0  //clBtnFace
                       else if odd(aRow) then sgTiFile.Canvas.Brush.Color :=clWhite
                                         else sgTiFile.Canvas.Brush.Color := $B5FFFF; //clGradientInactiveCaption;

   case sgc[aCol,aRow] of
      0 : sgTiFile.Font.Color:=clGray;
      1 : sgTiFile.Font.Color:=clBlack;
      2 : sgTiFile.Font.Color:=clBlue;
      3 : sgTiFile.Font.Color:=clRed;
      4 : sgTiFile.Font.Color:=clPurple;
     else sgTiFile.Font.Color:=clBlack;
   end;
end;


procedure TFormTiFiles.sgTiFileValidateEntry(Sender: TObject; aCol,
  aRow: Integer; const OldValue: string; var NewValue: String);
var rc : integer;
    v : Extended;
begin
  if (OldValue=NewValue) then exit;
  val(trim(newvalue),v,rc);
  if rc=0 then sgc[aCol,aRow]:=2
          else if sgc[aCol,aRow]>1 then sgc[aCol,aRow]:=3 // if it was a number then error
                                   else sgc[aCol,aRow]:=1;
  dirty := true;
end;

end.

