unit TI_ImageMgr;

//* ***********************************************************************
//* *                                                                     *
//* *  This is a rewrite/refresh of my TI project. I have added the       *
//* *  ability to read/print FORTH screens, and printed disk catalogs     *
//* *  in a format appropriate for diskette labels.                       *
//* *                                                                     *
//* *  I have also included CF7a+ formatted CF image/disk reads (and      *
//* *  maybe I'll add writes, volume mounting and the like.)              *
//* *                                                                     *
//* *  I will continue to eschew the PC99 emulated diskette format, just  *
//* *  because I don't use it.                                            *
//* *                                                                     *
//* *  I have 'fixed' the pointer error which used to come up             *
//* *  when the program closed if you you had read a larger (1440 sector  *
//* *  and up) diskette image. I have changes the way I size the          *
//* *  buffer size for the sector bitmap, and that seems to have done the *
//* *  trick.                                                             *
//* *                                                                     *
//* *  I'm not sure that the bitmap display code works correctly now,     *
//* *  but at least the pointer errors seem to have been defeated.        *
//* *                                                                     *
//* *  All char arrays have been converted to byte arrays for             *
//* *  compatibility with Alexander Grau's raw device access code, and    *
//* *  all RX components, and many others, have been replaced/updated     *
//* *  with JEDI components.                                              *
//* *                                                                     *
//* *                    Spring-Summer 2010                               *
//* *                                                                     *
//* ***********************************************************************
//* *                                                                     *
//* *   Original comment:                                                 *
//* *                                                                     *
//* *   TI Diskette Image/File Reader - Reformatter                       *
//* *   RGM, The summer of Nostalgia, 2003                                *
//* *                                                                     *
//* *   This program will handle files that have been transferred to the  *
//* *   PC using XMODEM or any other protocol which provides a TIFILES    *
//* *   header. It will also read a V9T9 diskette image and display       *
//* *   individual files, and extract them constructing an appropriate    *
//* *   TIHEADERs.                                                        *
//* *                                                                     *
//* *   Formatting of TI-Writer and 'companion' files is rudimentary, but *
//* *   does clean ip formatting codes and leave a clean block of text,   *
//* *   line-oriented in the case of TI Writer and paragraph-oriented     *
//* *   (a-la unwrapped notepad texts) in the case of 'companion.'        *
//* *   Formatted output can be saved as plain text or as an RFT.         *
//* *                                                                     *
//* ***********************************************************************

{
  TIFILES RECORD:
    BYTE - Description
    -----------------------------
      00 - TIFILES Header byte. 07 is standard; 08 used by MXT for Ymodem only
      01- 07  "TIFILES", ASCII characters
      08 FileSectors, Word  (number of sectors allocated to the file)
      0A StatusFlag, Byte,
      0B Records per Sector, Byte
      0C BytesLastSector, Byte (End of file offset)
      0D LogicalRecordLen, Byte
      0E Rec/Sector Used, Word
      10 Filename, Asci; 10 bytes padded with space 20
      1A MXTFlag, byte (extension to standard; 0=last file, 0 more to come)
      1B reserved.
      1C Extended Header Flag ( FFFF=port extended header; FFFF,ignored by PORT)
      1E Creation Date/time (4 bytes, compressed)
      22 Update Date/time (4 bytes, compressed)
      26-end - padded with 2020. See info item #1
      60 - likely start of MYTERM extended record

    ADDITIONAL INFORMATION BLOCK
    ---------------------------------------
    The TIFILES information is extracted from the Additional Info block, as described
    in the Myarc HFDC manual and other publications.  Archiver uses the same 8 bytes
    to store file info for re-creation.

    Standard TIFILES Header does NOT transmit the extended record length, the MSB
    level 3 record length, or the MSB of the 1st sector. These extensions were added to
    the HFDC DSR for very large files or record sizes greater than 256 bytes.
    Emulators must, however, clear these bytes upon incoming file creation.  When
    used with the Geneve, additional care must be taken to clear "PAB" bytes related
    to time/date stamping.

    (Note: I do not recall whether or not EPROM H11 provides for date/ time stamping
    when creating a file using direct ouput opcodes. The Geneve supports time/date
    natively)

    STATUS FLAG, Word
    -------------------------
    As described in the HFDC manual, the STATUS flag determines the type of file and
    its attributes.  The bits are set as follows:

    BIT - Description
    -------------------
    7  0=fixed; 1=variable
    6  reserved
    5  Emulate File (1=emulate file; 0=not emulate file)
    4  Backup Flag (1=modified; 0-not modified).  BackupBit remover,
       Port,Arc4 eliminate this bit for RAmdisk compatibility.
    3  0=unprotected, 1=protected
    2  reserved
    1  0=display, 1=internal/program
    0  0=date, 1=program

    * EMulate file status flag is 21
    ** reserved bits should be masked for compatibility
    *** reserved bits are not defined in any known documentation.

    Additional file types used when cataloging HFDC disk:
    --File type 6 corresponds to a subdirectory entry
    --File type 7 corresponds to an emulate file

    EMULATE FILES
    ---------------------------------------------
    Emulate files are stored on disk as a file with attributes
    similar to that of a program file.  An emulate file
    is comprised of 256-byte sectors, in a format identical
    to that of a floppy disk.

    The DSR creates a file Descriptor Record for the emulate
    file which it then uses to track the location of each virtual
    disk sector.

    Emulate file sector 0 and its FDR are offsets inside the file.
    If the Emulate file resided on sectors 100 to 400, consecutively
    and contiguously, and you wanted sector 30 on the emulated disk,
    requested physical sector = 30 (logical sector on emulated disk)
    + 100 (starting physical sector on the hard drive) = 130

    IF not contiguous, the DSR locates the proper cluster and calculates
    accordingly.  I like to think of emulate files as files containing
    "x" number of 256 byte records - the emulate disk sector then  "looks"
    like an index into the file based on record number.
    A Myarc emulate file is equivalent to a MESS/V9T9 image.
    However, if the terminal program transfers the TIFILES
    header, a program must strip the header prior to using the
    image.  No further modification is required.

    Maximum emulate sector size:
    HFDC w/TI:  3200 sectors
    HFDC w/Geneve: 12800 sectors

    The floppy disk standard allows for a maximum of 1600 sectors based on
    the allocation bitmap stored in sector 0.  To accomplish more than 1600 on
    a disk, the DSR represents a sector or multiple sectors as an Allocation Unit,
    and calculates sectors per AU as follows:

    0-1599 sectors - 1 sector/AU (400MB Max)
    1600-3199 sectors - 2 sectors/AU  (800MB Max, "quad density")
    The following calculations are present in the MDOS DSR (thanks
    to Jim Schroeder) but are not present in any existing TI DSRs, HFDC
    included:
    3200-6399 sectors - 4 sectors/AU  (1.6MB Max, "high density")
    6400-12799 sectors - 8 sectors/AU  (3.2MB max, "ultra-high density")

    For this reason, larger capacity disks will consume more sectors per
    file than would be consumed on a lesser capacity disk.
    Floppy format DSR restrictions:
    TI,Corcomp,Myarc, FDCs cannot exceed 2 sectors/AU
    Horizon RAMdisks are limited to (400K, 1600 sectors - 1 sector/AU)
    Horizon RAMdisks w/Geneve are limited to 12,800 sectors, 8 sectors/AU (via FORM3MEG)
    HFDC TI DSR calculations cannot exceed 2 sectors/AU.
    Geneve DSR calculations cannot exceed 8 sectors/AU.

    Note:
    "Quad Density" is a misnomer applied to the use of
    80 tracks in order to double disk capacity.  The
    sector density per track does not increase between
    double and quad density formats as the term suggests;
    instead, track density is doubled from 40 to 80.
    ** EOF
    }

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ComCtrls, JvExComCtrls, JvToolBar, ExtCtrls, JvExExtCtrls,
  JvControlBar, ToolWin, JvCoolBar, JvStatusBar, LMDInformationLabel,
  LMDBaseControl, LMDBaseGraphicControl, LMDBaseLabel, LMDCustomSimpleLabel,
  LMDSimpleLabel, LMDControl, LMDCustomControl, LMDCustomPanel,
  LMDCustomBevelPanel, LMDCustomStatusBar, LMDStatusBar, VersInfo, JvAppStorage,
  JvAppIniStorage, JvComponentBase, JvFormPlacement, FABAbout, ImgList,
  ActnList, StdActns, ExtActns, PlatformDefaultStyleActnCtrls, ActnMan,
  StdCtrls, Mask, JvExMask, JvSpin, JvExStdCtrls, JvCombobox, JvColorCombo, Spin,
  FileCtrl, JvDriveCtrls, JvListBox, JvSplitter, JvExtComponent, JvPanel,
  JvComCtrls, BorBtns, Grids, LMDCustomScrollBox, LMDListBox, SynEdit,
  Printers, JvMemo, NiceGrid, Numedit, int13ext, JvHeaderControl, JvCheckBox,
  JvButton, JvCtrls, ShellAPI, LMDCustomComponent, LMDHookComponent, LMDFMDrop;

type
  TTIFilesHeader = Record
    HeaderString: string[7]; //* bytes 1 - 7
    Sectors: integer;        //* bytes 8 - 9
    StatusFlag: integer;     //* byte 11
    FileType: string;        //* interpreted from Byte 10 and RecSize (Byte 13)
    ProgType: boolean;       //* IMAGE (Program) if TRUE
    DataSize: string[3];     //* VAR or FIX
    DataType: string[3];     //* DISplay or INTernal (7 or 8 bit)
    ProtectBit : boolean;    //* Protected if TRUE
    RecordsPerSec: integer;  //* byte 11
    EOFOffset: integer;      //* byte 12
    RecSize: integer;        //* byte 13
    RecCount: integer;       //* bytes 14-15 in theory but really only byte 14?
                             //*    FIX files repeat SECTOR count
                             //*    VAR Files it is probably the true record count
                             //*      possibly with bytes 15 and 14 reversed for
                             //*      counts > 255
    end;

  TTISector = Record
    EndOffset: integer;                   //*recorded but never used?
    Contents: array[0..255] of byte;
    end;

  TTISegmentGroup = Record         //* This is a single entry in the segmet list
    Cluster: array[0..2] of byte;  //*  identifying the starting segment and the

    StartSeg: integer;             //*  segment counter in nybbles >AB >CD >EF

    EndOffset: integer;            //*  where >DAB is the sector number and

    end;                           //*        >EFC is the "number of sectors -1

                                   //*  in this segment added to the number of

                                   //*  sectors in all previous segments" per

                                   //*  DiskFixer. So, if this number is 20 and

                                   //*  the number in the previous entry was 18,

                                   //*  there are 2 sectors in this segment.


  TTISegmentList = array[0..74] of TTISegmentGroup;   //*just set at max count

  TTIFileDescriptorRecord = Record
    Address: integer;               //* sector number
    Filename: string[10];           //* bytes 0 - 9
    StatusFlag: integer;            //* byte 12
    RecordsPerSector: integer;      //* byte 13
    SectorCount: integer;           //* bytes 14-15
    LastByteInLastSector: integer;  //* byte 16
    Recordlength: integer;          //* byte 17
    NumberRecordsSectors: integer;  //* bytes 18 and 19 but swapped!
    SegmentList: TTISegmentList;
    end;

  TTIDiskInformation =  Record
    Diskname: string[10];       //* bytes 0 - 9
    SectorCount: integer;       //* bytes 10 and 11
    SectorsPerTrack: integer;   //* byte 12
    DSRMark: string[3];         //* bytes 13 - 15
    DiskProtected: boolean;     //* byte 16
    CFVolume: boolean;
    TracksPerSide: integer;     //* byte 17
    Sides: integer;             //* byte 18
    Density: integer;           //* byte 19
    Bitmap: array of byte;      //* starts at byte $38
    FileCount: integer;         //* # of FDR pointers in sec #1
    end;

  TTIDisk = Record
    PCName: TFilename;
    Info: TTIDiskInformation;
    SectorList: array of TTISector;
    SectorsRead: boolean;
    Directory: array of TTIFileDescriptorRecord;
    end;

  TCFVolume = Record
    VolumeNum: integer;
    Diskname: string[10];
    DSRMark: string[3];         //* bytes 13 - 15
    //SectorCount: integer;
    //SectorsPerTrack: integer;
    //DiskProtected: boolean;
    //TracksPerSide: integer;
    Sides: integer;
    Density: integer;
    end;

  TTIFile = Record              //* same format for extract or stand-alone
    Name: string;
    PCName: TFilename;
    Header: TTIFilesHeader;
    SectorList: array of TTISector;
    StandAlone: boolean;
    SectorsRead: boolean;
    BasicFlag: boolean;
    CompanionFlag: boolean;
    TIWriterFlag: boolean;
    FormattedFlag: boolean;
    end;

  TfrmMain = class(TForm)
    JvCoolBar1: TJvCoolBar;
    LMDStatusBar2: TLMDStatusBar;
    lblMessageRed: TLMDSimpleLabel;
    lblMessage: TLMDSimpleLabel;
    statMain: TLMDStatusBar;
    statMainDateTime: TLMDInformationLabel;
    statMainVariable: TLMDInformationLabel;
    lblBuild: TLMDInformationLabel;
    actionMain: TActionManager;
    actTIImageOpen: TFileOpen;
    actSaveAs: TFileSaveAs;
    actExit: TFileExit;
    actPrint: TPrintDlg;
    actAbout: TAction;
    Images: TImageList;
    JvFormStorage: TJvFormStorage;
    JvAppIniFileStorage: TJvAppIniFileStorage;
    cbFonts: TJvFontComboBox;
    cbarMain: TJvControlBar;
    JvToolBar1: TJvToolBar;
    ToolButton1: TToolButton;
    ToolButton2: TToolButton;
    spinFontSize: TSpinEdit;
    JvToolBar2: TJvToolBar;
    pnlBottom: TPanel;
    pnlTopRight: TPanel;
    JvToolBar3: TJvToolBar;
    ToolButton6: TToolButton;
    ToolButton7: TToolButton;
    ToolButton8: TToolButton;
    ToolButton9: TToolButton;
    cbDrives: TJvDriveCombo;
    lbDirs: TJvDirectoryListBox;
    lbFiles: TJvFileListBox;
    pnlTopLeft: TPanel;
    pagesTI: TJvPageControl;
    tabImage: TTabSheet;
    JvSplitter1: TJvSplitter;
    pnlImageTop: TJvPanel;
    pnlImageBottom: TJvPanel;
    JvCoolBar2: TJvCoolBar;
    JvControlBar1: TJvControlBar;
    JvToolBar4: TJvToolBar;
    ToolButton10: TToolButton;
    ToolButton11: TToolButton;
    ToolButton12: TToolButton;
    ToolButton13: TToolButton;
    tabFile: TTabSheet;
    tabSector: TTabSheet;
    tabFormatted: TTabSheet;
    JvCoolBar3: TJvCoolBar;
    JvControlBar2: TJvControlBar;
    JvToolBar7: TJvToolBar;
    ToolButton15: TToolButton;
    JvTabDefaultPainter: TJvTabDefaultPainter;
    ToolButton20: TToolButton;
    ToolButton21: TToolButton;
    ToolButton22: TToolButton;
    ToolButton23: TToolButton;
    ToolButton24: TToolButton;
    gridDiskBitMap: TDrawGrid;
    edDiskName: TEdit;
    ckProtected: TBorCheck;
    ckFORTHDisk: TBorCheck;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    Label6: TLabel;
    Label7: TLabel;
    Label8: TLabel;
    lbCatalog: TLMDListBox;
    gridSectors: TNiceGrid;
    memForth: TSynEdit;
    lblTIFileName: TLabel;
    ckTIFILESHeader: TCheckBox;
    gridHeader: TStringGrid;
    rgProtected: TRadioGroup;
    rgProgram: TRadioGroup;
    edTISectorCount: TNumberEdit;
    edTISectorCountHex: TEdit;
    edEOFOffsetHex: TEdit;
    edEOFOffset: TNumberEdit;
    Label9: TLabel;
    lblSectors: TLabel;
    edRecPerSectorHex: TEdit;
    edRecCountHex: TEdit;
    edRecSizeHex: TEdit;
    edRecSize: TNumberEdit;
    edRecCount: TNumberEdit;
    edRecPerSector: TNumberEdit;
    lblRecordsPerSector: TLabel;
    lblVarRecordCount: TLabel;
    lblRecordSize: TLabel;
    rgDataSize: TRadioGroup;
    rgDataType: TRadioGroup;
    ckBASIC: TCheckBox;
    ckCompanion: TCheckBox;
    ckTIWriter: TCheckBox;
    lblTIFileType: TLabel;
    Label13: TLabel;
    edSectorNumHex: TEdit;
    edSectorNum: TNumberEdit;
    spinSector: TSpinButton;
    Label14: TLabel;
    edByteNumHex: TEdit;
    edByteNum: TNumberEdit;
    Label15: TLabel;
    edContent: TNumberEdit;
    edContentchar: TEdit;
    edSectorCount: TNumberEdit;
    edTracksPerSide: TNumberEdit;
    edSides: TNumberEdit;
    edDensity: TNumberEdit;
    edSectorsPerTrack: TNumberEdit;
    actTIFileOpen: TFileOpen;
    actFormatCompanion: TAction;
    memFormatted: TRichEdit;
    actFormatDV80: TAction;
    actFormatBASIC: TAction;
    actFormatFORTH: TAction;
    actNarrowList: TAction;
    actExtractFile: TAction;
    SaveDialog: TSaveDialog;
    actDisplayFile: TAction;
    actFormatFont: TAction;
    FontDialog: TFontDialog;
    tabCF7: TTabSheet;
    pnlSectorGrid: TJvPanel;
    pnlRemovableDrives: TPanel;
    listRDrives: TJvListBox;
    edCFVolume: TNumberEdit;
    spinVolume: TSpinButton;
    lbVolumes: TLMDListBox;
    memRawText: TJvMemo;
    pnlRaw: TJvPanel;
    JvHeaderControl1: TJvHeaderControl;
    actGotoCFVolume: TAction;
    ckCFSector: TJvCheckBox;
    btnGotoSector: TJvImgBtn;
    ckCFVolume: TBorCheck;
    btnGotoCFSector: TJvImgBtn;
    actGotoTISector: TAction;
    gridCFSectors: TNiceGrid;
    memCFRaw: TJvMemo;
    ToolButton3: TToolButton;
    ToolButton14: TToolButton;
    edCFSector: TNumberEdit;
    spinCFSector: TSpinButton;
    cbFormattedFont: TJvFontComboBox;
    spinFormatted: TSpinEdit;
    HelpContents: THelpContents;
    ToolButton16: TToolButton;
    ckTrueCount: TJvCheckBox;
    ckShowDirTree: TJvCheckBox;
    actCFtoPC: TAction;
    actPCtoCF: TAction;
    coolbarCF7: TJvCoolBar;
    controlbarCF7: TJvControlBar;
    toolbarCF7: TJvToolBar;
    ToolButton17: TToolButton;
    ToolButton18: TToolButton;
    ToolButton25: TToolButton;
    actFormatCFVol: TAction;
    actFormatAllVols: TAction;
    ToolButton26: TToolButton;
    ToolButton27: TToolButton;
    ToolButton28: TToolButton;
    ToolButton29: TToolButton;
    ToolButton30: TToolButton;
    ToolButton31: TToolButton;
    ToolButton4: TToolButton;
    dlgOpenImage: TOpenDialog;
    dlgSaveImage: TSaveDialog;
    tabDebug: TTabSheet;
    memDebug: TJvMemo;
    ckDisplayAfterWrite: TJvCheckBox;
    actCSVReport: TAction;
    ToolButton5: TToolButton;
    edUsed: TNumberEdit;
    Label10: TLabel;
    Splitter1: TSplitter;
    procedure statMainVariableClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure actAboutExecute(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    //*
    procedure NewFonts(Sender: TObject);
    procedure spinFormattedChange(Sender: TObject);
    //*
    procedure actTIImageOpenBeforeExecute(Sender: TObject);
    procedure actTIImageOpenAccept(Sender: TObject);
    procedure ClearCurrentDiskette;
    procedure ClearCurrentFile;
    procedure ClearDisketteInfo;
    procedure ClearMemos;
    procedure Tellem(D: boolean; S: string);
    procedure ShowSec(S: array of byte);
    procedure ClearTIFILESFileInfo;
    procedure DisplayTIDisketteImageFile(aFilename: TFilename);
    procedure DisplayTISector(SecNum: integer; Sec: array of byte);
    procedure DisplayTISectorNoClear(SecNum: integer; Sec: array of byte);
    procedure ReadTIFilesSectors(Sender: TObject);
    procedure actTIFileOpenBeforeExecute(Sender: TObject);
    procedure actTIFileOpenAccept(Sender: TObject);
    procedure actFormatCompanionExecute(Sender: TObject);
    procedure actFormatDV80Execute(Sender: TObject);
    procedure actFormatBASICExecute(Sender: TObject);
    procedure actFormatFORTHExecute(Sender: TObject);
    procedure actNarrowListExecute(Sender: TObject);
    procedure actExtractFileExecute(Sender: TObject);
    procedure actDisplayFileExecute(Sender: TObject);
    procedure MakeTIFilesHeader(var ThisFile: TTIFile);
    procedure PrintDisketteImageCatalog;
    procedure PrintCFList;
    procedure PrintRichEdit;
    procedure PrintFORTH;
    procedure actPrintAccept(Sender: TObject);
    procedure ckTIWriterClick(Sender: TObject);
    procedure ckCompanionClick(Sender: TObject);
    procedure ckBASICClick(Sender: TObject);
    procedure gridDiskBitMapDrawCell(Sender: TObject; ACol, ARow: Integer;
      Rect: TRect; State: TGridDrawState);
    procedure actionMainUpdate(Action: TBasicAction; var Handled: Boolean);
    procedure spinSectorDownClick(Sender: TObject);
    procedure spinSectorUpClick(Sender: TObject);
    procedure actGotoTISectorExecute(Sender: TObject);
    procedure gridSectorsClick(Sender: TObject);
    procedure lbFilesDblClick(Sender: TObject);
    procedure lbCatalogDblClick(Sender: TObject);
    procedure actSaveAsBeforeExecute(Sender: TObject);
    procedure actSaveAsAccept(Sender: TObject);
    procedure actFormatFontExecute(Sender: TObject);
    //
    procedure CurrentCFVolumeClear;
    procedure ReadCFImage;
    procedure DisplayCFSector;
    procedure ReadCFVolumeList;
    procedure listRDrivesClick(Sender: TObject);
    procedure lbVolumesDblClick(Sender: TObject);
    procedure DisplayCFDisketteImage;
    procedure cbFormattedFontClick(Sender: TObject);
    procedure HelpContentsExecute(Sender: TObject);
    procedure actGotoCFSectorExecute(Sender: TObject);
    procedure actGotoCFVolumeExecute(Sender: TObject);
    procedure spinCFSectorDownClick(Sender: TObject);
    procedure spinCFSectorUpClick(Sender: TObject);
    procedure spinVolumeUpClick(Sender: TObject);
    procedure spinVolumeDownClick(Sender: TObject);
    //* These actions will depend on general procs below
    procedure actCFtoPCExecute(Sender: TObject);
    procedure actPCtoCFExecute(Sender: TObject);
    procedure actFormatCFVolExecute(Sender: TObject);
    procedure actFormatAllVolsExecute(Sender: TObject);
    //* General procs for above actions, drag & drop, etc.
    procedure FormatCFVol(Volnum:integer; Showerror: boolean);
    procedure ImageFiletoCF(VolNum: integer; PCImage: TFilename; Disp: boolean);
    procedure CFVoltoImageFile(VolNum: integer; PCImage: TFilename);
    procedure lbVolumesDragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure lbFilesDragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure lbVolumesDragDrop(Sender, Source: TObject; X, Y: Integer);
    procedure lbFilesDragDrop(Sender, Source: TObject; X, Y: Integer);
    procedure actCSVReportExecute(Sender: TObject);
   private
    BuildString: string;
    { Private declarations }
  public
    procedure DisplayHint(Sender: TObject);
    { Public declarations }
  end;

var
  frmMain: TfrmMain;
  TIHeaderBuf: array[1..16] of byte;
  TIFullHeaderBuf: array[1..128] of byte;
  TISectorBuf: array[0..255] of byte;
  CurrentSector: integer;
  MaxSectors: integer;
  CurrentCFVolume: TCFVolume;    //* one volume at a time
  CurrentFile: TTIFile;          //* one file at a time
  CurrentDisk: TTIDisk;          //* one disk at a time, PC or CF volume
  DisplayingFile: boolean;
  DisplayingDiskImage: boolean;
  DisplayingDiskFile: boolean;
  ResizingGrid: boolean;
  FORTHDisk: boolean;
  LineNumStart: integer;
  LineNumEnd: integer;
  ProgramTableEnd: integer;
  ProgramTableStart: integer;
  PrintMargins: TRect;
  //*
  TrueCFSector: array[0..511] of byte;
  CFSector: array[0..255] of byte;     // every other byte of above
  drivetable: array[0..25] of byte;
  currCFsec: longint;
  currCFdrv: integer;
  currDriveparams: TDriveParams;
  CFVolumeCount: integer;
  CFMountInfo: boolean;
  CFDSK1, CFDSK2, CFDSK3: integer;
  Save_Cursor: TCursor;

const cIllegalChars : tSysCharSet = ['\', '/', '?', '*', '.', ':', '<', '>', '|'];

implementation


uses JclFileUtils;

{$R *.dfm}

procedure TfrmMain.statMainVariableClick(Sender: TObject);
{0	User
1	Win Directory
2	Current Directory
3	Free Space
4	Disk Size
5	System Directory
6	Registered Owner
7	Registered Company
8	WIN Version
9	Colordepth (not)
10	Screensaver Delay (zero based?)
11	Mem Size
12	Free System Resources
13	Free GDI Resources
14	Free User Resources
15	Computername
16	Major Version
17	Build (WIN)
18	Memory Loaded
19	Memory Free
20	Free Memory Pages
21	Total Memory Pages
22	Free Virtual Memory
23	Total Virtual Memory
24	Platform ID
25	Platform
26	Number of Processors
27	Processor
28	Temp Path
29	DOS Ver
30	NumLock State
31	Capslock State
32	ScrollLock State
33	Date & Time
34	Company Name
35	Product Name (Running Program)
36	Product Version (Running Program)
37	Legal Copyright (Running Program)
38	Legal Trademark (Running Program)
39	File Description (Running Program)
40	Comment (Running Program)
41-100	Empty
}
begin
  statMainVariable.Visible := False;
  //
  case Ord(statMainVariable.Info) of
    0: statMainVariable.Info := Succ(statMainVariable.Info);
    1: statMainVariable.Info := Succ(statMainVariable.Info);
    2..14:  while (Ord(statMainVariable.Info) <> 15) do
      statMainVariable.Info := Succ(statMainVariable.Info);
    15..24: while (Ord(statMainVariable.Info) <> 25) do
      statMainVariable.Info := Succ(statMainVariable.Info);
    25: statMainVariable.Info := Succ(statMainVariable.Info);
    26: statMainVariable.Info := Succ(statMainVariable.Info);
    27..34: while (Ord(statMainVariable.Info) <> 35) do
      statMainVariable.Info := Succ(statMainVariable.Info);
    35: statMainVariable.Info := Succ(statMainVariable.Info);
    36: statMainVariable.Info := Succ(statMainVariable.Info);
    37..39: while (Ord(statMainVariable.Info) <> 40) do
      statMainVariable.Info := Succ(statMainVariable.Info);
    40: while (Ord(statMainVariable.Info) <> 0) do
      statMainVariable.Info := Pred(statMainVariable.Info);
    end;
  //
  if Ord(statMainVariable.Info) = 40 then
        statMainVariable.UseStandardCaption := false
  else
        statMainVariable.UseStandardCaption := true;
  statMainVariable.Visible := True;
end;

procedure TfrmMain.FormCreate(Sender: TObject);
var  FileDate: TDateTime;
begin
  //
  // I like to display the build date
  //
  if FileAge(Application.exename, FileDate) then
   Buildstring :='Built ' + FormatDateTime('dddd, mmmm d, yyyy '
     + '"at" h:mm am/pm', FileDate)
  else
    BuildString := 'cannot determine file Date';
  //
  Application.OnHint := DisplayHint; Application.OnHint := DisplayHint;
  Application.HelpFile := ExtractFilePath(Application.ExeName) + 'TI Image Reader.chm';
  //
  //  I do not show the About Box at startup
  //
  lblMessage.Caption := BuildString;
  Tellem(true, '');
end;

procedure TfrmMain.DisplayHint(Sender: TObject);
begin
  lblMessage.Caption := GetLongHint(Application.Hint);
end;

procedure TfrmMain.FormActivate(Sender: TObject);
var
  i: integer;
  dp: TDriveParams;
  sinfo: string;
  ssize: string;
  size: real;
  tableindex: integer;
begin
  Tellem(true, '');
  currCFsec:=0;
  currCFdrv:=0;
  //* list frives for CF reader
  ListRDrives.Items.clear;
  tableindex:=0;
  for i:=0 to 255 do
  begin
    if GetDriveParams(i, @dp) then
    begin
      drivetable[tableindex]:=i; inc(tableindex);
      size:=dp.physSecLO / 2048;
      if size < 1000 then
        ssize:=Format('%f MB)',[size])
      else
        ssize:=Format('%f GB)',[size / 1024]);
      if dp.infoflags AND IFLAG_REMOVABLE<>0 then
        sinfo:='Removable Disk ('+ssize   //<--- We'll only read removables
      else
        sinfo:='Fixed Disk ('+ssize;
      listRDrives.items.add(sinfo);
    end;
  end;
  //* label gridSector and gridCFSector cells
  for i := 0 to gridSectors.RowCount - 1 do begin
      if ((i <= (gridSectors.ColCount div 2)) and (i <> 0)) then begin
        gridSectors.cells[i, 0] := format('%.2x', [i - 1]);
        gridSectors.cells[i + (gridSectors.ColCount div 2) , 0] :=
          format('%.2d', [i - 1]);
        end;
      if i <> 0 then gridSectors.cells[0, i] :=
        format('%.4x', [(i - 1) * ((gridSectors.colcount div 2))]);
      end;
  gridSectors.ColWidths[0] := 30;
  //*
  for i := 0 to gridCFSectors.RowCount - 1 do begin
      if ((i <= (gridCFSectors.ColCount div 2)) and (i <> 0)) then begin
        gridCFSectors.cells[i, 0] := format('%.2x', [i - 1]);
        gridCFSectors.cells[i + (gridCFSectors.ColCount div 2) , 0] :=
          format('%.2d', [i - 1]);
        end;
      if i <> 0 then gridCFSectors.cells[0, i] :=
        format('%.4x', [(i - 1) * ((gridCFSectors.colcount div 2))]);
      end;
  gridCFSectors.ColWidths[0] := 30;
  //*
  NewFonts(Self);
  cbFormattedFontClick(Self);
  spinFormattedChange(Self);
  pagesTI.ActivePageIndex := 0;
end;

procedure TfrmMain.actAboutExecute(Sender: TObject);
var cstring: string;
begin
 randomize;
 //
 case random(8) of
  0: cstring := 'The truth about life is a process of elimination';
  1: cstring := 'Life is just high school with money';
  2: cstring := 'Weapons often turn upon the wielder.'+ #13 + #10 +
                      'An army' + #39 + 's harvest is a waste of thorns';
  3: cstring := 'What the...?';
  4: cstring := 'Hippies use side door-->';
  5: cstring := '...You gotta dress for ' + #39 + 'em All';
  6: cstring := 'The TI99-4 What??';
  7: cstring := #39 + 'Invalid Pointer Exception?' + #39 + 'Not in my code, baby!';
  end;
 //
 // Create an instance of the AboutBox component
 //
 with TFABABoutBox.Create(Self) do
 //
 try
 Title := Buildstring;
 //
 with TJclFileVersionInfo.Create(Application.ExeName) do
  try
    ProdName := ProductName;
    ProdVer := Format('Version: %s', [FileVersion]); //<-- not ProductVersion
    ProdCopyright := LegalCopyright;
    ProdComment := Comments;
  finally
  end;
 ShowMoreButton := True;
 LogoVisible := False;
 //*
 SecretText.Clear;
 with SecretText do begin
        Add('');
        Add('');
        Add('');
        Add('This application views and manages');
        Add('TI diskette DOAD images, ');
        Add('TI File-on-Disk or FIAD files');
        Add('with TIFILES headers, and');
        Add('CF7a+ formatted CF cards.');
        Add('');
        Add('There may yet be a Help File!');
        Add('');
        Add('Read it for all the issues.');
        Add('');
        Add('Heaven help us!');
        Add('');
        Add('And many thanks to our friends:');
        Add('the JEDI team');
        Add('Alexander Grau');
        Add('Jaime Malilong');
        Add('the TI ' + #39 + 'community' + #39);
        Add('and the good folks at LMD');
        end;
 //*
 OtherComments := cstring;
 OtherCommentVisible := True;
 ProdCommentVisible := True;
 LogoVisible := False;
 Timer := False;
 //
 Execute;
 finally
 //
 //  free the instance
 //
 Free;
 end;
end;

procedure TfrmMain.HelpContentsExecute(Sender: TObject);
begin
  ShellExecute(Handle, 'open', 'TI Image Reader.chm', nil, nil, SW_SHOWNORMAL) ;
end;

//*****************************************************
//*    Most Application-Specific Code Here            *
//*****************************************************

//*****************************************************
//*    Utility Procedures & Functions                 *
//*****************************************************

function StripIllegalChars(Filename: String): String;
var i, j : integer;
begin
    SetLength (result, Length (Filename));
    j := 0;
    for i := 1 to Length (Filename) do
        begin
        if not (Filename [i] in cIllegalChars)
        then begin
            inc (j);
            result [j] := Filename [i];
            end;
        end;
    SetLength (result, j);
end;

procedure SetRichEdMargins(ARichEdit: TRichEdit; Arect: TRect);

//*
//* routine to set Richedit pixel margins based on a
//*    page setup 1/1000" margins Rect passed as parameter
//*

var
   NewRect: TRect;
   LogX, LogY: integer;
begin
   { Margins are in 1000ths of an inch from each edge of the paper. }
   { PageRect is the outline of the printable area in pixels.       }
   {                                                                }
   { The docs say twips, but the source code for TRichEdit98.Print    }
   { converts pixels...                                             }
   {                                                                }
   { We still have to convert the Right and Bottom values           }
   { since they are the distance from the edge of their respective  }
   { sides of the paper, not the distance from the other margin     }
   LogX := GetDeviceCaps(Printer.Handle, LOGPIXELSX);
   LogY := GetDeviceCaps(Printer.Handle, LOGPIXELSY);
   //*
   NewRect.Left   := (ARect.Left * LogX div 1000) -
      GetDeviceCaps(Printer.Handle, PHYSICALOFFSETX);
   NewRect.Top    := ARect.Top * LogY div 1000 -
      GetDeviceCaps(Printer.Handle, PHYSICALOFFSETY);
   //*
   NewRect.Right  := Printer.PageWidth - (ARect.Right * LogX div 1000) +
      GetDeviceCaps(Printer.Handle, PHYSICALOFFSETX);
   NewRect.Bottom := Printer.PageHeight - (ARect.Bottom * LogY div 1000) +
      GetDeviceCaps(Printer.Handle, PHYSICALOFFSETY);
   //*
   ARichEdit.PageRect:= NewRect;
end;

function DecodeType(T, RSize: integer): string;
 //*
 //*  decode 'status flag' byte into a tight little 7-byte string
 //*
 begin
   case T of
   //* Program/Image
   1: Result :=
         format('IMAGE U', [RSize]); //*image u  0000 0001  = 1
   9: Result :=
         format('IMAGE P', [RSize]); //*image p  0000 1001  = 5
   //* DIS/VAR
   0: Result :=
         format('DF%3d U', [RSize]); //*dv sz u  0000 0000  = 0
   4: Result :=
         format('DF%3d P', [RSize]); //*dv sz p  0000 1000  = 8
   //* INT/VAR
   2: Result :=
         format('IF%3d U', [RSize]); //*iv sz u  0000 0010  = 2
   10: Result :=
         format('IF%3d P', [RSize]); //*iv sz u  0000 1010  = 10
   //* DIS/FIX
   128: Result :=
         format('DV%3d U', [RSize]); //*dv sz p  1000 0000  = 128
   136: Result :=
         format('DV%3d P', [RSize]); //*dv sz p  1000 1000  = 136
   //* INT/FIX
   130: Result :=
         format('IV%3d U', [RSize]); //*iv sz u  1000 0010  = 130
   138: Result :=
         format('IV%3d P', [RSize]); //*iv sz u  1000 1010  = 138
   end;
end;

function CheckforComp: boolean;
begin
//**************************************
//**                                   *
//**  companion was my original        *
//**  favorite word processor. It      *
//**  has a unique file format.        *
//**                                   *
//**************************************
  Result := False;
  if ((ord(CurrentFile.SectorList[0].contents[0]) =
       ord(CurrentFile.SectorList[0].contents[1]) + 1)
     and
      (Copy(CurrentFile.Header.FileType, 1, 5) = 'IV254'))
  then Result := True;
end;

function CheckforWriter: boolean;
begin
//**************************************
//**                                   *
//**  TI Writer files are just DV 80   *
//**  so for now we just say TRUE here *
//**  and let the user override with   *
//**  the GUI                          *
//**                                   *
//**************************************
  Result := Copy(CurrentFile.Header.FileType, 1, 5) = 'DV 80';
end;

function CheckforBasic: boolean;
var word1: integer;
begin
//**************************************
//**                                   *
//**  the first word of a basic file   *
//**  is equal to the XOR of the       *
//**  second and third words. Logical? *
//**                                   *
//**  also capture here the critical   *
//**  line and statement table         *
//**  addresses from the BASIC header  *
//**                                   *
//**************************************
  word1 :=
    (256 * ord(CurrentFile.SectorList[0].contents[0]))
      + ord(CurrentFile.SectorList[0].contents[1]);       //*test word
  LineNumEnd :=
    (256 * ord(CurrentFile.SectorList[0].contents[2]))
      + ord(CurrentFile.SectorList[0].contents[3]);       //*End of Line
  //*                                                      //*Number Table
  ProgramTableStart := LineNumEnd + 2;
  {}
  LineNumStart :=
    (256 * ord(CurrentFile.SectorList[0].contents[4]))
      + ord(CurrentFile.SectorList[0].contents[5]);       //*Start of Line
  ProgramTableEnd :=                                      //*Number Table
    (256 * ord(CurrentFile.SectorList[0].contents[6]))
      + ord(CurrentFile.SectorList[0].contents[7]);       //*"Higest Address"
  //*                                                      //* of statement table
  Result := (word1 = (LineNumEnd xor LineNumStart));      //* <-- the test
end;

function UsedSegment(SegNumber: integer): boolean;
//************************************************
//**                                             *
//**  "turn on" bit (grid square) for used       *
//**  segments. Assumes an existing and valid    *
//**  CurrentDisk.Info.Bitmap                    *
//**                                             *
//************************************************
var BitMapIndex, BitIndex: integer;
begin
Result := False;
{//*
//* Don't bother unless we have a disk image in memory
//*}
if DisplayingDiskImage then begin
  {//*
  //* which word?
  //*}
  BitMapIndex := SegNumber div 8;
  BitIndex := SegNumber mod 8;
  //*
  //* AND with correct bit
  //*
  case bitIndex of
    0: if (1 and ord(CurrentDisk.Info.Bitmap[BitMapIndex])) = 1 then
               Result := true;
    1: if (2 and ord(CurrentDisk.Info.Bitmap[BitMapIndex])) = 2 then
               Result := true;
    2: if (4 and ord(CurrentDisk.Info.Bitmap[BitMapIndex])) = 4 then
               Result := true;
    3: if (8 and ord(CurrentDisk.Info.Bitmap[BitMapIndex])) = 8 then
               Result := true;
    4: if (16 and ord(CurrentDisk.Info.Bitmap[BitMapIndex])) = 16 then
               Result := true;
    5: if (32 and ord(CurrentDisk.Info.Bitmap[BitMapIndex])) = 32 then
               Result := true;
    6: if (64 and ord(CurrentDisk.Info.Bitmap[BitMapIndex])) = 64 then
               Result := true;
    7: if (128 and ord(CurrentDisk.Info.Bitmap[BitMapIndex])) = 128 then
               Result := true;
    end;
  end;
end;

function ThisToken(a: byte): string;
//******************************************
//**                                       *
//**     BASIC token interpreter           *
//**                                       *
//******************************************
begin
case ord(a) of
    {//*
    //* Basic Tokens
    //*}
    {//*
    //*      these should never be parsed
    //*}
    0  : ThisToken := ''; //*'end of line marker';
    255: ThisToken := ''; //*'end of file marker';
    {//*
    //*      these are special cases
    //*}
    199: ThisToken := '"'; //*'quoted string';   //*>C7  followed by length byte
                                                //*     trailing double quote not
                                                //*     and the actual string.
                                                //*     the trailing doublequote
                                                //*     is only implied, and must
                                                //*     be supplied by the
                                                //*     decoding routine

    200: ThisToken := '';  //*'string';          //*>C8  followed by length byte
    201: ThisToken := '';  //*'Stmt #';          //*>C9  followed by two byte int
    {//*
    //*   a sort of special case?
    //*}
    253: ThisToken := '#'; //*(files, a.k.a. )   //*>FD  nothing special, really,
                           //* "unquoted string  //*     but it's followed by the
                           //* NUMERIC"          //*     >C8 token and the file
                           //* by Millers        //*     number as a literal
                           //* Graphics          //*     string!!
    {//*
    //*  the rest of these are just parsed, one for one
    //*    positioning/supplying spaces can be a problem
    //*}
    129: ThisToken := ' ELSE ';
    132: ThisToken := ' IF ';
    133: ThisToken := ' GO';
    134: ThisToken := ' GOTO';
    135: ThisToken := ' GOSUB';
    136: ThisToken := ' RETURN ';
    137: ThisToken := ' DEF ';
    138: ThisToken := ' DIM ';
    139: ThisToken := ' END ';
    140: ThisToken := ' FOR ';
    141: ThisToken := ' LET ';
    142: ThisToken := ' BREAK ';
    143: ThisToken := ' UNBREAK ';
    144: ThisToken := ' TRACE ';
    145: ThisToken := ' UNTRACE ';
    146: ThisToken := ' INPUT ';
    147: ThisToken := ' DATA ';
    148: ThisToken := ' RESTORE ';
    149: ThisToken := ' RANDOMIZE ';
    150: ThisToken := ' NEXT ';
    151: ThisToken := ' READ ';
    152: ThisToken := ' STOP ';
    153: ThisToken := ' DELETE ';
    154: ThisToken := ' REM ';
    155: ThisToken := ' ON ';
    156: ThisToken := ' PRINT ';
    157: ThisToken := ' CALL ';
    158: ThisToken := ' OPTION ';
    159: ThisToken := ' OPEN ';
    160: ThisToken := ' CLOSE ';
    161: ThisToken := ' SUB ';
    162: ThisToken := ' DISPLAY ';
    164: ThisToken := ' ACCEPT ';
    169: ThisToken := ' RUN ';
    176: ThisToken := ' THEN ';
    177: ThisToken := ' TO ';
    178: ThisToken := ' STEP ';
    179: ThisToken := ','; //*(comma)
    180: ThisToken := ';'; //*(semi)
    181: ThisToken := ':'; //*(colon)
    182: ThisToken := ') ';
    183: ThisToken := '(';
    184: ThisToken := ' & ';
    190: ThisToken := ' = ';
    191: ThisToken := ' < ';
    192: ThisToken := ' > ';
    193: ThisToken := ' + ';
    194: ThisToken := ' -';   //* no space for negative numbers
    195: ThisToken := ' * ';
    196: ThisToken := '/';
    197: ThisToken := '^';
    202: ThisToken := ' EOF';
    203: ThisToken := ' ABS';
    204: ThisToken := ' ATN';
    205: ThisToken := ' COS';
    206: ThisToken := ' EXP';
    207: ThisToken := ' INT';
    208: ThisToken := ' LOG';
    209: ThisToken := ' SGN';
    210: ThisToken := ' SIN';
    211: ThisToken := ' SQR';
    212: ThisToken := ' TAN';
    213: ThisToken := ' LEN';
    214: ThisToken := ' CHR$';
    215: ThisToken := ' RND';
    216: ThisToken := ' SEG$';
    217: ThisToken := ' POS';
    218: ThisToken := ' VAL';
    219: ThisToken := ' STR$';
    220: ThisToken := ' ASC';
    222: ThisToken := ' REC';
    241: ThisToken := ' BASE ';
    243: ThisToken := ' VARIABLE ';
    244: ThisToken := ' RELATIVE ';
    245: ThisToken := ' INTERNAL ';
    246: ThisToken := ' SEQUENTIAL ';
    247: ThisToken := ' OUTPUT ';
    248: ThisToken := ' UPDATE ';
    249: ThisToken := ' APPEND ';
    250: ThisToken := ' FIXED ';
    251: ThisToken := ' PERMANENT ';
    252: ThisToken := ' TAB ';
    {//*
    //*  XBasic only
    //*}
    130: ThisToken := ' :: ';
    131: ThisToken := '!';
    163: ThisToken := ' IMAGE ';
    165: ThisToken := ' ERROR ';
    166: ThisToken := ' WARNING ';
    167: ThisToken := ' SUBEXIT ';
    168: ThisToken := ' SUBEND ';
    170: ThisToken := ' LINPUT ';
    186: ThisToken := ' OR ';
    187: ThisToken := ' AND ';
    188: ThisToken := ' XOR ';
    189: ThisToken := ' NOT ';
    221: ThisToken := ' PI ';
    223: ThisToken := ' MAX';
    224: ThisToken := ' MIN';
    225: ThisToken := ' RPT$';
    232: ThisToken := ' NUMERIC ';
    233: ThisToken := ' DIGIT ';
    234: ThisToken := ' UALPHA ';
    235: ThisToken := ' SIZE ';
    236: ThisToken := ' ALL ';
    237: ThisToken := ' USING ';
    238: ThisToken := ' BEEP ';
    239: ThisToken := ' ERASE ';
    240: ThisToken := ' AT';
    254: ThisToken := ' VALIDATE ';
    {//*
    //*  everything else
    //*}
    else ThisToken := chr(ord(a));
    end;  //*case
end;

function ReadTIImage(AFilename: string): boolean;
  //****************************************
  //**  subprocedure to decode nybbles     *
  //**  in a 3-byte cluster record         *
  //****************************************
  procedure Nybbler(var X: TTISegmentGroup);
  begin
     X.StartSeg := strtoint('$' +
      (copy((inttohex(ord(X.cluster[1]), 2)), 2, 1)
         + inttohex(ord(X.cluster[0]), 2)));
     //*
     X.EndOffset := strtoint('$' +
      (inttohex(ord(X.cluster[2]), 2)
         + copy((inttohex(ord(X.cluster[1]), 2)), 1, 1)));
  end;
//*
//*   main function code
//*
var FromF: file;
    SS: string[10];
    NumRead, i, j, k: Integer;
    FDRList: TStrings;
    ClusterPointer: integer;
begin
  Result := False;
  frmMain.Tellem(true, '');
  {//*
  //*  Open File and Read Sector 0;
  //*}
  FDRList := TStringList.Create;
  //*
  try
    AssignFile(FromF, AFileName);
    Reset(FromF, 1);
    BlockRead(FromF, TISectorBuf, SizeOf(TISectorBuf), NumRead);
    //*
    SS := '';
    for i := 0 to 9 do
      SS := SS +  Char(TISectorBuf[i]);
    CurrentDisk.Info.Diskname := SS;
    SS := '';
    CurrentDisk.Info.SectorCount := (256 * ord(TISectorBuf[10]))
                                    +  ord(TISectorBuf[11]);
    CurrentDisk.Info.SectorsPerTrack := ord(TISectorBuf[12]);
    //*
    //*case CurrentDisk.Info.SectorCount of
    //*  360: begin
    //*       frmMain.gridDiskBitMap.ColCount := 8;
    //*       frmMain.gridDiskBitMap.Width := 20;
    //*       frmMain.gridDiskBitMap.Height := 94;
    //*       SetLength(CurrentDisk.Info.Bitmap, 45);
    //*       end;
    //*  720: begin
    //*       frmMain.gridDiskBitMap.ColCount := 16;
    //*       frmMain.gridDiskBitMap.Width := 36;
    //*       frmMain.gridDiskBitMap.Height := 94;
    //*       SetLength(CurrentDisk.Info.Bitmap, 90);
    //*       end;
    //*  1440: begin
    //*        frmMain.gridDiskBitMap.ColCount := 32;
    //*        frmMain.gridDiskBitMap.Width := 68;
    //*        frmMain.gridDiskBitMap.Height := 94;
    //*        SetLength(CurrentDisk.Info.Bitmap, 180);
    //*        end;
    //* end; //*case

    ResizingGrid := true;            //* set flag to kill OnDrawCell
    frmMain.gridDiskBitMap.ColCount :=
      CurrentDisk.Info.SectorCount div 45;
    frmMain.gridDiskBitMap.Width :=
     (2 * frmMain.gridDiskBitMap.ColCount) + 4;
    frmMain.gridDiskBitMap.Height :=
     (2 * frmMain.gridDiskBitMap.RowCount) + 4;
    ResizingGrid := False;           //* Turn OnDrawCell back on

    //*frmMain.gridDiskBitMap.DefaultColWidth :=
    //*  2 * (1440 div CurrentDisk.Info.SectorCount);
    //*
    SetLength(CurrentDisk.Info.Bitmap,
      (CurrentDisk.Info.SectorCount div 8) + 1);  // <<--- why does this work?
    //*
    for i := 13 to 15 do
      SS := SS +  char(TISectorBuf[i]);
    CurrentDisk.Info.DSRMark := SS;
    //*
    if char(TISectorBuf[16]) = 'P' then
      CurrentDisk.Info.diskProtected := true
    else CurrentDisk.Info.diskProtected := False;
    //*
    CurrentDisk.Info.TracksPerSide := ord(TISectorBuf[17]);
    CurrentDisk.Info.Sides := ord(TISectorBuf[18]);
    CurrentDisk.Info.Density := ord(TISectorBuf[19]);
    //*
    //* Read Bitmap
    //*

    for i := 56 to (56 + (CurrentDisk.Info.SectorCount div 8)) do
      CurrentDisk.Info.Bitmap[i - 56] :=  TISectorBuf[i];

    //*
    //*  kinda false test, but it SHOULD be true
    //*
    if CurrentDisk.Info.DSRMark = 'DSK' then begin
    //*
      Result := True;
      DisplayingDiskImage := True;
      DisplayingDiskFile := False;
      DisplayingFile := False;
      MaxSectors := CurrentDisk.Info.SectorCount - 1;
      //*
      //*  Read Sector 1 list of FDR addresses and Set File Count
      //*
      BlockRead(FromF, TISectorBuf, SizeOf(TISectorBuf), NumRead);
      i := 0;
      while (256 * ord(TISectorBuf[i]) + ord(TISectorBuf[i + 1])) <> 0 do begin
        FDRList.Add(inttostr((256 * ord(TISectorBuf[i])
          + ord(TISectorBuf[i + 1]))));
        inc(i); inc(i);                   //* skip to next byte pair
        end;
      CurrentDisk.Info.FileCount := FDRList.Count;
      //*
      //*  adjust the array sizes
      //*
      SetLength(CurrentDisk.SectorList, CurrentDisk.Info.SectorCount);
      SetLength(CurrentDisk.Directory, CurrentDisk.Info.FileCount);
      end;
    //*
    //* Read ALL Sectors (yes, read #0 and #1 again...)
    //*
    if Result then begin
      CloseFile(FromF);
      Reset(FromF, 1);      //* back to the future
      for i := 0 to CurrentDisk.Info.SectorCount - 1 do begin
        frmMain.Tellem(true, 'Reading Sector ' + inttostr(i));
        BlockRead(
              FromF,
              CurrentDisk.SectorList[i].Contents,
              SizeOf(CurrentDisk.SectorList[i].Contents),
              NumRead);
        CurrentDisk.SectorList[i].EndOffset := NumRead;
        end;
        CurrentDisk.SectorsRead := true;
      end;
    //*
    //* Now we have all Sectors in Memory
    //*   so we can build FDR array
    //*
    //* FIXME for 0 length FDRList   <--?????
    for i := 0 to FDRList.Count - 1 do begin
      frmMain.Tellem(true,
          'FDR array entry ' + inttostr(i) + ' (' + inttostr(i+1) + ' files)');
      with CurrentDisk.Directory[i] do begin
        Address := strtoint(FDRList[i]);
        SS := '';
        for j := 0 to 9 do
          SS := SS + char(CurrentDisk.SectorList[Address].Contents[j]);
        //*
        Filename := SS;
        //* byte 12
        StatusFlag := ord(CurrentDisk.SectorList[Address].Contents[12]);
        //* byte 13
        RecordsPerSector :=
           ord(CurrentDisk.SectorList[Address].Contents[13]);
        //* bytes 14-15
        SectorCount :=
           (256 * ord(CurrentDisk.SectorList[Address].Contents[14]))
            + ord(CurrentDisk.SectorList[Address].Contents[15]);
        //* byte 16
        LastByteInLastSector :=
           ord(CurrentDisk.SectorList[Address].Contents[16]);
        //* byte 17
        RecordLength := ord(CurrentDisk.SectorList[Address].Contents[17]);
        //* bytes 18 and 19 but swapped!
        NumberRecordsSectors :=
           (256 * ord(CurrentDisk.SectorList[Address].Contents[19]))
            + ord(CurrentDisk.SectorList[Address].Contents[18]);
        //*frmMain.memDebug.Lines.Add(CurrentDisk.Directory[i].Filename);
        //*
        clusterpointer := 28;
        //*
        for k := 0 to 74 do begin
          Segmentlist[k].Cluster[0]
            := CurrentDisk.SectorList[Address].Contents[clusterpointer];
          Segmentlist[k].Cluster[1]
            := CurrentDisk.SectorList[Address].Contents[clusterpointer + 1];
          Segmentlist[k].Cluster[2]
            := CurrentDisk.SectorList[Address].Contents[clusterpointer + 2];
          //*  and decode it
          Nybbler(SegmentList[k]);
          clusterpointer := clusterpointer + 3;
          end; // for k
        end;  // with CurrentDisk.Directory
      end;   // for i
    //*
  finally
    CloseFile(FromF);
    FDRList.Free;                    //*<-- no memory leak
  end;
end;

function ReadTIFilesHeader(AFilename: String;
  var AHeader: TTIFilesHeader): boolean;
 { The oft-quoted FAQ I below is wrong: it reverses the VAR/FIX bit,
   or maybe I'm just dyslexic, but cut and paste can't lie...
   or maybe Telco is mis-building the Header? doubt it...

 "Offset  8 -- # sectors multiply by 256 to get actual bytes
  Offset 10 -- Status flags:
  Bit#  Description
   0    0 = Data file
        1 = Program (memory image) file       0000 0001
   1    0 = Display                           0000 0010
        1 = Internal
   2        Reserved (0)
   3    0 = Unprotected                       0000 1000
        1 = Protected
  4-6       Reserved (0)
   7    0 = Variable-length recs              1000 0000
        1 = Fixed-length records              //* this is the backwards part
                                              //* 1 = VAR; 0 = FIX
  "Offset 11 -- # recs per sector
               Divide 256 by logical record size.
               Only valid for FIXED LENGTH records.
               I'm pretty sure the field is meaningless
               for VARIABLE LENGTH records.
               A DF80 file has 3 records per sector...
               an IF128 has 2.  DV80 will probably tell you 3,
               but since its sectors may have more than 3 records,
               it's meaningless.
  "Offset 12 -- EOF offset
               You're quite correct... number of bytes used
               in last sector. In DV80 files, points to
               an >FF if I recall correctly.
  "Offset 13 -- Logical record size
               Well...  maximum number of chars per record...
               thus 80 in DV80, 163 in DV163, etc...
               Note that for variable-length records,
               the actual record length can be up to n+1
               (1byte for size storage)
  "Offset 14 -- # of lev 3 records = total # of records allocated
               You went and found an obtuse definition.
               Corresponds to offset 8 in extra params
               block of TDIF/TDOF. Simply put,
               how many records are in this file???
  "Offset 16 -- no clue! I'll have to check my old sources for this.}

  procedure DecodeStatusByte;
  //*****************************************************
  //**  This uses StatusFlag for some extra stuff       *
  //**     that I define separately in header record    *
  //*****************************************************
  begin
   AHeader.ProgType := (AHeader.StatusFlag and 1) = 1;

   if (AHeader.StatusFlag and 2) = 2 then
     AHeader.DataType := 'INT'
   else
     AHeader.DataType := 'DIS';

   AHeader.ProtectBit := (AHeader.StatusFlag and 8) = 8;

   if (AHeader.StatusFlag and 128) = 128 then
     AHeader.DataSize := 'VAR'
   else
     AHeader.DataSize := 'FIX';
   //**********************************************************
   //**  Includes this call to the external DeCodeType        *
   //**  routine, which is separated to be available to the   *
   //**  disk image catalog routines. DeCodeType is basically *
   //**  a sledgehammer approach since there are only a few   *
   //**  possible combinations                                *
   //**********************************************************
   AHeader.FileType := DecodeType(AHeader.StatusFlag, AHeader.RecSize);
  end;


//*  Main Code Block

var
  i, j: integer;
  FromF: file;
  SS: string;
  NumRead: Integer;
begin
  Result := False;
  SS := 'NOT  TI';                           //* prove me wrong
  Move(SS[1], TIHeaderBuf[1], Length(SS));

  AssignFile(FromF, AFileName);
  Reset(FromF, 1);

  try
    BlockRead(FromF, TIHeaderBuf, SizeOf(TIHeaderBuf), NumRead);
  finally
    CloseFile(FromF);
  end;

  Move(TIHeaderBuf[1], AHeader.HeaderString, Sizeof(AHeader.HeaderString));
  Result := (Aheader.HeaderString = 'TIFILES');

  if Result = true then begin
    DisplayingFile:= True;
    AHeader.Sectors := (256 * ord(TIHeaderBuf[9])) + ord(TIHeaderBuf[10]);
    MaxSectors := AHeader.Sectors - 1;
    AHeader.StatusFlag := ord(TIHeaderBuf[11]);
    AHeader.RecordsPerSec := ord(TIHeaderBuf[12]);
    AHeader.EOFOffset := ord(TIHeaderBuf[13]);
    AHeader.RecSize := ord(TIHeaderBuf[14]);

    //*  The 'record count' is only for
    //*   variable length records but we always read it

    AHeader.RecCount := (256 * ord(TIHeaderBuf[16])) + ord(TIHeaderBuf[15]);

    DecodeStatusByte;

    //*  Display the actual header 'Header' (1st 16 bytes) in hex

    for i := 0 to 1 do
      for j := 0 to 7 do
       frmMain.gridHeader.Cells[j, i] :=
         format(   '%.2x', [  ord(TIHeaderBuf[(i * 8) + j + 1])  ]   );

    end;
end;

function ReadCFSector: boolean;
var i, j: integer;
begin
  frmMain.edSectorNum.Value :=  currCFsec;
  frmMain.Tellem(true, 'reading CF sector #' + inttostr(currCFSec));
  if not ExtendedRead(currCFdrv, currCFsec, 1, @TrueCFSector) then
  begin
    MessageDlg('Error reading drive at sector #' + inttostr(currCFSec), mtInformation, [mbOk], 0);
    exit;
  end;
  //
  frmMain.Tellem(true, 'converting CF sector #' + inttostr(currCFSec));
  i := 0;
  j := 0;
  while i < 512 do begin
    CFSector[j] := TrueCFSector[i];
    i := i + 2;
    inc(j);
  end;
  frmMain.ShowSec(CFSector);
  frmMain.edCFSector.Value := currCFSec;
end;


function ReadCFDiskImage: boolean;
  //****************************************
  //**  subprocedure to decode nybbles     *
  //**  in a 3-byte cluster record         *
  //****************************************
  procedure Nybbler(var X: TTISegmentGroup);
  begin
     X.StartSeg := strtoint('$' +
      (copy((inttohex(ord(X.cluster[1]), 2)), 2, 1)
         + inttohex(ord(X.cluster[0]), 2)));
     //*
     X.EndOffset := strtoint('$' +
      (inttohex(ord(X.cluster[2]), 2)
         + copy((inttohex(ord(X.cluster[1]), 2)), 1, 1)));
  end;
//*
//*   main function code
//*
var SS: string[10];
    NumRead, i, j, k: Integer;
    FDRList: TStrings;
    ClusterPointer: integer;
begin
  Result := False;
  frmMain.Tellem(true, '');
  FDRList := TStringList.Create;
  //*
  try
    SS := '';
    for i := 0 to 9 do
      SS := SS +  Char(CFSector[i]);
    CurrentDisk.Info.CFVolume := true;
    CurrentDisk.Info.Diskname := SS;
    SS := '';
    CurrentDisk.Info.SectorCount := (256 * ord(CFSector[10]))
                                    +  ord(CFSector[11]);
    CurrentDisk.Info.SectorsPerTrack := ord(CFSector[12]);
    ResizingGrid := true;            //* set flag to kill OnDrawCell
    frmMain.gridDiskBitMap.ColCount :=
      CurrentDisk.Info.SectorCount div 45;
    frmMain.gridDiskBitMap.Width :=
     (2 * frmMain.gridDiskBitMap.ColCount) + 4;
    frmMain.gridDiskBitMap.Height :=
     (2 * frmMain.gridDiskBitMap.RowCount) + 4;
    ResizingGrid := False;           //* Turn OnDrawCell back on

    //*frmMain.gridDiskBitMap.DefaultColWidth :=
    //*  2 * (1440 div CurrentDisk.Info.SectorCount);
    //*
    SetLength(CurrentDisk.Info.Bitmap,
      (CurrentDisk.Info.SectorCount div 8) + 1);  // <<--- why does this work?
    //*
    for i := 13 to 15 do
      SS := SS +  char(CFSector[i]);
    CurrentDisk.Info.DSRMark := SS;
    //*
    if char(CFSector[16]) = 'P' then
      CurrentDisk.Info.diskProtected := true
    else CurrentDisk.Info.diskProtected := False;
    //*
    CurrentDisk.Info.TracksPerSide := ord(CFSector[17]);
    CurrentDisk.Info.Sides := ord(CFSector[18]);
    CurrentDisk.Info.Density := ord(CFSector[19]);
    //*
    //* Read Bitmap
    //*

    for i := 56 to (56 + (CurrentDisk.Info.SectorCount div 8)) do
      CurrentDisk.Info.Bitmap[i - 56] :=  CFSector[i];

    //*
    //*  kinda false test, but it SHOULD be true
    //*
    if CurrentDisk.Info.DSRMark = 'DSK' then begin
    //*
      Result := True;
      DisplayingDiskImage := True;
      DisplayingDiskFile := False;
      DisplayingFile := False;
      MaxSectors := CurrentDisk.Info.SectorCount - 1;
      //*
      //*  Read Sector 1 list of FDR addresses and Set File Count
      //*
      inc(currCFSec);
      ReadCFSector;
      i := 0;
      while (256 * ord(CFSector[i]) + ord(CFSector[i + 1])) <> 0 do begin
        FDRList.Add(inttostr((256 * ord(CFSector[i])
          + ord(CFSector[i + 1]))));
        inc(i); inc(i);                   //* skip to next byte pair
        end;
      CurrentDisk.Info.FileCount := FDRList.Count;
      //*
      //*  adjust the array sizes
      //*
      SetLength(CurrentDisk.SectorList, CurrentDisk.Info.SectorCount);
      SetLength(CurrentDisk.Directory, CurrentDisk.Info.FileCount);
      end;
    //*
    //* Read ALL Sectors (yes, read #0 and #1 again...)
    //*
    if Result then begin
      Dec(currCFSec);      //* back to the future
      for i := 0 to CurrentDisk.Info.SectorCount - 1 do begin
        frmMain.Tellem(true, 'Reading Sector ' + inttostr(i));
        ReadCFSector;
        {
        //
        //  need to tranfer sector byte array to CurrentDisk.Sectorlist[i].contents
        //

        BlockRead(
              FromF,
              CurrentDisk.SectorList[i].Contents,
              SizeOf(CurrentDisk.SectorList[i].Contents),
              NumRead);
        CurrentDisk.SectorList[i].EndOffset := NumRead;
        }
        for j := 0 to 254 do
          CurrentDisk.SectorList[i].Contents[j] := CFSector[j];
        CurrentDisk.SectorList[i].EndOffset := 255;
        inc(CurrCFSec);
        end;
      CurrentDisk.SectorsRead := true;
      end;
    //*
    //* Now we have all Sectors in Memory
    //*   so we can build FDR array
    //*
    for i := 0 to FDRList.Count - 1 do begin
      frmMain.Tellem(true,
         'FDR array entry ' + inttostr(i) + ' (' + inttostr(i+1) + ' files)');
      with CurrentDisk.Directory[i] do begin
        Address := strtoint(FDRList[i]);
        SS := '';
        for j := 0 to 9 do
          SS := SS + char(CurrentDisk.SectorList[Address].Contents[j]);
        //*
        Filename := SS;
        //* byte 12
        StatusFlag := ord(CurrentDisk.SectorList[Address].Contents[12]);
        //* byte 13
        RecordsPerSector :=
           ord(CurrentDisk.SectorList[Address].Contents[13]);
        //* bytes 14-15
        SectorCount :=
           (256 * ord(CurrentDisk.SectorList[Address].Contents[14]))
            + ord(CurrentDisk.SectorList[Address].Contents[15]);
        //* byte 16
        LastByteInLastSector :=
           ord(CurrentDisk.SectorList[Address].Contents[16]);
        //* byte 17
        RecordLength := ord(CurrentDisk.SectorList[Address].Contents[17]);
        //* bytes 18 and 19 but swapped!
        NumberRecordsSectors :=
           (256 * ord(CurrentDisk.SectorList[Address].Contents[19]))
            + ord(CurrentDisk.SectorList[Address].Contents[18]);
        //*frmMain.memDebug.Lines.Add(CurrentDisk.Directory[i].Filename);
        //*
        clusterpointer := 28;
        //*
        for k := 0 to 74 do begin
          Segmentlist[k].Cluster[0]
            := CurrentDisk.SectorList[Address].Contents[clusterpointer];
          Segmentlist[k].Cluster[1]
            := CurrentDisk.SectorList[Address].Contents[clusterpointer + 1];
          Segmentlist[k].Cluster[2]
            := CurrentDisk.SectorList[Address].Contents[clusterpointer + 2];
          //*  and decode it
          Nybbler(SegmentList[k]);
          clusterpointer := clusterpointer + 3;
          end; // for k
        end;  // with CurrentDisk.Directory
      end;   // for i
    //*
  finally
    FDRList.Free;                    //*<-- no memory leak
    Result := true;
  end;
//
end;

procedure TfrmMain.gridDiskBitMapDrawCell(Sender: TObject; ACol, ARow: Integer;
  Rect: TRect; State: TGridDrawState);
//**************************************************
//** main work is done by UsedSegment routine      *
//**************************************************
var Segnumber: integer;
begin
  if not ResizingGrid then begin
    SegNumber := ARow * gridDiskBitMap.ColCount + ACol;
    with (Sender as TDrawGrid) do begin
      with Canvas do begin
        //*
        //*  red for used, aqua for available...
        //*
        if UsedSegment(Segnumber) then Brush.Color := clRed
            else Brush.Color := clAqua;
        FillRect(Rect);
        end;  //* with Canvas
      end;  //*with Sender
    end; //* if not Resizing
end;

//******************************************************
//*                                                    *
//*    GUI flags Click responses etc.                  *
//*                                                    *
//******************************************************

procedure TfrmMain.actFormatFontExecute(Sender: TObject);
begin
   FontDialog.Font.Name := memFormatted.SelAttributes.Name;
   FontDialog.Font.Style := memFormatted.SelAttributes.Style;
   FontDIalog.Font.Size := memFormatted.SelAttributes.Size;
   if FontDialog.Execute then begin
     memFormatted.SelAttributes.Name := FontDialog.Font.Name;
     memFormatted.SelAttributes.Size := FontDialog.Font.Size;
     memFormatted.SelAttributes.Style := FontDialog.Font.Style;
   end;
end;

procedure TfrmMain.spinFormattedChange(Sender: TObject);
begin
  memFOrmatted.Font.size := spinFormatted.Value;
end;

procedure TfrmMain.cbFormattedFontClick(Sender: TObject);
begin
   memFormatted.Font.name := cbFormattedFont.FontName;
end;

procedure TfrmMain.ckBasicClick(Sender: TObject);
begin
   If CurrentFile.SectorsRead then
     CurrentFile.BASICFlag := not CurrentFile.BasicFlag;
end;

procedure TfrmMain.ckCompanionClick(Sender: TObject);
begin
   If CurrentFile.SectorsRead then
     CurrentFile.CompanionFlag := not CurrentFile.CompanionFlag;
end;

procedure TfrmMain.ckTIWriterClick(Sender: TObject);
begin
   If CurrentFile.SectorsRead then
     CurrentFile.TIWriterFlag :=
       not CurrentFile.TIWriterFlag;
end;

procedure TfrmMain.lbCatalogDblClick(Sender: TObject);
begin
  actDisplayFileExecute(Self);
end;

procedure TfrmMain.lbFilesDblClick(Sender: TObject);
begin
  DisplayTIDisketteImageFile(lbFiles.FileName);
end;

//******************************************************
//*    General Procedures & Complex actions            *
//******************************************************

procedure TfrmMain.MakeTIFilesHeader(var ThisFile: TTIFile);
      //****************************************
      //**  This is separated so it can be     *
      //**  called for a stand-alone file or   *
      //**  one extracted from a DOAD file.    *
      //****************************************
begin
  ThisFile.Header.HeaderString := 'TIFILES';
  ThisFile.Header.Sectors :=
    CurrentDisk.Directory[lbCatalog.ItemIndex].SectorCount;
  ThisFile.Header.StatusFlag :=
    CurrentDisk.Directory[lbCatalog.ItemIndex].StatusFlag;
  ThisFile.Header.RecSize :=
    CurrentDisk.Directory[lbCatalog.ItemIndex].Recordlength;

  Thisfile.Header.ProgType := (Thisfile.Header.StatusFlag and 1) = 1;

  if (Thisfile.Header.StatusFlag and 2) = 2 then
   Thisfile.Header.DataType := 'INT'
  else
   Thisfile.Header.DataType := 'DIS';

  Thisfile.Header.ProtectBit := (Thisfile.Header.StatusFlag and 8) = 8;

  if (Thisfile.Header.StatusFlag and 128) = 128 then
   Thisfile.Header.DataSize := 'VAR'
  else
   Thisfile.Header.DataSize := 'FIX';

  Thisfile.Header.FileType :=
    DecodeType(Thisfile.Header.StatusFlag, Thisfile.Header.RecSize);

  ThisFile.Header.RecordsPerSec :=
    CurrentDisk.Directory[lbCatalog.ItemIndex].RecordsPerSector;
  ThisFile.Header.EOFOffset :=
    CurrentDisk.Directory[lbCatalog.ItemIndex].LastByteInLastSector;
  ThisFile.Header.RecCount :=
    CurrentDisk.Directory[lbCatalog.ItemIndex].NumberRecordsSectors;

  MaxSectors := ThisFile.Header.Sectors - 1;
end;


procedure TfrmMain.ReadTIFilesSectors(Sender: TObject);
      //***********************************************
      //**   Written as separate subroutine           *
      //**   for development but unique to stand-     *
      //**   alone files and ALWAYS called when       *
      //**   one is opened.                           *
      //***********************************************
      //**   Treats the file as if it were on a       *
      //**   256 byte sector disk, and stores it as   *
      //**   a set of (continugous) whole sectors     *
      //**   which we fetch via blockread, treating   *
      //**   the file as a sort of stream.            *
      //***********************************************
var i: integer;
  FromF: file;
  NumRead: Integer;
  TIDummyBuf: array[1..128] of AnsiChar;
begin
  //*
  //*  open file and pre-read header 128 bytes
  //*
  AssignFile(FromF, CurrentFile.PCName);
  Reset(FromF, 1);
  //*
  //*  skip header if present
  //*
  if CurrentFile.Header.HeaderString = 'TIFILES' then
    BlockRead(FromF, TIDummyBuf, SizeOf(TIDummyBuf), NumRead)
  else begin
    Tellem(true, 'Not a TI Header');
    exit;
    end;
  //*
  if NumRead <> 128 then begin
    Tellem(true, 'Error Reading TI Header');
    CloseFile(FromF);
    exit;
    end;
  //*
  try
    //*
    for i := 1 to CurrentFile.Header.Sectors do begin
      Tellem(true, 'Reading TI Sector #...' + inttostr(i));
      try
        BlockRead(FromF, CurrentFile.SectorList[i - 1].Contents,
          SizeOf(CurrentFile.SectorList[i - 1].Contents), NumRead);
        CurrentFile.SectorList[i - 1].EndOffset := NumRead;
      except
        //*
      end; //*try
      end; //*for
  //*
  finally
    CloseFile(FromF);
  end;     //*try
  //*
  //* these checks are kind of kludgy, so I improve the odds of being
  //*    accurate by submitting only likely file types
  //*
  if CurrentFile.Header.ProgType then
    CurrentFile.BasicFlag := CheckforBasic;
  if not CurrentFile.Header.ProgType then begin
    CurrentFile.CompanionFlag := CheckforComp;
    CurrentFile.TIWriterFlag := CheckforWriter;
    end;
  //*
  ckBASIC.Checked := CurrentFile.BASICFlag;
  ckTIWriter.Checked := CurrentFile.TIWriterFlag;
  ckCompanion.Checked := CurrentFile.CompanionFlag;
  //*
  CurrentFile.SectorsRead := true;
  Tellem(true, 'Read ' + inttostr(i - 1) + ' Sectors');
  //*
  if CurrentFile.SectorsRead then begin
    //*
    ckBASIC.Enabled := CurrentFile.SectorsRead and
        (Copy(CurrentFile.Header.FileType, 1, 5) = 'IMAGE');
    ckCompanion.Enabled := CurrentFile.SectorsRead and
        (Copy(CurrentFile.Header.FileType, 1, 5) = 'IV254');
    ckTIWriter.Enabled := CurrentFile.SectorsRead and
        (Copy(CurrentFile.Header.FileType, 1, 5) = 'DV 80');
    //*
    DisplayingDiskImage := False;
    DisplayingDiskFile := False;
    DisplayingFile := True;
    DisplayTISector(0, CurrentFile.SectorList[0].Contents);
    PagesTI.ActivePage := tabFile;
    end;
end;

procedure TfrmMain.actDisplayfileExecute(Sender: TObject);
      //***********************************
      //** Extract and display a file     *
      //** from a DOAD disk image         *
      //***********************************
var Cluster,
    FileSeg,
    DiskSeg: integer;
    Ss: string;
    i, j: integer;
begin
  if lbCatalog.ItemIndex = -1 then begin
    ShowMessage('Please Select a File.');
    exit;
    end;
  Tellem(true, '');

  //* de-allocate sector array

  ClearCurrentFile;
  ClearTIFILESFileInfo;
  //*ClearDisketteInfo;     //*<-- do not
  //*ClearCurrentDiskette   //*<-- do not
  ClearMemos;

  CurrentFile.Name := lbCatalog.ItemPart(lbCatalog.ItemIndex, 0);
  CurrentFile.StandAlone := False;
  CurrentFile.SectorsRead := False;
  MakeTIFILESHeader(CurrentFile);

  //*  Display header information

  lblTIFileType.caption := CurrentFile.Header.FileType;

  rgProtected.Enabled := true;
  rgProgram.Enabled := true;

  if CurrentFile.Header.ProtectBit then rgProtected.ItemIndex := 0
    else rgProtected.ItemIndex := 1;

  if not CurrentFile.Header.ProgType then begin
    rgDataType.Visible := True;
    rgDataSize.Visible := True;
    label1.Visible := True;
    label3.Visible := True;
    edRecSizeHex.Visible := True;
    edRecSize.Visible := True;
    if CurrentFile.Header.DataSize = 'VAR' then begin
      label2.Visible := True;
      edRecCountHex.Visible := True;
      edRecCount.Visible := True;
      end;
    edRecPerSectorHex.Visible := True;
    edRecPerSector.Visible := True;
    rgProgram.ItemIndex := 1;
    if CurrentFile.Header.datasize = 'FIX' then
      rgDataSize.itemindex := 0 else rgDataSize.itemindex := 1;
    if CurrentFile.Header.dataType = 'DIS' then
      rgDataType.itemindex := 0 else rgDataType.itemindex := 1;
    edRecSizeHex.Text := inttohex(CurrentFile.Header.RecSize, 4);
    edRecCountHex.Text := inttohex(CurrentFile.Header.RecCount, 4);
    edRecPerSectorHex.Text := inttoHex(CurrentFile.Header.RecordsPerSec, 4);
    edRecSize.Value := CurrentFile.Header.RecSize;
    edRecCount.Value := CurrentFile.Header.RecCount;
    edRecPerSector.Value := CurrentFile.Header.RecordsPerSec;
    end
  else rgProgram.ItemIndex := 0;

  edTISectorCountHex.Text := inttoHex(CurrentFile.Header.Sectors, 4);
  edEOFOffsetHex.Text := inttoHex(CurrentFile.Header.EOFOffset, 4);
  edTISectorCount.Value := CurrentFile.Header.Sectors;
  edEOFOffset.Value := CurrentFile.Header.EOFOffset;

  //*  Re-allocate Sector Array and
  //*    Reset rest of CurrentFile Record

  SetLength(CurrentFile.SectorList, CurrentFile.Header.Sectors);
  CurrentFile.SectorsRead := false;
  CurrentFile.PCName := '';
  lblTIFileName.caption := CurrentFile.Name + ' NOT SAVED';
  ckTIFILESHeader.Checked := True;

  //*     now read sectors by iterating through
  //*          well not exactly iterating...
  //*          maybe we go backwards?

  FileSeg := 0;
  for Cluster := 0 to 74 do begin
    //* if at first empty cluster record then break
    if CurrentDisk.Directory[lbCatalog.ItemIndex]
           .SegmentList[Cluster].StartSeg
            = 0 then break;

    DiskSeg :=
       CurrentDisk.Directory[lbCatalog.ItemIndex]
        .SegmentList[Cluster].StartSeg;
    repeat
      Tellem(true,
         'Reading Sector ' + inttostr(DiskSeg)
           + ' into File Sector ' + inttostr(FileSeg));
      Move(CurrentDisk.SectorList[DiskSeg].Contents,
        CurrentFile.SectorList[Fileseg].Contents[0],
        sizeof(CurrentDisk.SectorList[DiskSeg].Contents));

      inc(DiskSeg);
      inc(FileSeg);
    until
      FileSeg =
       CurrentDisk.Directory[lbCatalog.ItemIndex]
         .SegmentList[Cluster].EndOffset + 1;

    end; //*for which increments Cluster

  //*  Need Sectors in memory for these checks

  if CurrentFile.Header.ProgType then
    CurrentFile.BasicFlag := CheckforBasic;
  if not CurrentFile.Header.ProgType then begin
    CurrentFile.CompanionFlag := CheckforComp;
    CurrentFile.TIWriterFlag := CheckforWriter;
    end;

  //*  Check boxed before setting SectorsRead

  ckBASIC.Checked := CurrentFile.BASICFlag;
  ckTIWriter.Checked := CurrentFile.TIWriterFlag;
  ckCompanion.Checked := CurrentFile.CompanionFlag;

  //*  Sectors now in memory

  CurrentFile.SectorsRead := true;
  Tellem(true, 'Read ' + inttostr(FileSeg + 1) + ' Sectors');
  if CurrentFile.SectorsRead then begin
    ckBASIC.Enabled := CurrentFile.SectorsRead and
        (Copy(CurrentFile.Header.FileType, 1, 5) = 'IMAGE');
    ckCompanion.Enabled := CurrentFile.SectorsRead and
        (Copy(CurrentFile.Header.FileType, 1, 5) = 'IV254');
    ckTIWriter.Enabled := CurrentFile.SectorsRead and
        (Copy(CurrentFile.Header.FileType, 1, 5) = 'DV 80');

  //*   create 128 byte string header

  TIFullHeaderBuf[1] := ord(chr(7));
  SS := 'TIFILES';                           //* prove me wrong
  Move(SS[1], TIFullHeaderBuf[2], Length(SS));

  TIFullHeaderBuf[9] := CurrentFile.Header.Sectors div 256;
  TIFullHeaderBuf[10] := CurrentFile.Header.Sectors mod 256;
  TIFullHeaderBuf[11] := CurrentFile.Header.StatusFlag;
  TIFullHeaderBuf[12] := CurrentFile.Header.RecordsPerSec;
  TIFullHeaderBuf[13] := CurrentFile.Header.EOFOffset;
  TIFullHeaderBuf[14] := CurrentFile.Header.RecSize;
  TIFullHeaderBuf[16] := CurrentFile.Header.RecCount div 256;
  TIFullHeaderBuf[15] := CurrentFile.Header.RecCount mod 256;
  for i := 17 to 128 do TIFullHeaderBuf[i] := ord(chr(0));

  //*  Display 16 bytes of Header

    for i := 0 to 1 do
      for j := 0 to 7 do
       frmMain.gridHeader.Cells[j, i] :=
         format(   '%.2x', [  ord(TIFullHeaderBuf[(i * 8) + j + 1])  ]   );

  //*   Then Display first Sector

  DisplayingDiskImage := False;
  DisplayingDiskFile := False;
  DisplayingFile := True;
  DisplayTISector(0, CurrentFile.SectorList[0].Contents);
  PagesTI.ActivePage := tabFile;
  end;
end;

procedure TfrmMain.PrintRichEdit;
var  LM, RM, TM, BM: integer;
begin
  //*
  //*  we may need these as program variables someday
  //*
  LM := 1000;
  TM := 1000;
  RM := 1000;
  BM := 1000;
  //*
  //*  do this here every time because I'm paranoid
  //*
  PrintMargins.TopLeft := Point(LM, TM);
  PrintMargins.BottomRight := Point(RM, BM);     //* These are treated as offsets
                                                 //* from the right and bottom
                                                 //* by the internal margin
                                                 //* routine
  SetRichEdMargins(memFormatted, PrintMargins);
  //*
  memFormatted.Print(CurrentFile.PCName);
end;

procedure TfrmMain.PrintFORTH;
var
    iI  : Integer;
    fOut: TextFile;
    sp: string;
    scrNum: integer;
begin
 sp := StringofChar(#32, 8);
 try
   AssignPrn(fOut);
   Rewrite(fOut);
   { Print the document title... }
   with Printer.Canvas.Font do begin
     Name := memFORTH.font.name;
     Size := 10;
     Style := [fsBold];
   end;
   scrnum := currentsector div 4;
   for iI := 0 to 6 do WriteLn(fOut,'');
   WriteLn(fOut, sp + edDiskName.text +
                 ' - Screen #' + inttostr(scrnum));
   for iI := 0 to 2 do WriteLn(fOut,'');
   { ...and the TStrings }
   Printer.Canvas.Font.Style := []; { The printer font style }
   for iI := 0 to memFORTH.Lines.Count - 1 do
     WriteLn(fOut, sp + MemFORTH.Lines[iI]);
 finally
   CloseFile(fOut);
 end;
end;

procedure TfrmMain.PrintDisketteImageCatalog;
var i, prow, printx, printy, colwid, HalfFactor, Thirdfactor: integer;
    msg: string;
begin
with Printer do begin
  //*    X = Horizontal position
  //*    Y = Vertical Position
  BeginDoc;
  Printer.Title := 'TI Catalog';
  //*
  //*  DiskName and Info
  //*
  //*
  if lbCatalog.Items.Count < 12 then begin
    //*
    //*    1-up routine
    //*
    canvas.font.name := lbCatalog.Font.Name;
    canvas.font.size := 10;
    canvas.Font.Style := canvas.Font.Style + [fsBold];
    canvas.font.color := clNavy;
    prow := pageheight div 40;
    printy := prow;
    colwid := pagewidth div 34;  //*about 1/4" for 1st column of labels
    //*
    msg := 'Disk Image Name: ' + CurrentDisk.Info.Diskname;
    canvas.textout(colwid, printy, msg);
    //*
    printy := printy + canvas.textheight(msg);
    canvas.font.color := clBlack;
    canvas.font.size := 8;
    canvas.Font.Style := canvas.Font.Style - [fsBold];
    if CurrentDisk.Info.Sides = 2 then msg := 'DS' else msg := 'SS';
    if CurrentDisk.Info.Density = 2 then
        msg := msg + 'DD' else msg := msg + 'SD';
    msg := msg + ' -- Total Sectors: ' + Inttostr(CurrentDisk.Info.SectorCount);
    canvas.textout(colwid, printy, msg);
    printy := printy + canvas.textheight(msg);
    msg := '        Total Files: ' + Inttostr(CurrentDisk.Info.FileCount);
    canvas.textout(colwid, printy, msg);
    //*canvas.font.size := 10;
    printy := printy + canvas.textheight(msg);
    msg := '   ======================';
    canvas.textout(colwid, printy, msg);
    printy := printy + canvas.textheight(msg);
    msg := '   name      size  type p';
    canvas.textout(colwid, printy, msg);
    printy := printy + canvas.textheight(msg);
    msg := '   ----------------------';
    canvas.textout(colwid, printy, msg);
    //*
    for i := 0 to lbCatalog.Items.Count - 1 do begin
          printy := printy + canvas.textheight(msg);
          msg := '   ' + lbCatalog.ItemPart(i, 0) +
             #32 + format('%3d', [strtoint(lbCatalog.ItemPart(i, 1))]) +
             #32 + lbCatalog.ItemPart(i, 2);
          canvas.textout(colwid, printy, msg);
          end;  //* if
    //*
    end //* if < 12
  else if  lbCatalog.Items.Count - 1 < 50 then begin
    //*
    //*  Catalog 2 across
    //*
    canvas.font.name := lbCatalog.Font.Name;
    canvas.font.size := 8;
    canvas.Font.Style := canvas.Font.Style + [fsBold];
    canvas.font.color := clNavy;
    prow := pageheight div 50;
    printy := prow;
    colwid := pagewidth div 34;  //*about 1/4" for 1st column of labels
    //*
    msg := 'Disk Image Name: ' + CurrentDisk.Info.Diskname;
    canvas.textout(colwid, printy, msg);
    //*
    printy := printy + canvas.textheight(msg);
    canvas.font.color := clBlack;
    canvas.font.size := 6;
    canvas.Font.Style := canvas.Font.Style - [fsBold];
    if CurrentDisk.Info.Sides = 2 then msg := 'DS' else msg := 'SS';
    if CurrentDisk.Info.Density = 2 then
      msg := msg + 'DD' else msg := msg + 'SD';
    msg := msg +
     ' -- Total Sectors: ' + Inttostr(CurrentDisk.Info.SectorCount)
      + '  --  Total Files: ' + Inttostr(CurrentDisk.Info.FileCount);
    canvas.textout(colwid, printy, msg);
    canvas.font.size := 6;
    printy := printy + canvas.textheight(msg);
     msg := '================================================';
     canvas.textout(colwid, printy, msg);
    printy := printy + canvas.textheight(msg);
    msg := 'name      size  type p    name      size  type p';
    canvas.textout(colwid, printy, msg);
    printy := printy + canvas.textheight(msg);
    msg := '------------------------------------------------';
    canvas.textout(colwid, printy, msg);
    //*
    i := 0;
    HalfFactor := (lbCatalog.Items.Count - 1) div 2;
    while i <= (HalfFactor - (lbCatalog.Items.Count mod 2)) do begin
      printy := printy + canvas.textheight(msg);
      msg := lbCatalog.ItemPart(i, 0) +
           #32 + format('%3d', [strtoint(lbCatalog.ItemPart(i, 1))]) +
           #32 + lbCatalog.ItemPart(i, 2) +
           #32 + #32 + #32 + #32 + lbCatalog.ItemPart(i + HalfFactor +
                    1 - (lbCatalog.Items.Count mod 2), 0) +
           #32 + format('%3d', [strtoint(lbCatalog.ItemPart(i + HalfFactor +
                    1 - (lbCatalog.Items.Count mod 2), 1))]) +
           #32 + lbCatalog.ItemPart(i + HalfFactor +
                    1 - (lbCatalog.Items.Count mod 2), 2);
      canvas.textout(colwid, printy, msg);
      inc(i);
      end; //*while
    //*
    if (lbCatalog.Items.Count mod 2) = 1 then begin //* tack last name on 2d column
      printy := printy + canvas.textheight(msg);
      msg := stringofchar(#32, 26)
           + lbCatalog.ItemPart(lbCatalog.Items.Count - 1, 0) +
           #32 + format('%3d',
             [strtoint(lbCatalog.ItemPart(lbCatalog.Items.Count - 1, 1))]) +
           #32 + lbCatalog.ItemPart(lbCatalog.Items.Count - 1, 2);
      canvas.textout(colwid, printy, msg);
      end;  //*if 1 left
    end //* if < 50
  else begin
    //*
    //*  Catalog 3 across
    //*
    canvas.font.name := lbCatalog.Font.Name;
    canvas.font.size := 8;
    canvas.Font.Style := canvas.Font.Style + [fsBold];
    canvas.font.color := clNavy;
    prow := pageheight div 60;
    printy := prow;
    colwid := pagewidth div 34;  //*about 1/4" for 1st column of labels
    //*
    msg := 'Disk Image Name: ' + CurrentDisk.Info.Diskname;
    canvas.textout(colwid, printy, msg);
    //*
    printy := printy + canvas.textheight(msg);
    canvas.font.color := clBlack;
    canvas.font.size := 5;
    canvas.Font.Style := canvas.Font.Style - [fsBold];
    if CurrentDisk.Info.Sides = 2 then msg := 'DS' else msg := 'SS';
    if CurrentDisk.Info.Density = 2 then
      msg := msg + 'DD' else msg := msg + 'SD';
    msg := msg +
        ' -- Total Sectors: ' + Inttostr(CurrentDisk.Info.SectorCount) +
        '  --  Total Files: ' + Inttostr(CurrentDisk.Info.FileCount);
    canvas.textout(colwid, printy, msg);
    canvas.font.size := 4;
    printy := printy + canvas.textheight(msg);
    msg := '==========================================================================';
    canvas.textout(colwid, printy, msg);
    printy := printy + canvas.textheight(msg);
    msg := 'name      size  type p    name      size  type p    name      size  type p';
    canvas.textout(colwid, printy, msg);
    printy := printy + canvas.textheight(msg);
    msg := '--------------------------------------------------------------------------';
    canvas.textout(colwid, printy, msg);
    //*
    i := 0;
    ThirdFactor := (lbCatalog.Items.Count - 1) div 3;
    while i <= (ThirdFactor - (lbCatalog.Items.Count mod 3)) do begin
      printy := printy + canvas.textheight(msg);
      msg := lbCatalog.ItemPart(i, 0) +
           #32 + format('%3d', [strtoint(lbCatalog.ItemPart(i, 1))]) +
           #32 + lbCatalog.ItemPart(i, 2) +
           #32 + #32 + #32 + #32 + lbCatalog.ItemPart(i + ThirdFactor +
                    1 - (lbCatalog.Items.Count mod 3), 0) +
           #32 + format('%3d', [strtoint(lbCatalog.ItemPart(i + ThirdFactor +
                    1 - (lbCatalog.Items.Count mod 3), 1))]) +
           #32 + lbCatalog.ItemPart(i + (2*ThirdFactor) +
                    1 - (lbCatalog.Items.Count mod 3), 2) +
           #32 + #32 + #32 + #32 + lbCatalog.ItemPart(i + (2*ThirdFactor) +
                    1 - (lbCatalog.Items.Count mod 3), 0) +
           #32 + format('%3d', [strtoint(lbCatalog.ItemPart(i + (2*ThirdFactor) +
                    1 - (lbCatalog.Items.Count mod 3), 1))]) +
           #32 + lbCatalog.ItemPart(i + (2*ThirdFactor) +
                    1 - (lbCatalog.Items.Count mod 3), 2);
      canvas.textout(colwid, printy, msg);
      inc(i);
      end; //*while
    //*
    if (lbCatalog.Items.Count mod 3) = 1 then begin //* tack on last name
      printy := printy + canvas.textheight(msg);
      msg := stringofchar(#32, 52)
           + lbCatalog.ItemPart(lbCatalog.Items.Count - 1, 0) +
           #32 + format('%3d',
             [strtoint(lbCatalog.ItemPart(lbCatalog.Items.Count - 1, 1))]) +
           #32 + lbCatalog.ItemPart(lbCatalog.Items.Count - 1, 2);
      canvas.textout(colwid, printy, msg);
      end; //*if one left
    //*
    if (lbCatalog.Items.Count mod 3) = 2 then begin //* tack on last 2 name
      printy := printy + canvas.textheight(msg);
      msg := stringofchar(#32, 26)
           + lbCatalog.ItemPart(lbCatalog.Items.Count - 2, 0) +
           #32 + format('%3d',
             [strtoint(lbCatalog.ItemPart(lbCatalog.Items.Count - 2, 1))]) +
           #32 + lbCatalog.ItemPart(lbCatalog.Items.Count - 2, 2) +
             lbCatalog.ItemPart(lbCatalog.Items.Count - 1, 0) +
           #32 + format('%3d',
             [strtoint(lbCatalog.ItemPart(lbCatalog.Items.Count - 1, 1))]) +
           #32 + lbCatalog.ItemPart(lbCatalog.Items.Count - 1, 2);
      canvas.textout(colwid, printy, msg);
      end;  //* if 2 left
    end; //* else
  //*
  EndDoc;
end;  //* with printer
end;

procedure TfrmMain.PrintCFList;
var i, prow, printx, printy, colwid, HalfFactor, Thirdfactor: integer;
    msg: string;
begin
  with Printer do begin
    //*    X = Horizontal position
    //*    Y = Vertical Position
    BeginDoc;
    Printer.Title := 'CF7 Catalog';
    //*
    //*  DiskName and Info
    //*
    //*
    if lbVolumes.Items.Count < 60 then begin
      //*
      //*    1-up routine
      //*
      canvas.font.name := cbFonts.FontName;
      canvas.font.size := 14;
      canvas.Font.Style := canvas.Font.Style + [fsBold];
      canvas.font.color := clNavy;
      prow := pageheight div 45;
      printy := prow;
      colwid := pagewidth div 17;  //*about 1/2" for 1st column of labels
      //*
      msg := 'CF Volume List             ';
      canvas.textout(colwid, printy, msg);
      //*
      printy := printy + canvas.textheight(msg);
      canvas.font.color := clBlack;
      canvas.font.size := 10;
      msg := '===========================';
      canvas.textout(colwid, printy, msg);
      printy := printy + canvas.textheight(msg);
      msg := 'vol# diskname    type  mnt ';
      canvas.textout(colwid, printy, msg);
      printy := printy + canvas.textheight(msg);
      msg := '---------------------------';
      canvas.textout(colwid, printy, msg);
      //*
      for i := 0 to lbVolumes.Items.Count - 1 do begin
            printy := printy + canvas.textheight(msg);
            msg := format('%4d', [strtoint(lbVolumes.ItemPart(i, 0))])
               + #32 + lbVolumes.ItemPart(i, 1)
                  + StringOfChar(#32, 12 - Length(lbVolumes.ItemPart(i, 1)))
               + lbVolumes.ItemPart(i, 2)
                  + StringOfChar(#32, 6 - Length(lbVolumes.ItemPart(i, 2)))
               + lbVolumes.ItemPart(i, 3)
                  + StringOfChar(#32, 6 - Length(lbVolumes.ItemPart(i, 3)));
            canvas.textout(colwid, printy, msg);
            end;  //* if
      //*
      end //* if < 60
    else if  lbVolumes.Items.Count - 1 < 120 then begin
      //*
      //*  Catalog 2 across
      //*
      canvas.font.name := lbVolumes.Font.Name;
      canvas.font.size := 14;
      canvas.Font.Style := canvas.Font.Style + [fsBold];
      canvas.font.color := clNavy;
      prow := pageheight div 60;
      printy := prow;
      colwid := pagewidth div 17;  //*about 1/2" for 1st column of labels
      //*
      msg := 'CF Volumes Col #1     CF Volumes Col #2';
      canvas.textout(colwid, printy, msg);
      //*
      printy := printy + canvas.textheight(msg);
      canvas.font.color := clBlack;
      canvas.font.size := 10;
      msg := '=========================== ===========================';
      canvas.textout(colwid, printy, msg);
      printy := printy + canvas.textheight(msg);
      msg := 'vol# diskname    type  mnt  vol# diskname    type  mnt ';
      canvas.textout(colwid, printy, msg);
      printy := printy + canvas.textheight(msg);
      msg := '--------------------------- ---------------------------';
      canvas.textout(colwid, printy, msg);
      //*
    i := 0;
    HalfFactor := (lbVolumes.Items.Count - 1) div 2;
    while i <= (HalfFactor - (lbVolumes.Items.Count mod 2)) do begin
      printy := printy + canvas.textheight(msg);
      msg := format('%4d', [strtoint(lbVolumes.ItemPart(i, 0))])
         + #32 + lbVolumes.ItemPart(i, 1)
            + StringOfChar(#32, 12 - Length(lbVolumes.ItemPart(i, 1)))
         + lbVolumes.ItemPart(i, 2)
            + StringOfChar(#32, 6 - Length(lbVolumes.ItemPart(i, 2)))
         + lbVolumes.ItemPart(i, 3)
            + StringOfChar(#32, 6 - Length(lbVolumes.ItemPart(i, 3)))
         + format('%4d', [strtoint(lbVolumes.ItemPart(i + halffactor, 0))])
         + #32 + lbVolumes.ItemPart(i + halffactor, 1)
            + StringOfChar(#32, 12 - Length(lbVolumes.ItemPart(i + halffactor, 1)))
         + lbVolumes.ItemPart(i + halffactor, 2)
            + StringOfChar(#32, 6 - Length(lbVolumes.ItemPart(i + halffactor, 2)))
         + lbVolumes.ItemPart(i + halffactor, 3)
            + StringOfChar(#32, 6 - Length(lbVolumes.ItemPart(i + halffactor, 3)));
      canvas.textout(colwid, printy, msg);
      inc(i);
      end; //*while
    //*
    if (lbVolumes.Items.Count mod 2) = 1 then begin //* tack last name on 2d column
      printy := printy + canvas.textheight(msg);
      msg := stringofchar(#32, 28)
         + format('%4d', [strtoint(lbVolumes.ItemPart(lbVolumes.Items.count - 1, 0))])
         + #32 + lbVolumes.ItemPart(lbVolumes.Items.count - 1, 1)
            + StringOfChar(#32, 12 - Length(lbVolumes.ItemPart(lbVolumes.Items.count - 1, 1)))
         + lbVolumes.ItemPart(lbVolumes.Items.count - 1, 2)
            + StringOfChar(#32, 6 - Length(lbVolumes.ItemPart(lbVolumes.Items.count - 1, 2)))
         + lbVolumes.ItemPart(lbVolumes.Items.count - 1, 3)
            + StringOfChar(#32, 6 - Length(lbVolumes.ItemPart(lbVolumes.Items.count - 1, 3)));
      canvas.textout(colwid, printy, msg);
      end;  //*if 1 left
    end // > 60
  else begin
    //*
    //*  Catalog 3 across
    //*
    canvas.font.name := lbVolumes.Font.Name;
    canvas.font.size := 14;
    canvas.Font.Style := canvas.Font.Style + [fsBold];
    canvas.font.color := clNavy;
    prow := pageheight div 60;
    printy := prow;
    colwid := pagewidth div 17;  //*about 1/4" for 1st column of labels
      //*
      msg := 'CF Volumes Col #1   CF Volumes Col #2   CF Volumes Col #1';
      canvas.textout(colwid, printy, msg);
      //*
      printy := printy + canvas.textheight(msg);
      canvas.font.color := clBlack;
      canvas.font.size := 10;
      msg := '=========================== =========================== ===========================';
      canvas.textout(colwid, printy, msg);
      printy := printy + canvas.textheight(msg);
      msg := 'vol# diskname    type  mnt  vol# diskname    type  mnt  vol# diskname    type  mnt ';
      canvas.textout(colwid, printy, msg);
      printy := printy + canvas.textheight(msg);
      msg := '--------------------------- --------------------------- ---------------------------';
      canvas.textout(colwid, printy, msg);
    //*
    i := 0;
    ThirdFactor := (lbVolumes.Items.Count - 1) div 3;
    while i <= (ThirdFactor - (lbVolumes.Items.Count mod 3)) do begin
      printy := printy + canvas.textheight(msg);
      msg :=
         format('%4d', [strtoint(lbVolumes.ItemPart(i, 0))])
         + #32 + lbVolumes.ItemPart(i, 1)
            + StringOfChar(#32, 12 - Length(lbVolumes.ItemPart(i, 1)))
         + lbVolumes.ItemPart(i, 2)
            + StringOfChar(#32, 6 - Length(lbVolumes.ItemPart(i, 2)))
         + lbVolumes.ItemPart(i, 3)
            + StringOfChar(#32, 5 - Length(lbVolumes.ItemPart(i, 3)))
         + format('%4d', [strtoint(lbVolumes.ItemPart(i + thirdfactor, 0))])
         + #32 + lbVolumes.ItemPart(i + thirdfactor, 1)
            + StringOfChar(#32, 12 - Length(lbVolumes.ItemPart(i + thirdfactor, 1)))
         + lbVolumes.ItemPart(i + thirdfactor, 2)
            + StringOfChar(#32, 6 - Length(lbVolumes.ItemPart(i + thirdfactor, 2)))
         + lbVolumes.ItemPart(i + thirdfactor, 3)
            + StringOfChar(#32, 5 - Length(lbVolumes.ItemPart(i + thirdfactor, 3)))
         + format('%4d', [strtoint(lbVolumes.ItemPart(i + (thirdfactor * 2), 0))])
         + #32 + lbVolumes.ItemPart(i + (thirdfactor * 2), 1)
            + StringOfChar(#32, 12 - Length(lbVolumes.ItemPart(i + (thirdfactor * 2), 1)))
         + lbVolumes.ItemPart(i + (thirdfactor * 2), 2)
            + StringOfChar(#32, 6 - Length(lbVolumes.ItemPart(i + (thirdfactor * 2), 2)))
         + lbVolumes.ItemPart(i + (thirdfactor * 2), 3)
            + StringOfChar(#32, 6 - Length(lbVolumes.ItemPart(i + (thirdfactor * 2), 3)));
      canvas.textout(colwid, printy, msg);
      inc(i);
      end; //*while
    //*
    if (lbVolumes.Items.Count mod 3) = 1 then begin //* tack on last name
      printy := printy + canvas.textheight(msg);
      msg := stringofchar(#32, 28)
         + format('%4d', [strtoint(lbVolumes.ItemPart(lbVolumes.Items.count - 1, 0))])
         + #32 + lbVolumes.ItemPart(lbVolumes.Items.count - 1, 1)
            + StringOfChar(#32, 12 - Length(lbVolumes.ItemPart(lbVolumes.Items.count - 1, 1)))
         + lbVolumes.ItemPart(lbVolumes.Items.count - 1, 2)
            + StringOfChar(#32, 6 - Length(lbVolumes.ItemPart(lbVolumes.Items.count - 1, 2)))
         + lbVolumes.ItemPart(lbVolumes.Items.count - 1, 3)
            + StringOfChar(#32, 6 - Length(lbVolumes.ItemPart(lbVolumes.Items.count - 1, 3)));
       canvas.textout(colwid, printy, msg);
      end; //*if one left
    //*
    if (lbVolumes.Items.Count mod 3) = 2 then begin //* tack on last 2 name
      printy := printy + canvas.textheight(msg);
      msg := stringofchar(#32, 56)
         + #32 + format('%4d', [strtoint(lbVolumes.ItemPart(lbVolumes.Items.count - 2, 0))])
         + lbVolumes.ItemPart(lbVolumes.Items.count - 2, 1)
            + StringOfChar(#32, 12 - Length(lbVolumes.ItemPart(lbVolumes.Items.count - 2, 1)))
         + lbVolumes.ItemPart(lbVolumes.Items.count - 2, 2)
            + StringOfChar(#32, 6 - Length(lbVolumes.ItemPart(lbVolumes.Items.count - 2, 2)))
         + lbVolumes.ItemPart(lbVolumes.Items.count - 2, 3)
            + StringOfChar(#32, 6 - Length(lbVolumes.ItemPart(lbVolumes.Items.count - 2, 3)))
         + #32 + format('%4d', [strtoint(lbVolumes.ItemPart(lbVolumes.Items.count - 1, 0))])
         + lbVolumes.ItemPart(lbVolumes.Items.count - 1, 1)
            + StringOfChar(#32, 12 - Length(lbVolumes.ItemPart(lbVolumes.Items.count - 1, 1)))
         + lbVolumes.ItemPart(lbVolumes.Items.count - 1, 2)
            + StringOfChar(#32, 6 - Length(lbVolumes.ItemPart(lbVolumes.Items.count - 1, 2)))
         + lbVolumes.ItemPart(lbVolumes.Items.count - 1, 3)
            + StringOfChar(#32, 6 - Length(lbVolumes.ItemPart(lbVolumes.Items.count - 1, 3)));
       canvas.textout(colwid, printy, msg);
      end;  //* if 2 left
    end; //* else
    //*
    EndDoc;
  end;  //* with printer
end;

procedure TfrmMain.actExtractFileExecute(Sender: TObject);
      //***********************************
      //** Write a file which has been    *
      //** extracted from a disk image    *
      //** as a stand-alone file.         *
      //***********************************
var i, j: integer;
    TempName: string;
    ToF: file;
    NumWritten: Integer;
begin
  tempname := CurrentFile.Name;
  for i := 0 to length(TempName) - 1 do
    case ord(TempName[i]) of
      58: TempName[i] := #95;
      3..47: TempName[i] := #95;
      //* etc.
      else TempName[i] := TempName[i];
      end; //* case
   //*
   SaveDialog.Filter := 'All Files|*.*';
   SaveDialog.DefaultExt := '';
   SaveDialog.FileName := TempName;
   SaveDialog.InitialDir := lbDirs.Directory;
   SaveDialog.Title := 'Save formatted text as RTF';
   if SaveDialog.Execute then begin
     AssignFile(ToF, SaveDialog.FileName);
       try
       ReWrite(ToF, 1);
       BlockWrite(ToF, TIFullHeaderBuf, Sizeof(TIFullHeaderBuf), NumWritten);
       for j := 0 to CurrentFile.Header.Sectors do
         BlockWrite(ToF, CurrentFile.SectorList[j].Contents,
                    SizeOf(CurrentFile.SectorList[j].Contents), NumWritten);
       finally
       CloseFile(Tof);
       end; //* try
     end; //*if
  lblTIFileName.caption := CurrentFile.Name + ' SAVED';
  Tellem(true, ExtractFileName(SaveDialog.FileName)
    + ' stored in ' + inttostr(CurrentFile.Header.Sectors) + ' sectors');
  pagesTI.ActivePage := tabFile;
end;

procedure TfrmMain.actTIFileOpenAccept(Sender: TObject);
      //****************************************
      //**  Open stand-alone TI PC file        *
      //****************************************
begin
  Tellem(true, '');
  //*pagesTI.ActivePage := tabFileInformation;

  //* de-allocate sector arrays and clean up generally

  ClearCurrentFile;
  ClearTIFILESFileInfo;
  ClearDisketteInfo;
  ClearCurrentDiskette;
  ClearMemos;

  //*  Read and check file header

  ckTIFilesHeader.Checked :=
   ReadTIFilesHeader(actTIFileOpen.Dialog.FileName, CurrentFile.Header);

  //*  Display header information

  lblTIFileType.caption := CurrentFile.Header.FileType;
  rgProtected.Enabled := True;
  rgProgram.Enabled := True;

  if CurrentFile.Header.ProtectBit then rgProtected.ItemIndex := 0
    else rgProtected.ItemIndex := 1;

  if not CurrentFile.Header.ProgType then begin
    rgDataType.Visible := True;
    rgDataSize.Visible := True;
    label1.Visible := True;
    label3.Visible := True;
    edRecSizeHex.Visible := True;
    edRecSize.Visible := True;
    if CurrentFile.Header.DataSize = 'VAR' then begin
      label2.Visible := True;
      edRecCountHex.Visible := True;
      edRecCount.Visible := True;
      end;
    edRecPerSectorHex.Visible := True;
    edRecPerSector.Visible := True;
    rgProgram.ItemIndex := 1;
    if CurrentFile.Header.datasize = 'FIX' then
      rgDataSize.itemindex := 0 else rgDataSize.itemindex := 1;
    if CurrentFile.Header.dataType = 'DIS' then
      rgDataType.itemindex := 0 else rgDataType.itemindex := 1;
    edRecSizeHex.Text := inttohex(CurrentFile.Header.RecSize, 4);
    edRecCountHex.Text := inttohex(CurrentFile.Header.RecCount, 4);
    edRecPerSectorHex.Text := inttoHex(CurrentFile.Header.RecordsPerSec, 4);
    edRecSize.Value := CurrentFile.Header.RecSize;
    edRecCount.Value := CurrentFile.Header.RecCount;
    edRecPerSector.Value := CurrentFile.Header.RecordsPerSec;
    end
  else rgProgram.ItemIndex := 0;

  edTISectorCountHex.Text := inttoHex(CurrentFile.Header.Sectors, 4);
  edEOFOffsetHex.Text := inttoHex(CurrentFile.Header.EOFOffset, 4);
  edTISectorCount.Value := CurrentFile.Header.Sectors;
  edEOFOffset.Value := CurrentFile.Header.EOFOffset;

  //*  Re-allocate Sector Array and
  //*    Reset rest of CurrentFile Record

  SetLength(CurrentFile.SectorList, CurrentFile.Header.Sectors);
  CurrentFile.SectorsRead := false;
  CurrentFile.PCName := actTIFileOpen.Dialog.FileName;
  lblTIFileName.caption := UpperCase(ExtractFileName(CurrentFile.PCName));

  Tellem(true, CurrentFile.PCName);
  if not ckTIFILESHeader.Checked then
    Tellem(true, '  Not a TI File?');

  if CurrentFile.Header.HeaderString = 'TIFILES' then begin
    ReadTIFilesSectors(Self);
    Tellem(true, 'TIFILES PC File in memory');
    CurrentFile.StandAlone := True;
    end;
end;

//******************************************************
//*  Routines to clear data and display                *
//******************************************************

procedure TfrmMain.ClearCurrentDiskette;
begin
  CurrentDisk.PCName := '';
  CurrentDisk.Info.Diskname := 'NO TI DISK';
  CurrentDisk.Info.SectorCount := 0;
  CurrentDisk.Info.SectorsPerTrack := 0;
  CurrentDisk.Info.DSRMark := '';
  CurrentDisk.Info.DiskProtected := false;
  CurrentDisk.Info.CFVolume := false;
  CurrentDisk.Info.TracksPerSide := 0;
  CurrentDisk.Info.Sides := 0;
  CurrentDisk.Info.Density := 0;
  CurrentDisk.SectorList := nil;
  CurrentDisk.Directory := nil;
  CurrentDisk.SectorsRead := False;
  CurrentDisk.Info.Bitmap := nil; //*<-- Uncomment to get errors!
end;

procedure TfrmMain.ClearCurrentFile;
begin
  CurrentFile.SectorList := nil;
  CurrentFile.Header.HeaderString := 'NOT  TI';
  CurrentFile.Header.Sectors := 0;
  CurrentFile.Header.RecCount := 0;
  CurrentFile.Header.StatusFlag := 0;
  CurrentFile.Header.FileType := '';
  CurrentFile.Header.DataSize := '';
  CurrentFile.Header.DataType := '';
  CurrentFile.Header.ProtectBit := false;
  CurrentFile.Header.RecSize := 0;
  CurrentFile.Header.ProgType := False;
  CurrentFile.Header.RecordsPerSec := 0;
  CurrentFile.Header.EOFOffset := 0;
  CurrentFile.Name := '';
  CurrentFile.PCName := '';
  CurrentFile.StandAlone := False;
  CurrentFile.SectorsRead := False;
  CurrentFile.BasicFlag := False;
  CurrentFile.CompanionFlag := False;
  CurrentFile.TIWriterFlag := False;
  CurrentFile.FormattedFlag := False;
end;

procedure TfrmMain.ClearDisketteInfo;
begin
  edDiskName.text := 'NO TI DISK';
  ckProtected.Checked := False;
  edSectorCount.Value := 0;
  edSectorsPerTrack.Value := 0;
  edTracksPerSide.Value := 0;
  edSides.Value := 0;
  edDensity.Value := 0;
  edUsed.Value := 0;
  ckProtected.checked := False;
  //*
  lbCatalog.Clear;
end;

procedure TfrmMain.CurrentCFVolumeClear;
begin
with CurrentCFVolume do begin
  VolumeNum := 0;
  Diskname := 'NO TI DISK';
  //SectorCount := 0;
  //SectorsPerTrack := 0;
  //DiskProtected := False;
  //TracksPerSide := 0;
  Sides := 0;
  Density := 0;
  end;
end;

procedure TfrmMain.ClearTIFILESFileInfo;
var i, j: integer;
begin
  //*
  //*  Reset all displays
  //*
  ckBASIC.Checked := false;
  ckCompanion.Checked := false;
  ckTIWriter.Checked := false;
  //*
  for i := 1 to GridSectors.RowCount - 1 do
    for j := 1 to gridSectors.ColCount - 1 do
      GridSectors.cells[j, i] := '';
  //*
  for i := 0 to GridHeader.RowCount - 1 do
    for j := 0 to gridHeader.ColCount - 1 do
      GridHeader.cells[j, i] := '';
  //*
  //*  Reset TIHEADER display
  //*
  ckTIFilesHeader.Checked := false;
  ckBASIC.Enabled := False;
  ckBASIC.Checked := False;
  ckCompanion.Enabled := False;
  ckCompanion.Checked := False;
  ckTIWriter.Enabled := False;
  ckTIWriter.Checked := False;
  //*
  edEOFOffset.Value := 0;
  edEOFOffsetHex.Text := '0';
  edRecCount.Visible := False;
  edRecCountHex.Visible := False;
  edRecPerSector.Visible := False;
  edRecPerSectorHex.Visible := False;
  edRecSize.Visible := False;
  edRecSizeHex.Visible := False;
  edTISectorCount.Value := 0;
  edTISectorCountHex.Text := '0';
  lblTIFileName.Caption := 'NO TI FILE';
  lblTIFileType.caption := '';
  rgDataType.Visible := False;
  rgDataSize.Visible := False;
  lblRecordSize.Visible := False;
  lblVarRecordCount.Visible := False;
  lblRecordsPerSector.Visible := False;
end;

procedure TfrmMain.ClearMemos;
begin
  memFormatted.Clear;
  memRawText.Clear;
end;

procedure TfrmMain.ShowSec(S: array of byte);
var i: integer;
    st: string;
begin
exit; //<<--- remove for debugging
  st := '';
  for i := 0 to sizeof(S) - 1 do
    if (S[i] > 31) and (S[i] < 127) then st := st + char(S[i])
    else st := st + '.';
  memdebug.lines.add(st);
end;

procedure TfrmMain.Tellem(D: boolean; S: string);
begin
  if D then lblmessageRed.caption := S;
  lblMessageRed.Repaint;
  //memDebug.lines.add(S);  <<-- uncomment for debugging
end;

//************************************
//** Display and Conversion Actions  *
//************************************

procedure TfrmMain.DisplayCFDisketteImage;
      //***********************************
      //** ReadCFImage function reads     *
      //** into CurrentDiskInfo and       *
      //** displays result                *
      //***********************************
var i, j: integer;
    SS: string;
begin
  screen.Cursor := crHourGlass;
  memFormatted.Clear;
  memFORTH.Clear;
  memFORTH.Visible := False;
  Tellem(true, 'Stage One');

  //*  Reset all displays, records and arrays

  Tellem(true, 'Clearing File Info');
  ClearTIFilesFileInfo;
  Tellem(true, 'Clearing Disk Display');
  ClearDisketteInfo;
  Tellem(true, 'Clearing Disk Array');
  ClearCurrentDiskette;

  //*  Get valid disk info

  Tellem(true, 'Opening Image.......');
  if ReadCFDiskImage then begin
    ckCFVolume.Checked := CurrentDIsk.Info.CFVolume;
    edDiskName.text := CurrentDisk.Info.DiskName;
    ckProtected.Checked := CurrentDisk.Info.DiskProtected;
    edSectorCount.Value := CurrentDisk.Info.SectorCount;
    edSectorsPerTrack.Value := CurrentDisk.Info.SectorsPerTrack;
    edTracksPerSide.Value := CurrentDisk.Info.TracksPerSide;
    edSides.Value := CurrentDisk.Info.Sides;
    edDensity.Value := CurrentDisk.Info.Density;

    //* Display Directory

    lbCatalog.Clear;
    Tellem(true, '   Building catalog...');
    for i := 0 to CurrentDisk.Info.FileCount - 1 do begin
       //* Construct Starting Segment List
       j := 0;  SS := '';
       repeat
          SS := SS
           + inttostr(CurrentDisk.Directory[i].SegmentList[j].StartSeg)
           + ','
           + inttostr(CurrentDisk.Directory[i].SegmentList[j].EndOffset);
          inc(j);
          SS := SS + #32;
       until ord(CurrentDisk.Directory[i].SegmentList[j].EndOffset) = 0;

       lbCatalog.AddLine
         ([CurrentDisk.Directory[i].FileName,
           inttostr(CurrentDisk.Directory[i].SectorCount),
           DecodeType(CurrentDisk.Directory[i].StatusFlag,
                      CurrentDisk.Directory[i].Recordlength),
           SS,
           inttostr(CurrentDisk.Directory[i].Address)]);
       end;

    Tellem(true,
       inttostr(CurrentDisk.Info.SectorCount) + ' sectors - '
       + inttostr(CurrentDisk.Info.FileCount) + ' files');
    Tellem(true, actTIFileOpen.Dialog.Filename);
    Tellem(true, 'TI Diskette Image in Memory');
    //*
    FORTHDisk := false;
    if lbCatalog.Items.Count > 2 then
     if TRIM(lbCatalog.ItemPart(2, 0)) = 'SYS-SCRNS' then
       FORTHDisk := true;
    ckFORTHDisk.checked := FORTHDisk;

    PagesTI.ActivePage := tabImage;

    DisplayTISector(0, CurrentDisk.SectorList[0].Contents);
    end;
  screen.Cursor := crDefault;
  lbCatalog.SetFocus;
  lbCatalog.ItemIndex := 0;
end;

procedure TfrmMain.DisplayTIDisketteImageFile(aFilename: TFilename);
      //***********************************
      //** BlockReads into CurrentDiskInfo*
      //** and displays result            *
      //***********************************
var i, j: integer;
    SS: string;
begin
  screen.Cursor := crHourGlass;
  memFORTH.Clear;
  memFORTH.Visible := False;
  memFormatted.Clear;
  Tellem(true, 'Stage One');

  //*  Reset all displays, records and arrays

  Tellem(true, 'Clearing File Info');
  ClearTIFilesFileInfo;
  Tellem(true, 'Clearing Disk Display');
  ClearDisketteInfo;
  Tellem(true, 'Clearing Disk Array');
  ClearCurrentDiskette;

  //*  Get valid disk info

  Tellem(true, 'Opening Image.......');
  if ReadTIImage(aFilename) then begin
    CurrentDIsk.Info.CFVolume := false;
    ckCFVolume.Checked := false;
    edDiskName.text := CurrentDisk.Info.DiskName;
    ckProtected.Checked := CurrentDisk.Info.DiskProtected;
    edSectorCount.Value := CurrentDisk.Info.SectorCount;
    edSectorsPerTrack.Value := CurrentDisk.Info.SectorsPerTrack;
    edTracksPerSide.Value := CurrentDisk.Info.TracksPerSide;
    edSides.Value := CurrentDisk.Info.Sides;
    edDensity.Value := CurrentDisk.Info.Density;
    //*
    edUsed.Value := 0;
    for j := 0 to CurrentDisk.Info.FileCount - 1 do
      edUsed.Value := edUsed.Value + CurrentDisk.Directory[j].SectorCount;
    //* Display Directory

    lbCatalog.Clear;
    Tellem(true, '   Building catalog...');
    for i := 0 to CurrentDisk.Info.FileCount - 1 do begin
       //* Construct Starting Segment List
       j := 0;  SS := '';
       repeat
          SS := SS
           + inttostr(CurrentDisk.Directory[i].SegmentList[j].StartSeg)
           + ','
           + inttostr(CurrentDisk.Directory[i].SegmentList[j].EndOffset);
          inc(j);
          SS := SS + #32;
       until ord(CurrentDisk.Directory[i].SegmentList[j].EndOffset) = 0;

       lbCatalog.AddLine
         ([CurrentDisk.Directory[i].FileName,
           inttostr(CurrentDisk.Directory[i].SectorCount),
           DecodeType(CurrentDisk.Directory[i].StatusFlag,
                      CurrentDisk.Directory[i].Recordlength),
           SS,
           inttostr(CurrentDisk.Directory[i].Address)]);
       end;

    Tellem(true,
       inttostr(CurrentDisk.Info.SectorCount) + ' sectors - '
       + inttostr(CurrentDisk.Info.FileCount) + ' files');
    Tellem(true, actTIFileOpen.Dialog.Filename);
    Tellem(true, 'TI Diskette Image in Memory');
    FORTHDisk := False;
    if lbCatalog.Items.Count > 2 then
      if TRIM(lbCatalog.ItemPart(2, 0)) = 'SYS-SCRNS' then
        FORTHDIsk := true;
    ckFORTHDisk.checked := FORTHDisk;

    PagesTI.ActivePage := tabImage;

    DisplayTISector(0, CurrentDisk.SectorList[0].Contents);
    end;
  screen.Cursor := crDefault;
  lbCatalog.SetFocus;
  lbCatalog.ItemIndex := 0;
end;

procedure TfrmMain.DisplayTISector(SecNum: integer; Sec: array of byte);
var i, gCol, gRow: integer;
begin
  //*
  //*  byte-by-byte add Buffer to memo and grid and to integer array
  //*
  edSectorNumHex.Text := inttohex(SecNum, 2);
  edSectorNum.Value := SecNum;
  MemRawText.Clear;
  gCol := 1;
  gRow := 1;
  ;;
  for i := 0 to 255 do begin
    gridSectors.Cells[gCol, gRow] :=
     format('%.2x',[ord(Sec[i])]);
    gridSectors.Cells[gCol + (gridSectors.ColCount div 2), gRow] := char(Sec[i]);
    //*
    memRawText.Text := memRawText.Text + char(Sec[i]);
    //*
    inc(gCol);
    if gCol > (gridSectors.ColCount div 2) then begin
      gcol := 1;
      inc(gRow);
      end;
    end;
  CurrentSector := SecNum;
end;

procedure TfrmMain.DisplayTISectorNoClear(SecNum: integer; Sec: array of byte);
var i: integer;
    line64: string;
begin
  line64:= '';
  for i := 0 to 63 do line64 := line64 + Char(Sec[i]);
  memForth.lines.add(line64);
  line64:= '';
  for i := 64 to 127 do line64 := line64 + Char(Sec[i]);
  memForth.lines.add(line64);
  line64:= '';
  for i := 128 to 191 do line64 := line64 + Char(Sec[i]);
  memForth.lines.add(line64);
  line64:= '';
  for i := 192 to 255 do line64 := line64 + Char(Sec[i]);
  memForth.lines.add(line64);
end;

procedure TfrmMain.actFormatBASICExecute(Sender: TObject);
  //************************************************************
  //**                                                         *
  //**   The ondisk format of BASIC programs mirrors their     *
  //**   in memory storage. That's unfortunate, to say the     *
  //**   least.                                                *
  //**                                                         *
  //**   First come line numbers (sorted decending, which is   *
  //**   perverse) paired with pointers to the matching        *
  //**   console memory addresses where the matching state-    *
  //**   ments were at when the program was SAVed and where    *
  //**   they will be when loaded again.                       *
  //**                                                         *
  //**   Next come the actual lines, tokenized (of course)     *
  //**   with a length byte preceeding the line, and a >00     *
  //**   to mark the line ending, as well. The statements      *
  //**   are in MEMORY ORDER, not sequential. Isn't that       *
  //**   interesting? so you hava to put the statements in     *
  //**   the same RELATIVE ORDER as the memory addresses of    *
  //**   the line numbers, and then of course, reverse the     *
  //**   overall order ot the line-number-statement com-       *
  //**   binations. A system only a computer could love!       *
  //**                                                         *
  //**   De-tokenizing is also not exactly straightforward:    *
  //**                                                         *
  //**   There's a special token to indicates that the next    *
  //**   XX bytes are a literal string, and that token is      *
  //**   followed by a length byte for the literal string.     *
  //**   The literal strings are all the non-tokenized code.   *
  //**                                                         *
  //**   There's another token to indicate a quoted string.    *
  //**   That token must be translated into a double-quote     *
  //**   character, then comes the length byte, and at the     *
  //**   end of the quoted string the closing double-quote     *
  //**   is only implied, so to list the program correctly     *
  //**   the closing double-quote must be suppplied.           *
  //**                                                         *
  //**   Another special token indicates that what follows     *
  //**   is a line number address, so one has to eat that      *
  //**   token, read the next two byte word and treat it as    *
  //**   a line number.                                        *
  //**                                                         *
  //**   Very messy.                                           *
  //**                                                         *
  //************************************************************
var BasicLineNumbers, BasicStatements, BasicStatementAddress: TStrings;
//*    LineNumBlock: array[0..3] of char;
    ALine, DebugLine: string;
    OldLineNumVal, LineNumVal, QuotedStringLength: integer;
    StatementNum, FileNum, ACell: integer;
    LineCount, SecNum, SecIndex, ThisStatementLength, i, j: integer;
    TempChar, TempAddress, TempLineNumber, TempStatement: string;

  //*  Utility Procedure to Advance File Pointers

  procedure NextByte(Count: integer);
  begin
   if SecIndex < 256 - Count then
     SecIndex := SecIndex + Count
   else begin                //*start of new sector
     SecNum := SecNum + 1;
     SecIndex := 0;
     end;
  end;

  //*  main procedure code

begin
  memFormatted.WantTabs := true;
  memFormatted.WordWrap := False;
  memFormatted.ScrollBars := ssVertical;
  memFormatted.Clear;
  //*memFormattedNew.lines.Clear;
  BasicLineNumbers := TStringList.Create;
  BasicStatements := TStringList.Create;
  BasicStatementAddress := TStringList.Create;
  try
    Screen.Cursor := crHourGlass;

    QuotedStringLength := 0;
    OldLineNumVal := 40000; //* dummy line number
    SecNum := 0;            //* start at the top
    SecIndex := 8;          //* reset index to start of Basic Line Number Table

    //* Construct Line Number Table

    while true do begin
      //* read line Number byte
      LineNumVal :=
       ((256 * ord(CurrentFile.SectorList[SecNum].Contents[SecIndex]))
             + ord(CurrentFile.SectorList[SecNum].Contents[SecIndex + 1]));
      //* as long as line numbers decrease
      if LineNumVal < OldLineNumVal then begin
       //* then read line number and memory address
       OldLineNumVal := LineNumVal;
       BasicLineNumbers.Append(format('%5d', [LineNumVal]));
       BasicStatementAddress.Append(format('%5d',
        [((256 * ord(CurrentFile.SectorList[SecNum].Contents[SecIndex + 2]))
              + ord(CurrentFile.SectorList[SecNum].Contents[SecIndex + 3]))- ProgramTableStart]));
       NextByte(4);
       end
      else break;  //* get out of loop, in the process
                   //*  saving Sector Number and Sector Index
    end;

    //*      We're now at the start of the statement table.
    //*      Each entry is a length byte, followed by the
    //*      tokenized statement, followed by a >00 byte.

    LineCount := BasicLineNumbers.Count; //* we use this
    for i := 1 to LineCount do begin

      if i <> 1 then NextByte(1); //* skip previous line terminating >00

      //* Don't really use this value but let's store it anyway

      ThisStatementLength :=
        ord(CurrentFile.SectorList[SecNum].Contents[SecIndex]);
      NextByte(1);                //* eat length byte

      //*  Detokenize line

      while ord(CurrentFile.SectorList[SecNum].Contents[SecIndex]) <> 0 do begin
        ALine := ALine +
          ThisToken(CurrentFile.SectorList[SecNum].Contents[SecIndex]);
        //DebugLine := DebugLine +
        //  CurrentFile.SectorList[SecNum].Contents[SecIndex];

        //*  if we're in a quoted string keep track,
        //*    check for the end of the string, and
        //*      insert close quote if at the end

        if QuotedStringLength > 0 then begin
          dec(QuotedStringLength);
          if QuotedStringLength = 0 then ALine := ALine + '"';
          end;

        //*  special behaviors for special tokens

        case ord(CurrentFile.SectorList[SecNum].Contents[SecIndex]) of
           199: begin ;  //* save string length bytes
                 NextByte(1);
                // DebugLine := DebugLine +
                //   CurrentFile.SectorList[SecNum].Contents[SecIndex];
                 QuotedStringLength :=
                   ord(CurrentFile.SectorList[SecNum].Contents[SecIndex]);
                end;

           200: begin   //* non-quoted string so just eat the length byte
                 NextByte(1);
                 //DebugLine :=
                 //  DebugLine + CurrentFile.SectorList[SecNum].Contents[SecIndex];
                end;

           201: begin   //*statement # reference
                 NextByte(1);  //*eat the >c9
                 //DebugLine :=
                 //  DebugLine + CurrentFile.SectorList[SecNum].Contents[SecIndex];

                 //*    calculate the number

                 StatementNum :=
                   256 * ord(CurrentFile.SectorList[SecNum].Contents[SecIndex]);
                 NextByte(1);
                 //DebugLine :=
                 //  DebugLine + CurrentFile.SectorList[SecNum].Contents[SecIndex];
                 StatementNum := StatementNum +
                   ord(CurrentFile.SectorList[SecNum].Contents[SecIndex]);

                 //*  Add the number to the line

                 ALine := ALine + format('%5d', [StatementNum]);
                 end;
           end;   //* of special cases
        //*
        //*  Now proceed to next char/token
        //*
        NextByte(1);
        end;
      //*
      //*  add the parsed line to the array of lines
      //*
      BasicStatements.Append(trim(ALine));
      ALine := '';
      //*
      //*  end of parsing loop
      //*
      end;
    //*
    //*   finished capturing line numbers, address offsets and statements
    //*
    Tellem(true, '   Formatted ' + inttostr(Linecount - 1) + ' lines...');
    //*
    //*  Now fold the arrays and display
    //*
    //*    I just use a pair of simple bubble sorts, since it's
    //*    an easy way to sort multiple arrays by one array.
    //*    If you want to make a fancy Shell Sort, be my guest.
    //*
    //*    first sort the line numbers and address offsets
    //*     into order ot the address offsets
    //*
    for i := 0 to LineCount - 1 do begin
       for j := i + 1 to LineCount - 1 do begin
          if strtoint(BasicStatementAddress[i])
            > strtoint(BasicStatementAddress[j]) then begin
             //*
             tempAddress := BasicStatementAddress[i];
             BasicStatementAddress[i] := BasicStatementAddress[j];
             BasicStatementAddress[j] := tempAddress;
             //*
             tempLineNumber := BasicLineNumbers[i];
             BasicLineNumbers[i] := BasicLineNumbers[j];
             BasicLineNumbers[j] := tempLineNumber;
          end; //* if
       end; //* j
    end; //* i
    //*
    //*    then sort all three arrays
    //*     into order ot the line numbers
    //*
    for i := 0 to LineCount - 1 do begin
       for j := i + 1 to LineCount - 1 do begin
          if strtoint(BasicLineNumbers[i])
            > strtoint(BasicLineNumbers[j]) then begin
             //*
             tempLineNumber := BasicLineNumbers[i];
             BasicLineNumbers[i] := BasicLineNumbers[j];
             BasicLineNumbers[j] := tempLineNumber;
             //*
             tempAddress := BasicStatementAddress[i];
             BasicStatementAddress[i] := BasicStatementAddress[j];
             BasicStatementAddress[j] := tempAddress;
             //*
             tempStatement := BasicStatements[i];
             BasicStatements[i] := BasicStatements[j];
             BasicStatements[j] := tempStatement;
          end; //* if
       end; //* j
    end; //* i
    //*
    //*  Now build the output memo
    //*
    for i := 0 to LineCount - 1 do begin
      //*
      //*  I could parse lines to clean double spaces
      //*    but that would destroy lines with strings or comments
      //*    which include spaces, so the heck with it
      //*
      {TempStatement := '';
      j := 1;
      while j <= length(trim(BasicStatements[i])) do begin
        tempchar := copy(trim(BasicStatements[i]), j, 1);
        tempStatement := TempStatement + Tempchar;
        if tempchar = #32 then
          while copy(trim(BasicStatements[i]), j, 1) = #32 do inc(j)
        else
          inc(j);
        end;
      BasicStatements[i] := TempStatement;}
      //*
      memFormatted.lines.Add(trim(BasicLineNumbers[i])
       + #32 + Trim(BasicStatements[i]));
      end;
    //*
    //*  Clean Up
    //*
    CurrentFile.FormattedFlag := True;
    //*
    pagesTI.ActivePage := tabFormatted;
    memFormatted.SetFocus;
    SendMessage(MemFormatted.Handle,EM_SETSEL, 0, 0);
    //*
  finally
    Screen.Cursor := crDefault;
    BasicLineNumbers.Free;
    BasicStatements.Free;
    BasicStatementAddress.Free;
  end; // try
  pagesTI.ActivePage := tabFormatted;
end;

procedure TfrmMain.actFormatCompanionExecute(Sender: TObject);
//******************************************************
//**                                                   *
//**  The 'companion' word processor used a custom     *
//**  ondisk format with character substitutions for   *
//**  spaces, linefeeds, etc. The file format is IV254 *
//**  so the first sector usually begins >FE for the   *
//**  record length, but the next byte is the          *
//**  companion record length, always one less. From   *
//**  there each record is a stream of text,           *
//**  substituted characters and control characters.   *
//**  End-of-Sector is marked by HEX FF.               *
//**                                                   *
//**  I translate it here using a method loosely based *
//**  on my old RITEFRND:A TI Basic program, which     *
//**  translated between companion and TI-Writer       *
//**  and vice-versa.                                  *
//**                                                   *
//**  Formatting for dot-matrix printers was pretty    *
//**  crude anyway, so we only handle and simulate     *
//**  the obvious stuff here. Besides, I long since    *
//**  lost my old 'companion' docs, and have created   *
//**  most of the stuff here from trial-and-error      *
//**  examiniation of sample texts.                    *
//**                                                   *
//******************************************************
var SecNum, SecIndex, RecLen: integer;
    var ParIndent, OldParIndent, TrapCount, TrapType, ParamCode: integer;
    TempString, TranString, CodeStr, ParamChar, ParamString: string;
    SelAttrLineFlag, CenterLine, BoldFlag, UnderFlag, DoNotEat, NoText: boolean;
    LMargin, RMargin, FormWidth, LineSpacing, MidLine: integer;

  //*  internal utility procedure to advance file pointers

  procedure NextByte(Count: integer);
  begin
   if SecIndex < 256 - Count then
     SecIndex := SecIndex + Count
   else begin                //*start of new sector
     SecNum := SecNum + 1;
     SecIndex := 0;
     end;
  end;

  //*  internal utility procedure Next Line

  procedure NextLine;
  begin
    if not SelAttrLineFlag then begin
      memFormatted.Lines.add(tempstring);
      end
    else begin
      SelAttrLineFlag := False;
      //* add the balance of the line
      memFormatted.SelText := TempString;
      memFormatted.Lines.Add('');
      end;
  end;

  //*  internal utility procedure apply paragraph setting

  procedure ApplySettings;
  begin
    //*with memFormatted.Paragraph do begin
    with memFormatted do begin
      if CenterLine then Alignment := taCenter else Alignment := taLeftJustify;
      //*FirstIndent := 4 * (LMargin + Parindent);    //*FIXME
      //*LeftIndent :=  - (4 * ParIndent);
      //*RightIndent :=  4 * RMargin;
      //*SetLineSpacing(lsrMultiple, LineSpacing);
      end; //*with
  end;

begin
  //*
  //*    conversions used in RITEFRND:A
  //*    ------------------------------
  //*    companion chr           TIW char
  //*    128                     32   SPACE
  //*    129                     13   CR   //* Ctl-C - Center Line
  //*    130     -------->       10   LF
  //*    130     <--------       13   CR   //* Ctl-L - LineFeed
  //*    131                     13   CR   //* Ctl-P - Paragraph
  //*    132                     13   CR   //* Ctl-M - MidLine (?para indent?)
  //*    136                     12   FF   //* Ctl-N - NewPage
  //*    137                     94   ^    //* Ctl-J - (a 'required' space)

  //*    additional codes not in RITEFRND:A
  //*    ----------------------------------

  //*    026                     Slash Left    ?
  //*    027                     Slash Right   ?

  //*    133                     CTL-9    start of underline (we use italics)
  //*    134                     CTL-0    end of underline

  //*    135                     CTL-I  - Might be ......?

  //*    138                     CTL-T  - no ascii param but no idea of meaning

  //*    139                     CTL-H  - Horizontal Tab

  //*    140                     REVISE - which can be followed
  //*                                     by a number of characters
  //*                                     and needs to be interpreted
  //*                                     first alpha after must be
  //*                                     in proper subset. first
  //*                                     non-numeric after the alpha
  //*                                     is straight text.

  //*    141                     CTL-A  - which can be followed
  //*                                     by literal numeric characters
  //*                                     representing an ASCII code
  //*                                     sent straight to the printer

  //*    142                     CTL-G  - Graphics Character (ascii)

  //*    143                     CTL-B  - Might be overstrike in Epson?

  //*  **************************************************************

  //*  we're just going to build paragraphs, not lines,
  //*  so we let the control do the wrapping

  memFormatted.WantTabs := true;
  memFormatted.WordWrap := true;
  memFormatted.ScrollBars := ssVertical;
  memFormatted.Clear;

  //*  some companion 'defaults'

  ParIndent := 5;
  LMargin := 8;
  RMargin := 8;
  FormWidth := 80;
  MidLine := 32;
  LineSpacing := 1;
  CenterLine := False;
  //* apply them
  ApplySettings;

  //*  Lower the Trap Flags

  SelAttrLineFlag := False;
  BoldFlag := False;          //* not used
  UnderFlag := False;         //* not used
  DoNotEat := False;

  NoText := True;

  //*  Clear the traps

  TrapType := 0;
  TrapCount := 0;
  TempString := '';
  TranString := '';
  CodeStr := '';
  ParamChar := '';
  ParamString := '';

  Screen.Cursor := crHourGlass;

  //*  start of byte-by-byte conversion

  for SecNum := 0 to CurrentFile.Header.Sectors - 1 do begin
    SecIndex := 1;
    Tellem(true, '   converting sector '
       + inttostr(SecNum) + ' byte ' + inttostr(SecIndex));

    //* Read IV254 true Length Byte - never really used

    RecLen := ord(CurrentFile.SectorList[SecNum].Contents[SecIndex]);

    //* skip companion length byte which is always one less

    NextByte(1);

    //*  Record #0 always starts with 4 bytes of character counting

    //*if SecNum = 0 then NextByte(4);

    //*  Other long files (parts of multi-part or chained documents) have an
    //*       even longer header, but I'm going to blissfully ignore that

    while ord(CurrentFile.SectorList[SecNum].Contents[SecIndex]) <> 255 do begin

      //*  Special Cases

      if TrapCount > 0 then begin

        //*  in-trap processing

        Case TrapType of
          //* ASCII Escape Code
           0: begin
              if ((ord(CurrentFile.SectorList[SecNum].Contents[SecIndex])
                     >= 48)
                 and (ord(CurrentFile.SectorList[SecNum].Contents[SecIndex])
                     <= 57))
                  then inc(TrapCount)
                  else DoNotEat := true;
              end;
          //* Graphics ASCII code
          1: begin
             if ((ord(CurrentFile.SectorList[SecNum].Contents[SecIndex])
                     >= 48)
                 and (ord(CurrentFile.SectorList[SecNum].Contents[SecIndex])
                     <= 57)) then begin
               Inc(TrapCount);
               CodeStr := CodeStr +
                 char(CurrentFile.SectorList[SecNum].Contents[SecIndex]);
               end
               else begin
                  TempString := Tempstring + chr(strtoint(CodeStr));
                  DoNotEat := true;
                  end;
             end;
          //* Revise    65-90 = alpha
          2: begin
             if TrapCount = 99 then begin
                ParamChar := char(CurrentFile.SectorList[SecNum].Contents[SecIndex]);
                TrapCount := 2;
                end
             else begin  //*we already have param so we're looking for numerics
                if ((ord(CurrentFile.SectorList[SecNum].Contents[SecIndex])
                         >= 48)
                  and (ord(CurrentFile.SectorList[SecNum].Contents[SecIndex])
                         <= 57)
                  and (ord(CurrentFile.SectorList[SecNum].Contents[SecIndex])
                         <> 45))
                  then begin
                  ParamString := ParamString
                      + char(CurrentFile.SectorList[SecNum].Contents[SecIndex]);
                  inc(TrapCount);
                  end
                  else begin
                  if paramstring <> '' then
                     ParamCode := strtoint(Paramstring)
                  else ParamCode := 0;

                  //*  Act on (certain) Parameters  (no error checking!!!)

                   case ord(ParamChar[1]) of
                     //*LMargin
                     76, 108: if ParamCode = 0 then
                                    LMargin := 8
                              else LMargin := ParamCode;
                     //*Midline
                     77, 109: if ParamCode = 0 then
                                    Midline := 32
                              else MidLine := ParamCode;
                     //*ParIndent
                     80, 112: if ParamCode = 0 then
                                    ParIndent := 5
                              else ParIndent := ParamCode;
                     //*RMargin
                     82, 114: if ParamCode = 0 then
                                    RMargin := 8
                              else RMargin := ParamCode;
                     //*Line Spacing
                     83, 115: if ParamCode = 0 then
                                    LineSpacing := 1
                              else LineSpacing := ParamCode;
                   end;

                  DoNotEat := true;
                  end;
                end;
             end;
          //* UnderLine
          3: begin
               if (ord(CurrentFile.SectorList[SecNum].Contents[SecIndex])
                <> 134) then begin

                  //*  inside trap we handle spaces only!!!!!

                  case ord(CurrentFile.SectorList[SecNum].Contents[SecIndex]) of
                    128: TempString := TempString + #32;
                    137: TempString := TempString + #32;
                  else tempstring := tempstring
                    + char(CurrentFile.SectorList[SecNum].Contents[SecIndex]);
                  end;
                  inc(TrapCount);
                  end
                else begin
                  memFormatted.SelText := TempString;
                  TempString := '';
                  memFormatted.SelAttributes.Style := [];
                  SelAttrLineFlag := True;
                  end;
              end;
          end;  //*case

        //* always

        Dec(TrapCount);
        if not DoNotEat then NextByte(1) else DoNotEat := False;
        end

      //*  normal processing

      else begin
        case ord(CurrentFile.SectorList[SecNum].Contents[SecIndex])of

           0: ;  //* no op


           //*  the bold slashes ??

           26: ;

           27: ;


           //*  'normal' text, not within one of the traps below

           32 .. 127: begin
                       if NoText then NoText := False;
                       tempstring := tempstring    //* straight ASCII
                       + char(CurrentFile.SectorList[SecNum].Contents[SecIndex]);
                       end;

           //*  various conversions, and special traps to read the
           //*     parameters of certain control codes and apply
           //*     to memFormatted.SelAttributes


           128: tempstring := tempstring + #32;        //* the space character


           //*   We're going to center as formatted Rich Text, rather than
           //*   counting the form width, line width, subtracting, etc...

           129: begin                                  //* Center Line

                  //*  only if not at top of file????

                  if not NoText then NextLine;

                  CenterLine := True;                  //* we set this flag
                  ApplySettings;                       //* Set up for Next Para
                  CenterLine := False;                 //* Clear the Flag
                  tempstring := '';                    //* Clear the TempString
                  end;

           130: begin                                  //* LineFeed
                  NextLine;

                  OldParIndent := ParIndent;           //* Save Value
                  ParIndent := 0;                      //* temp change
                  ApplySettings;                       //* Set up for Next Para
                  ParIndent := OldParIndent;           //* restore Value
                  tempstring := '';                    //* Clear the TempString
                  end;

           131:  begin                                 //* Paragraph
                  NextLine;

                  ApplySettings;                       //* Set up for Next Para
                  memFormatted.Lines.add('');          //* we add extra line
                  tempstring := '';                    //* Clear the TempString
                  end;

           132:  begin                                 //* MidLine Indent
                  NextLine;

                  ApplySettings;                       //* Set up for Next Para
                  tempstring
                    := stringofchar(#32, MidLine);     //* we honor MidLine
                  end;


           //*  We Italicize rather than underline.


           133: begin                                   //* Underline
                if TempString <> '' then begin
                  memFormatted.SelText := Tempstring;   //* dump line so far
                  TempString := '';
                  end;
                memFormatted.SelAttributes.Style := [fsItalic]; //*  We Italicize
                SelAttrLineFlag := True;
                TrapType := 3;                          //* Set type to underline
                TrapCount := 1;                         //* set up trap
                end;

           //* since it's caught in the trap we should never see this
           134: ;                                       //* end underline

           135: ;                                       //* unknown  ?

           136:  begin                                  //* New Page
                  NextLine;

                  ApplySettings;                        //* Set up for Next Para
                  tempstring := '';
                  end;

           //* Ctl-J
           137: tempstring := tempstring + #32;         //* a 'required' space
           //* Ctl-T
           138: ;                                       //* unknown
           //* Ctl-H
           139: tempstring := tempstring + #9;          //* Horizintal tab


           //*   The revise command... we have to parse the code and paramater(s)
           //*      and take appropriate action for each. or we store general
           //*      formatting and apply it only when we add the tempstring

           //*      Code   Min     Max     Default
           //*      W      40      132     80      form width
           //*      F      22      132     66      form length
           //*      L      0       35      8       left margin
           //*      R      0       25      8       right margin
           //*      T      2       34      6       top margin
           //*      B      2       20      6       bottom margin
           //*      S      1       5       1       (line) spacing
           //*      P      -20     20      5       Paragraph Indent
           //*      M      -20     60      32      Midline Indentation
           //*                                      (not true centering!)
           //*      H      1       8       1       Horizontal Tab Group
           //*                                      (tabs not defined in the
           //*                                       document, a weakness)
           //*      J      0       9       0       Justification
           //*                                      (never implemented)

           140: begin;
                 TrapType := 2;
                 ParamChar := '';                  //* Parameter Code
                 ParamString := '';                //* Parameter Value
                 TrapCount := 99;                  //* artificial count as flag
                 end;


           141: begin                              //* ASCII (printer escape)
                 TrapType := 0;
                 TrapCount := 1;                   //* look for the ascii
                 end;


           142: begin;                             //* GRAPHICS ascii code
                 TrapType := 1;
                 TrapCount := 1;
                 CodeStr := '';
                 end;

           143: ;                                  //* unknown Ctl-B

           255: Break;                             //* end of sector marker

           else

           //* everything else
           //* is really unknown so
           //* just skip the byte

             tempstring := tempstring;
           end;  //* case

        NextByte(1);
        end; //*else
      end;

    end; //*of document

  NextLine;

  if CenterLine then CenterLine := False;
  TempString := '';

  //*  we got here so

  CurrentFile.FormattedFlag := true;


  if CurrentFile.FormattedFlag then begin

    Tellem(true, '  conversion complete');
    pagesTI.ActivePage := tabFormatted;
    memFormatted.SetFocus;
    SendMessage(MemFormatted.Handle,EM_SETSEL, 0, 0);
    end
  else
    Tellem(true, '  conversion cancelled');
  screen.Cursor := crDefault;
  pagesTI.ActivePage := tabFormatted;
end;

procedure TfrmMain.actFormatDV80Execute(Sender: TObject);
//******************************************************
//**  DV80, DVany, DFix Display, formatted or not      *
//**                                                   *
//**  TI Writer is essentially a line-by-line text     *
//**  editor  which pre-formats its own output,        *
//**  so I do not bother with constructing             *
//**  formatted paragraphs. I'll try to note and       *
//**  capture italic and bold (underscore and over-    *
//**  strike) codes, and reflect the most basic line   *
//**  formatting, like spacing and indentation.        *
//******************************************************
var SecNum, SecIndex, RecLen, i: integer;
    TIUnderscoreStart, TIOverstrikeStart: integer;
    TIUnderscoreStop, TIOverstrikeStop: integer;
    FormWidth, CenterCount, LMargin, RMargin, ParIndent, LineSpacing: integer;
    TempString: string;
    TIUnderScoreFlag, TIOverStrikeFlag, TICenterLineFlag: boolean;
  //****************************************************
  //* some subfunctions for TI Writer formatting       *
  //*     only get called when TIWriter box is checked *
  //****************************************************
  //**  TI Writer sub procedure to format and add      *
  //**  each individual line                           *
  //****************************************************
  procedure AddLine(var s: string);
  var i, j: integer;
  t: string;
  begin
  TIUnderScoreStart := -1;
  TIUnderScoreStop := -1;
  TIOverStrikeStart := -1;
  TIOverStrikeStop := -1;
  //*TIUnderScoreFlag := False;
  //*TIOverStrikeFlag := False;
  t := '';

  //*  parse the line

  for i := 1 to length(s) do begin
    case ord(s[i]) of
      {13: begin
           if TIOverstrikeflag then begin
            TIOverstrikeFlag := False;
            TIOverstrikeStop := i - 1;
            end
           else if TIUnderScoreFlag then begin
            TIUnderscoreFlag := False;
            TIUnderScoreStop := i - 1;
            end;
           end; }
      30: ;                                //* no op
      31: ;                                //* no op
      32: begin
           if TIOverstrikeflag then begin
            TIOverstrikeFlag := False;
            TIOverstrikeStop := i;
            end
           else if TIUnderScoreFlag then begin
            TIUnderscoreFlag := False;
            TIUnderScoreStop := i;
            end;
           t := t + s[i];
           end;
      33..37: t := t + s[i];
      38: begin
           TIUnderScoreFlag := True;
           TIUnderscoreStart := i;
           end;
      39..63: t := t + s[i];
      64: begin
           TIOverStrikeFlag := True;
           TIOverStrikeStart := i;
           end;
      65..93: t := t + s[i];
      94: if TIOverStrikeFlag or TIUnderScoreFlag
             then t:= t + #32
           else t := t+ s[i];
      95..126: t := t + s[i];
      end; //*case
    end; //* for
   TIOverstrikeFlag := False;
   TIOverstrikeStop := length(t);
   TIUnderscoreFlag := False;
   TIUnderScoreStop := length(t);

   for j := 1 to LineSpacing do memformatted.lines.Add('');
   if CenterCount > 0 then begin
     Memformatted.SelText := StringofChar(#32,
            (FormWidth - length(t)) div 2);
     Dec(CenterCount);
     end;

   for i := 1 to length(t) do begin

       //*  check for formatting turn-on

       if i = TIOverStrikeStart then
          memformatted.SelAttributes.Style :=
            memformatted.SelAttributes.Style + [fsBold];
       if i = TIUnderscoreStart then
          memformatted.SelAttributes.Style :=
            memformatted.SelAttributes.Style + [fsItalic];

       //*  add char

       memFormatted.Seltext := memFormatted.SelText + t[i];
       //*memFormatted.Seltext := t[i];  //*old form with RichEdit98

       //*  check for turn-off

       if i = TIOverStrikeStop then
          memformatted.SelAttributes.Style :=
            memformatted.SelAttributes.Style - [fsBold];
       if i = TIUnderscoreStop then
          memformatted.SelAttributes.Style :=
            memformatted.SelAttributes.Style - [fsItalic];

       end;
   end; //*sub procedure
   //** ***************************************************
   //**  sub function to process line for TIWriter codes  *
   //******************************************************
   function ProcessTIWriterLine(var s: string): boolean;
   begin
     Result := True;
     if copy(s, 1, 1) = '.' then begin
       Result := False;
       //*  ShowMessage(s);
       //*  ShowMessage(Copy(s, 4, length(s) - 3));

       //*  act on formatting

       if copy(s, 1, 3) = '.CE' then
         if length(s) > 3 then
           try
           CenterCount := strtoint(trim(Copy(s, 4, length(s) - 3)))
           except
           CenterCOunt := 1;
           end;
       if copy(s, 1, 3) = '.LM' then
         if length(s) > 3 then
           try
           LMargin := strtoint(trim(Copy(s, 4, length(s) - 3)))
           except
           LMargin := 8;
           end;
       if copy(s, 1, 3) = '.RM' then
         if length(s) > 3 then begin
           try
           RMargin := strtoint(trim(Copy(s, 4, length(s) - 3)))
           except
           RMargin := 8;
           end;

           //*  this is a kludgey approximation

           FormWidth := RMargin - LMargin;
           end;
       if copy(s, 1, 3) = '.LS' then
         if length(s) > 3 then
           try
           LineSpacing := strtoint(trim(Copy(s, 4, length(s) - 3)))
           except
           LineSpacing := 1;
           end;
       if copy(s, 1, 3) = '.IN' then
         if length(s) > 3 then
           try
           ParIndent := strtoint(trim(Copy(s, 4, length(s) - 3)))
           except
           ParIndent := 5;
           end;
       end; //*if
   //*ShowMessage('LeftMargin = ' + inttostr(LMargin) + #10 + #13 +
   //*            'RightMargin = ' + inttostr(RMargin) + #10 + #13 +
   //*            'ParIndent = ' + inttostr(ParIndent) + #10 + #13 +
   //*            'LineSpacing = ' + inttostr(LineSpacing) + #10 + #13 +
   //*            'CenterCount = ' + inttostr(CenterCount));
   end; //*sub function

//*  **************************************************
//*  *                main procedure code             *
//*  **************************************************

begin
//******************************************************
//**                                                   *
//**  Translating TI_Writer and other DV80 files       *
//**  into straight PC text is relatively simple.      *
//**  VAR records have use length byte for each record *
//**  followed by the actual data. VAR files also use  *
//**  a length byte of >FF to mark end of data in a    *
//**  given sector. One just reads the lines and       *
//**  stacks them up.                                  *
//**                                                   *
//**  If the TIWriter File flag is set we look for     *
//**  and act on the 'dot' codes and parse the lines   *
//**  for underscores, circumflex marks and other      *
//**  TIWriter specific stuff. Formatting for dot-     *
//**  matrix printers was pretty crude anyway, so we   *
//**  only handle and simulate the obvious stuff here. *
//**                                                   *
//**  For DF records, we just do the math. There       *
//**  is no end of sector marker, so if another record *
//**  won't fit we just skip to the next sector.       *
//**  Otherwise it's s similar process to DV80 files.  *
//**  Unfortunately, we usually get some trash         *
//**  text at the end of the FIX file when there is    *
//**  a lot of unused space in the last sector.        *
//**                                                   *
//******************************************************

  TIOverStrikeFlag := False;
  TIUnderScoreFlag := False;
  TICenterLineFlag := False;    //* not used

  ParIndent := 5;
  LMargin := 8;
  RMargin := 8;
  CenterCount := 0;
  FormWidth := 80;
  LineSpacing := 1;

  //*  we're doing line-by line formatting here, so no wrap

  memFormatted.WantTabs := true;
  memFormatted.WordWrap := False;
  memFormatted.ScrollBars := ssBoth;
  memFormatted.Clear;

  Screen.Cursor := crHourGlass;
  //*  DV80 TIWriter type files
  if CurrentFile.Header.DataSize = 'VAR' then begin
    //*  VARiable routine
    for SecNum := 0 to CurrentFile.Header.Sectors - 1 do begin
      Tellem(true, '   converting sector '
                 + inttostr(SecNum) + '..........');
      //* reset index
      SecIndex := 0;
      //* read 1st length byte
      RecLen := ord(CurrentFile.SectorList[SecNum].Contents[SecIndex]);
      //*  read lines
      while ((RecLen <> $FF) and (recLen <> 0)) do begin
        //* $FF is end of sector marker
        //* increment sector pointer
        inc(SecIndex);
        //* read line (record)
        TempString := '';

        for i := 1 to RecLen do begin
          TempString := TempString
            + char(CurrentFile.SectorList[SecNum].Contents[SecIndex]);
          inc(SecIndex);
          end;

        //*  we have the line now what do we do with it?

        if (not CurrentFile.TIWriterFlag) then

          //*  We're not formatting so just dump the line

          memFormatted.Lines.Append(TempString)

          //*  if we are formatting then test the line

        else if ProcessTIWriterLine(tempstring) then

          //*  if it's a 'real' text line and
          //*     not a formatting dot-command then

          AddLine(TempString);

        //* read next length byte to get ready for next line

        RecLen := ord(CurrentFile.SectorList[SecNum].Contents[SecIndex]);
        end;  //* [while] end of records in Sector
      RecLen := 0;
      end;  //* [for] of sectors in file
    CurrentFile.FormattedFlag := true;
    end  //* if VARiable

  else begin  //*DF files
    if MessageDlg('This is a DISplay FIXed file, which is usually an object file.'
                  + #10 + 'Text display may not make sense. Proceed?',
                  mtConfirmation, [mbYes, mbNo], 0) = mrYes then begin
      RecLen := CurrentFile.Header.RecSize;
      for SecNum := 0 to CurrentFile.Header.Sectors - 1 do begin
        Tellem(true, '   converting sector '
                 + inttostr(SecNum) + '..........');
        //* reset index
        SecIndex := 0;
        while (256 - SecIndex) > RecLen do begin
          TempString := '';
          //* read line (record)
          for i := 1 to RecLen do begin
            TempString := TempString
              + char(CurrentFile.SectorList[SecNum].Contents[SecIndex]);
            inc(SecIndex);
            end;
          //* Append Output
          try memFormatted.Lines.Append(TempString); except end;
          end;  //* [while] end of records in Sector
        end;  //* [for] of sectors in file
      CurrentFile.FormattedFlag := true;
      end; //*if warn.execute
    end; //* else

  if CurrentFile.FormattedFlag then begin
    Tellem(true, '  conversion complete');
    pagesTI.ActivePage := tabFormatted;
    memFormatted.SetFocus;
    SendMessage(MemFormatted.Handle,EM_SETSEL, 0, 0);
    end
  else
    Tellem(true, '  conversion cancelled');
  screen.Cursor := crDefault;
  pagesTI.ActivePage := tabFormatted;
end;

//******************************************************
//*  Change Sector Number response                     *
//******************************************************

procedure TfrmMain.spinSectorUpClick(Sender: TObject);
begin
  if strtoint('$' + edSectorNumHex.Text) < MaxSectors
    then begin
    if ckFORTHDisk.checked then CurrentSector := CurrentSector + 4 else
         inc(CurrentSector);
    //*
    if memFORTH.Visible then actFormatFORTHExecute(Self)
    else if DisplayingFile then
     DisplayTISector(CurrentSector,
       CurrentFile.SectorList[CurrentSector].Contents)
    else if DisplayingDiskImage then
     DisplayTISector(CurrentSector,
       CurrentDisk.SectorList[CurrentSector].Contents);
    //*
    edSectorNumHex.Text := inttohex(CurrentSector, 2);
    edSectorNum.Value := strtoint('$' + edSectorNumHex.Text);
    end;
end;

procedure TfrmMain.spinSectorDownClick(Sender: TObject);
begin
  if strtoint('$' + edSectorNumHex.Text) > 0
    then begin
    if ckFORTHDisk.checked then CurrentSector := CurrentSector - 4 else
         dec(CurrentSector);
    //*
    if memFORTH.Visible then actFormatFORTHExecute(Self)
    else if DisplayingFile then
     DisplayTISector(CurrentSector,
       CurrentFile.SectorList[CurrentSector].Contents)
    else if DisplayingDiskImage then
     DisplayTISector(CurrentSector,
       CurrentDisk.SectorList[CurrentSector].Contents);
    //*
    edSectorNumHex.Text := inttohex(CurrentSector, 2);
    edSectorNum.Value := strtoint('$' + edSectorNumHex.Text);
    end;
end;

procedure TfrmMain.gridSectorsClick(Sender: TObject);
begin
  //* for blanks
  if gridSectors.Cells[GridSectors.Col, GridSectors.Row] = '' then exit;
  //*
  if gridSectors.Col < 9 then begin
    edByteNumHex.Text :=
      inttohex((gridSectors.Col - 1) + (8 * (gridSectors.Row - 1)), 2);

    edContent.text := inttostr(strtoint('$'
     + gridSectors.Cells[GridSectors.Col, GridSectors.Row]));
    edContentChar.text := CHR(strtoint('$'
     + gridSectors.Cells[GridSectors.Col, GridSectors.Row]));
    end
  else begin
    edByteNumHex.Text :=
      inttohex((gridSectors.Col - 9) + (8 * (gridSectors.Row - 1)), 2);
    //*
    edContentChar.text := gridSectors.Cells[GridSectors.Col,
       GridSectors.Row];
    try
     edContent.text := inttostr(ord(edContentChar.text[1])); except end;
    end;
  //*
  edByteNum.Value := strtoint('$' + edByteNumHex.Text)
end;


procedure TfrmMain.NewFonts(Sender: TObject);
var i: integer;
begin
   memRawText.Font.Name := cbFonts.FontName;
   memRawText.Font.Size := TRUNC(spinFontSize.Value);
   //*
   memCFRaw.Font.Name := cbFonts.FontName;
   memCFRaw.Font.Size := TRUNC(spinFontSize.Value);
   //*
   memFORTH.Font.Name := cbFonts.FontName;
   memFORTH.Font.Size := TRUNC(spinFontSize.Value);
   //*
   gridHeader.Font.Name :=  cbFonts.FontName;
   gridHeader.Font.Size := TRUNC(spinFontSize.Value);
   //*
   gridSectors.Font.Name :=  cbFonts.FontName;
   gridSectors.Font.Size := TRUNC(spinFontSize.Value);
   //*
   for i := 0 to gridSectors.ColCount - 1 do begin
     gridSectors.Columns.Items[i].Font.Name := cbFonts.FontName;
     gridSectors.Columns.items[i].FOnt.Size := TRUNC(spinFontSize.Value);
     end;
   //*
   gridCFSectors.Font.Name :=  cbFonts.FontName;
   gridCFSectors.Font.Size := TRUNC(spinFontSize.Value);
   //*
   for i := 0 to gridCFSectors.ColCount - 1 do begin
     gridCFSectors.Columns.Items[i].Font.Name := cbFonts.FontName;
     gridCFSectors.Columns.items[i].FOnt.Size := TRUNC(spinFontSize.Value);
     end;
   //*
   lbCatalog.Font.Name := cbFonts.FontName;
   lbCatalog.Font.Size := TRUNC(spinFontSize.Value);
   //*
   lbVolumes.Font.Name := cbFonts.FontName;
   lbVolumes.Font.Size := TRUNC(spinFontSize.Value);
end;

//******************************************************
//*    Other Actions                                   *
//******************************************************

procedure TfrmMain.actTIImageOpenBeforeExecute(Sender: TObject);
begin
  ActTIImageOpen.Dialog.Filter := 'WIN994A images|*.TIDisk|V9T9 images|*.V9|All Files|*.*';
  ActTIImageOpen.Dialog.InitialDir := lbDirs.Directory;
end;

procedure TfrmMain.actTIImageOpenAccept(Sender: TObject);
begin
  DisplayTIDisketteImageFile(actTIImageOpen.Dialog.FileName);
end;

procedure TfrmMain.actTIFileOpenBeforeExecute(Sender: TObject);
begin
  actTIFileOpen.Dialog.Filter := 'Files|*.*';
  actTIFileOpen.Dialog.InitialDir := lbDirs.Directory;
end;

procedure TfrmMain.actSaveAsAccept(Sender: TObject);
begin
   memFormatted.Lines.SaveToFile(actSaveAs.Dialog.FileName);
end;

procedure TfrmMain.actSaveAsBeforeExecute(Sender: TObject);
begin
  actTIFileOpen.Dialog.Filter := 'Files|*.*';
  actTIFileOpen.Dialog.InitialDir := lbDirs.Directory;
end;

procedure TfrmMain.actFormatFORTHExecute(Sender: TObject);
var NewCols: TStrings;
    i, j: integer;
begin
  memForth.Clear;
  for i := CurrentSector to CurrentSector + 3 do
    DisplayTISectorNoClear(i, CurrentDisk.SectorList[i].Contents);
  pagesTI.ActivePage := tabFormatted;
end;

procedure TfrmMain.actGotoCFSectorExecute(Sender: TObject);
begin
  currCFsec := strtoint(inputbox('Goto Sector...','Input integer value: ', '666'));
  ReadCFSector;
end;

procedure TfrmMain.actGotoTISectorExecute(Sender: TObject);
begin
  edSectorNum.Text := inputbox('Goto Sector...','Input integer value: ', '666');
  if strtoint(edSectorNum.Text) < MaxSectors
    then begin
    CurrentSector := strtoint(edSectorNum.Text);
    edSectorNumHex.Text := inttohex(CurrentSector, 2);
    //*
    if DisplayingFile then
     DisplayTISector(CurrentSector,
       CurrentFile.SectorList[CurrentSector].Contents)
    else if DisplayingDiskImage then
     DisplayTISector(CurrentSector,
       CurrentDisk.SectorList[CurrentSector].Contents);
    end;
end;

procedure TfrmMain.actNarrowListExecute(Sender: TObject);
//*********************************************
//**                                          *
//**  Why anyone would want to do this        *
//**    [and why I wrote it]                  *
//**    I'll never understand.......          *
//**                                          *
//*********************************************
var NewCols: TStrings;
    i, j: integer;
begin
   NewCols := TStringList.Create;
   try
   for i := 0 to memFormatted.Lines.count do begin
     for j := 0 to (length(memFormatted.Lines[i]) div 28) do
        NewCols.Add(Copy(memFormatted.Lines[i], j * 28, 28));
     end;
   memFormatted.Lines.Assign(NewCols);
   finally
   NewCols.Free;
   end;
end;

procedure TfrmMain.actPrintAccept(Sender: TObject);
var  LM, RM, TM, BM: integer;
begin
  if PagesTI.ActivePage = tabCF7 then PrintCFList else
  if PagesTI.ActivePage = tabImage then PrintDisketteImageCatalog else
  if PagesTI.ActivePage = tabFormatted then
    if memFORTH.Visible then printFORTH else PrintRichEdit;
end;

//*****************************************************
//*  New CF routines                                  *
//*****************************************************


//*****************************************************
//*  Navigation                                       *
//*****************************************************

procedure TfrmMain.spinVolumeDownClick(Sender: TObject);
begin
  if edCFVolume.Value > 1 then
    edCFVolume.Value := edCFVolume.Value - 1;
  currCFSec := TRUNC((edCFVolume.Value - 1)) * 1600;
  ReadCFSector;
  DisplayCFSector;
  lbVolumes.ItemIndex := TRUNC(edCFVolume.Value -1);
  Tellem(true, '');
end;

procedure TfrmMain.spinVolumeUpClick(Sender: TObject);
begin
  if edCFVolume.Value < CFVolumeCount then
    edCFVolume.Value := edCFVolume.Value + 1;
  currCFSec := TRUNC((edCFVolume.Value - 1)) * 1600;
  ReadCFSector;
  DisplayCFSector;
  lbVolumes.ItemIndex := TRUNC(edCFVolume.Value -1);
  Tellem(true, '');
end;

procedure TfrmMain.actGotoCFVolumeExecute(Sender: TObject);
var newvol: integer;
begin
  newvol := strtoint(inputbox('Goto Sector...',
                              'Input integer value: ',
                              inttostr(CFVolumeCount)));
  if (newvol > 0) and (newvol <= CFVolumeCount) then
    edCFVolume.Value := newvol;
  currCFSec := TRUNC((edCFVolume.Value - 1)) * 1600;
  ReadCFSector;
  DisplayCFSector;
  lbVolumes.ItemIndex := newvol - 1;
  Tellem(true, '');
end;

procedure TfrmMain.spinCFSectorDownClick(Sender: TObject);
begin
  if currCFSec > 0 then begin
    dec(currCFsec);
    ReadCFSector;
    DisplayCFSector;
    end;
end;

procedure TfrmMain.spinCFSectorUpClick(Sender: TObject);
begin
  if currCFSec < currDriveParams.physsecLO then begin
    inc(currCFsec);
    ReadCFSector;
    DisplayCFSector;
    end;
end;

//***************************************************
//*  CF reading and writing actions & procedures    *
//***************************************************

procedure TfrmMain.listRDrivesClick(Sender: TObject);
var msg: string;
begin
  if Copy(listRDrives.items[listRDrives.itemindex], 1, 5) <> 'Remov' then exit;
  //
  //*Save_Cursorr := Screen.Cursor;
  Screen.Cursor := crHourGlass;    { Show hourglass cursor. }
  currCFdrv:=drivetable[listRDrives.ItemIndex];
  Tellem(true, 'Current Drive is ' + inttostr(currCFdrv));
  GetDriveParams(currCFdrv, @currDriveParams);
  //
  //  currDriveParams are wrong about size
  //
  currDriveParams.physsecLO := 62400;
  //
  //
  //
  CFMountInfo := False;
  CFDSK1 := 0;
  CFDSK2 := 0;
  CFDSK3 := 0;
  //
  ReadCFVolumeList;
  currCFsec:=0;
  ReadCFSector;
  //*sector 0 back in memory
  if (CFSector[20] = $AA) and (CFSector[21] = $03) then begin
     CFMountInfo := true;
     CFDSK1 := ord(CFSector[23]);
     lbVolumes.SetItemPart(CFDSK1 - 1, 3, 'DSK1');
     CFDSK2 := ord(CFSector[25]);
     lbVolumes.SetItemPart(CFDSK2 - 1, 3, 'DSK2');
     CFDSK3 := ord(CFSector[27]);
     lbVolumes.SetItemPart(CFDSK3 - 1, 3, 'DSK3');
     end;
  DisplayCFSector;
  Screen.Cursor := crDefault;
  Tellem(true, 'Useable volume count: ' + inttostr(lbVolumes.Items.count));
  //
  if lbVolumes.ItemPart(0, 1) = 'NO FORMAT' then begin
    msg := 'This may or may not be a DOS formatted CF card,'
      + ' but in any case it appears that'
      + ' the first write to Volume 1'
      + ' (sector 0) will fail.'
      + #10 + #13 + #10 + #13 +
      'Click OK to write to sector 0. Then remove'
      + ' and reinsert the CF card.'
      + #10 + #13 + #10 + #13 +
      'Click Cancel to skip this step and use another CF card.';
      if MessageDlg(msg, mtConfirmation, [mbOK, mbCancel], 0) = mrOK then begin
        FormatCFVol(1, false);
        lbVolumes.Items.Clear;
        Tellem(true, 'Format of Vol #1 failed!');
        msg := 'Remove, and re-insert and re-read CF card.'
                + #10 + #13 + #10 + #13 +
               'Dismiss any Windows messages about formatting the CF card.';
        MessageDlg(msg, mtInformation, [mbOk], 0);
        Screen.Cursor := crDefault;
        end;
  end;
end;

procedure TfrmMain.ReadCFVolumeList;
var msg: string;
    i, j, maxvols: integer;
begin
  lbVolumes.Clear;
  currCFsec := 0;
  //while currCFsec < currDriveParams.physsecLO do begin
  while true do begin
    CurrentCFVolumeClear;
    CurrentCFVolume.VolumeNum := (currCFSec div 1600) + 1;
    Tellem(true, 'Reading CF Volume#' + inttostr(CurrentCFVolume.VolumeNum));
    //
    //ReadCFSector;
    //
    edSectorNum.Value :=  currCFsec;
    Tellem(true, 'reading CF sector #' + inttostr(currCFSec));
    if not ExtendedRead(currCFdrv, currCFsec, 1, @TrueCFSector) then
      begin
      currDriveParams.physsecLO := currCFSec - 1;
      lbVolumes.Items.Delete(lbVolumes.Items.Count - 1);
      CFVolumeCount := CurrentCFVolume.VolumeNum;
      edCFVolume.Value := 1;
      exit;
      end;
    //
    Tellem(true, 'converting CF sector #' + inttostr(currCFSec));
    i := 0;
    j := 0;
    while i < 512 do begin
      CFSector[j] := TrueCFSector[i];
      i := i + 2;
      inc(j);
      end;
    ShowSec(CFSector);
    edCFSector.Value := currCFSec;
    //
    ReadCFImage;
    //*
    if CurrentCFVolume.DSRMark = 'DSK' then begin
      if CurrentCFVolume.Sides = 2 then msg := 'DS' else msg := 'SS';
      if CurrentCFVolume.Density = 2 then
        msg := msg + 'DD' else msg := msg + 'SD';
      end
    else begin
      CurrentCFVolume.Diskname := 'NO FORMAT';
      msg := 'N/A'
      end;
    //*
    lbVolumes.AddLine([inttostr(CurrentCFVolume.VolumeNum),
                       CurrentCFVolume.Diskname,
                       msg, 'N/A']);
    currCFsec := currCFSec + 1600;
    end;
end;

procedure TfrmMain.DisplayCFSector;
var i, gCol, gRow: integer;
begin
  //*
  //*  byte-by-byte add Buffer to memo and grid and to integer array
  //*
  memCFRaw.Clear;
  gCol := 1;
  gRow := 1;
  //
  if not ckCFSector.checked then begin
    for i := 0 to 511 do begin
      gridCFSectors.Cells[gCol, gRow] :=
       format('%.2x',[ord(TrueCFSector[i])]);
      gridCFSectors.Cells[gCol + (gridCFSectors.ColCount div 2), gRow] := CHR(TrueCFSector[i]);
      //*
      memCFRaw.Text := memCFRaw.Text + CHR(TrueCFSector[i]);
      //*
      inc(gCol);
      if gCol > (gridCFSectors.ColCount div 2) then begin
        gcol := 1;
        inc(gRow);
        end;
     end;
    end
  else begin
    for i := 0 to 255 do begin
      gridCFSectors.Cells[gCol, gRow] :=
       format('%.2x',[ord(CFSector[i])]);
      gridCFSectors.Cells[gCol + (gridCFSectors.ColCount div 2), gRow] := CHR(CFSector[i]);
      //*
      memCFRaw.Text := memCFRaw.Text + CHR(CFSector[i]);
      //*
      inc(gCol);
      if gCol > (gridCFSectors.ColCount div 2) then begin
        gcol := 1;
        inc(gRow);
        end;
      end;
    end;
end;

procedure TfrmMain.lbVolumesDblClick(Sender: TObject);
var savedsector: integer;
begin
  if lbVolumes.ItemPart(lbVolumes.ItemIndex, 1) = 'NO FORMAT' then exit;
  //*
  //*Save_Cursorr := Screen.Cursor;
  edCFVolume.Value := lbVolumes.itemindex + 1;
  currCFSec := TRUNC((edCFVolume.Value - 1)) * 1600;
  savedsector := currCFSec;
  ReadCFSector;
  DisplayCFSector;
  DisplayCFDisketteImage;
  currCFSec := savedsector;
  edCFSector.Value := savedsector;
  Screen.Cursor := crDefault;
end;

procedure TfrmMain.actFormatAllVolsExecute(Sender: TObject);
var warning: string;
    i: integer;
begin
  warning := 'This procedure will create numbered SSSD images in all '
             + inttostr(lbVolumes.items.count)
             + ' volumes of the CF card mounted as '
             + listRdrives.items[listRdrives.ItemIndex] + '.'
             + #13 +#10 + #13 +#10
             + 'It may take a while and it will render the '
             + ' CF card useless for anything but the CF7a.'
             + #13 +#10 + #13 + #10
             +'There will be no more warnings and you should not interrupt.'
             + #13 +#10 + #13 + #10
             + 'ARE YOU SURE?';
  if MessageDlg(warning, mtConfirmation, [mbYes, mbNo], 0) = mrYes then begin
    for i := 1 to lbVolumes.items.count do FormatCFVol(i, true);
    Tellem(true, 'Formatting all volumes complete');
    listRDrivesClick(Self);
  end;
end;

procedure TfrmMain.actFormatCFVolExecute(Sender: TObject);
var warning: string;
begin
  warning := 'This procedure will a numbered SSSD image in '
             + 'volume #' + inttostr(lbVolumes.itemindex + 1)
             + ' of the CF card mounted as '
             + listRdrives.items[listRdrives.ItemIndex] + '.'
             + #13 +#10 + #13 +#10
             + 'ARE YOU SURE?';
  if MessageDlg(warning, mtConfirmation, [mbYes, mbNo], 0) = mrYes then begin
     FormatCFVol(lbVolumes.ItemIndex + 1, true);
     //*
     Tellem(true, 'Formatting of Volume #'
       + inttostr(lbVolumes.ItemIndex + 1) + ' Complete!');
     listRDrivesClick(Self);
     end;
end;

procedure TfrmMain.actPCtoCFExecute(Sender: TObject);
var msg: string;
begin
  if lbVolumes.ItemIndex = -1 then exit;
  //*
  dlgOpenImage.Filter := 'WIN994A images|*.TIDisk|V9T9 images|*.V9|All Files|*.*';
  dlgOpenImage.InitialDir := lbDirs.Directory;
  if dlgOpenImage.Execute then begin
    msg :=  'This procedure will write ' + dlgOpenImage.FileName
             + ' to volume #' + inttostr(lbVolumes.itemindex + 1)
             + ' of the CF card mounted as '
             + listRdrives.items[listRdrives.ItemIndex] + '.';
             //
    if ckDisplayAfterWrite.checked then msg := msg
             + #13 +#10 + #13 +#10
             +'The image in the volume will be displayed '
             + 'after a successful write.'
             + #13 +#10 + #13 + #10;
             //
    msg := msg + #13 +#10 + #13 + #10 + 'Are you Sure?';
    if MessageDlg(msg, mtConfirmation, [mbYes, mbNo], 0, mbYes) = mrYes then
      if ckDisplayAfterWrite.Checked then
        ImageFiletoCF(lbVolumes.ItemIndex + 1, dlgOpenImage.FileName, true)
      else
        ImageFiletoCF(lbVolumes.ItemIndex + 1, dlgOpenImage.FileName, false);
    end;
end;

procedure TfrmMain.actCFtoPCExecute(Sender: TObject);
var Volnum, StartCFSec, SavedSec: integer;
begin
  SavedSec := CurrCFSec;
  //* Save Dialog
  dlgSaveImage.Filter := 'WIN994A images|*.TIDisk|V9T9 images|*.V9|All Files|*.*';
  dlgSaveImage.InitialDir := lbDirs.Directory;
  dlgSaveImage.FileName :=
     StripIllegalChars(TRIM(lbVolumes.ItemPart(lbVolumes.ItemIndex, 1)))
                                  + '.TIDisk';
  if dlgSaveImage.Execute then begin
    //* Write Volume to PC
    //*Save_Cursorr := Screen.Cursor;
    Screen.Cursor := crHourGlass;    { Show hourglass cursor. }
    Volnum := lbVolumes.ItemIndex + 1;
    Tellem(true, 'Calling CFVoltoImageFile with VolNum = ' + inttostr(VolNum));
    CFVoltoImageFile(Volnum, dlgSaveImage.FileName);
    end;
  CurrCFSec := SavedSec;
  //*
  Tellem(true, 'Writing CF Volume #' + inttostr(VolNum) + ' Complete!');
  Screen.Cursor := crDefault;
end;



procedure  TfrmMain.FormatCFVol(Volnum:integer; Showerror: boolean);
var i, j, CFSecNum: integer;
    vstr: array[0..2] of ansichar;
    str: ansistring;
    CFSec: array[0..511] of byte;
begin
  //*Save_Cursorr := Screen.Cursor;
  Screen.Cursor := crHourGlass;    { Show hourglass cursor. }
  CFSecNum := (VolNum - 1) * 1600;
  FillChar(CFSec, 512, 0);     // '00'
  //for i := 0 to 511 do CFSec[i] := 0;
  //*
  CFSec[0] :=  86;   // V
  CFSec[2] :=  79;   // O
  CFSec[4] :=  76;   // L
  //*
  //* a sledgehammer but it works?
  //*
  str := inttostr(Volnum);
  while length(str) < 3 do str := str + #32;
  for i := 0 to 2 do vstr[i] := str[i + 1];
  CFSec[6] :=  ord(vstr[0]);
  CFSec[8] :=  ord(vstr[1]);
  CFSec[10] := ord(vstr[2]);
  //*
  CFSec[12] :=  32;
  CFSec[14] :=  32;
  CFSec[16] :=  32;
  CFSec[18] :=  32;
  //*
  if ckTrueCount.checked then begin
    CFSec[20] :=  5;   // segm
    CFSec[22] :=  0;   // count (1280)
    end
  else begin
    CFSec[20] :=  6;   // segm
    CFSec[22] :=  64;  // count (1600)
    end;
  //*
  CFSec[24] :=  32;
  CFSec[26] :=  68;  // D
  CFSec[28] :=  83;  // S
  CFSec[30] :=  75;  // K
  CFSec[32] :=  32;
  CFSec[34] :=  40;  // (
  CFSec[36] :=  01;
  CFSec[38] :=  01;
  //*
  CFSec[112] :=  03;  // bitmap
  //*
  //* write to CF card
  //*
  Tellem(true, 'Writing CF Sector #' + inttostr(CFSecNum)
                + ' to drive #' + inttostr(currCFdrv));
  ShowSec(CFSec);
  if not ExtendedWrite(currCFdrv, CFsecNum, 1, @CFSec, True) then begin
     if ShowError then MessageDlg('Error writing drive', mtInformation, [mbOk], 0);
     Tellem(true, 'Write error; check CF card');
     exit;
     end;
  //*
  i := 0;
  while i < 512 do begin
    CFSec[i] := 0;
    CFSec[i + 1] := 207; //'CF'
    inc(i); inc(i);
  end;

  //FillChar(CFSec, 512, 207);     // 'CF'
  //*
  for j := 1 to 1599 do begin    // always write 1600 sectors
    INC(CFSecNum);
    Tellem(true, 'Writing CF Sector #' + inttostr(CFSecNum)
                  + ' to drive #' + inttostr(currCFdrv));
    ShowSec(CFSec);
    if not ExtendedWrite(currCFdrv, CFsecNum, 1, @CFSec, True) then begin
       if ShowError then MessageDlg('Error writing drive', mtInformation, [mbOk], 0);
       Tellem(true, 'Write error; check CF card');
       exit;
       end;
    end;
    Screen.Cursor := crDefault;
end;

procedure TfrmMain.ImageFiletoCF(VolNum: integer; PCImage: TFilename; Disp: boolean);
var
  CFSec: array[0..511] of byte;
  i, j, CFSecNum: integer;
begin
  //*Save_Cursor := Screen.Cursor;
  Screen.Cursor := crHourGlass;    { Show hourglass cursor. }

  //*capture all disk sectors

  ReadTIImage(PCImage); //captures all sectors
  CFSecNum :=  (Volnum - 1) * 1600;

  //* Convert & write sectors to CF card consecutively

  for i := 0 to CurrentDisk.Info.SectorCount - 1 do begin

    //* convert and expand sector

    for j := 0 to 255 do begin
       CFSec[j * 2] := CurrentDisk.SectorList[i].Contents[j];
       CFSec[(j * 2) + 1] := 0;
       end;

    //* write to CF card
    Tellem(true, 'Writing CF Sector #' + inttostr(CFSecNum)
                  + ' to drive #' + inttostr(currCFdrv));
    ShowSec(CFSec);
    if not ExtendedWrite(currCFdrv, CFsecNum, 1, @CFSec, True) then begin
       MessageDlg('Error writing drive', mtInformation, [mbOk], 0);
       Tellem(true, 'Write error; check CF card');
       exit;
       end;
    Inc(CFSecNum);
    end;

  //* fill any blank sectors to 1600

  FillChar(CFSec, 512, 207);     // 'CF'

  for i := CurrentDisk.Info.SectorCount to 1599 do begin
    Tellem(true, 'Writing CF Sector #' + inttostr(CFSecNum)
                  + ' to drive #' + inttostr(currCFdrv));
    if not ExtendedWrite(currCFdrv, CFsecNum, 1, @CFSec, True) then begin
       MessageDlg('Error writing drive', mtInformation, [mbOk], 0);
       Tellem(true, 'Write error; check CF card');
       exit;
       end;
    Inc(CFSecNum);
    end;

  Screen.Cursor := crDefault;

  //*re-read CFCard

  listRDrivesClick(Self);

  //*and end by displaying CF Volume

  lbVolumes.Itemindex := VolNum - 1;
  if ckDisplayAfterWrite.Checked then lbVolumesDblCLick(Self);
end;

procedure TfrmMain.CFVoltoImageFile(VolNum: integer; PCImage: TFilename);
var startCFSec,
    SectorCount,
    SectorsPerTrack,
    TracksPerSide,
    Sides,
    NumWritten,
    i,
    TotalSectors: integer;
    toF: File;
begin

  // *Read Sector 0 of CF image

  CurrCFSec := (Volnum - 1) * 1600 ;
  ReadCFSector;
  SectorCount := (256 * ord(CFSector[10])) +  ord(CFSector[11]);
  SectorsPerTrack := ord(CFSector[12]);
  TracksPerSide := ord(CFSector[17]);
  Sides := ord(CFSector[18]);
  TotalSectors := SectorsPerTrack * TracksPerSide * Sides;

  //* Fix sector count bytes and sectorcount variable
  //*   if ckTrueCount is checked

  if ckTrueCount.checked then begin
    CFSector[10] := TotalSectors DIV 256;
    CFSector[11] := TotalSectors MOD 256;
    SectorCount := TotalSectors;
    end;

  //* Write Sector 0

  Tellem(true, 'Writing CF Sector #0');
  AssignFile(ToF, dlgSaveImage.FileName);
  ReWrite(ToF, 1);
  BlockWrite(ToF, CFSector, SizeOf(CFSector), NumWritten);

  //* Read and write rest of sectors using Sectorcount
  //*   variable, which will reflect the state of ckTrueCount

  for i := 1 to SectorCount - 1 do begin
    inc(CurrCFSec);
    ReadCFSector;
    Tellem(true, 'Writing CF Sector #' + inttostr(i) + ' to PC');
    BlockWrite(ToF, CFSector, SizeOf(CFSector), NumWritten);
  end;
  CloseFile(ToF);
end;

procedure TfrmMain.ReadCFImage;
var SS: string[10];
    i: Integer;
begin
  frmMain.Tellem(true, '');
  SS := '';
  //*     DSRMark: string[3];         //* bytes 13 - 15
  for i := 13 to 15 do
    SS := SS +  char(CFSector[i]);
  CurrentCFVolume.DSRMark := SS;
  SS := '';
  //*
  for i := 0 to 9 do
    SS := SS +  char(CFSector[i]);
  CurrentCFVolume.Diskname := SS;
  SS := '';
  //CurrentCFVolume.SectorCount := (256 * ord(CFSector[10]))
  //                                +  ord(CFSector[11]);
  //CurrentCFVolume.SectorsPerTrack := ord(CFSector[12]);
  //*
  //if char(CFSector[16]) = 'P' then
  //  CurrentCFVolume.diskProtected := true
  //else CurrentCFVolume.diskProtected := False;
  //*
  //CurrentCFVolume.TracksPerSide := ord(CFSector[17]);
  CurrentCFVolume.Sides := ord(CFSector[18]);
  CurrentCFVolume.Density := ord(CFSector[19]);
end;

procedure TfrmMain.actCSVReportExecute(Sender: TObject);
var i, j, ii, iii, jj, SavedSec, used: integer;
    SS, warning: string;
    F: Textfile;
begin
  warning := 'This procedure will create a comma delimited text'
             + ' file which lists all'
             + ' the TI files in every used volume of the CF card.'
             + #13 +#10 + #13 +#10
             + 'The eight fields listed are:'
             + #13 + #10
             + '     VolumeNum, Diskname, Diskformat, DiskSectors,'
             + '     UsedSectors, Filename, FileSize and Filetype.'
             + #13 +#10 + #13 + #10
             + 'You can import the file into your favorite database,'
             + ' massage the data any which way you like,'
             + ' and use it to make pretty reports.'
             + #13 +#10 + #13 + #10
             +'The procedure will be time-consuming and you should not interrupt.'
             + #13 +#10 + #13 + #10
             + 'ARE YOU SURE?';
  if MessageDlg(warning, mtConfirmation, [mbYes, mbNo], 0) = mrNo then exit;
  //*
  SavedSec := CurrCFSec;   // Hold onto Current CF Sector
  //* Save Dialog
  dlgSaveImage.Filter := 'CSV files|*.CSV|All Files|*.*';
  dlgSaveImage.InitialDir := lbDirs.Directory;
  dlgSaveImage.FileName := 'VolReport.CSV';
  //*
  // Open the file
  //
  if dlgSaveImage.Execute then begin
    //*
    AssignFile(F, dlgSaveImage.FileName); { File selected in the dialog }
    ReWrite(F);
    //* Header
    Writeln(F, 'VolumeNum' + ','
             + 'Diskname' + ','
             + 'Diskformat' + ','
             + 'DiskSectors' + ','
             + 'UsedSectors' + ','
             + 'Filename' + ','
             + 'FileSize' + ','
             + 'Filetype');
    //*
    Screen.Cursor := crHourglass;
    //
    //  begin iteration through volumes
    //
    for i := 0 to lbVolumes.items.Count - 1 do begin
      if (lbVolumes.ItemPart(i, 1) <> 'NO FORMAT')   // skip blanks
         and (lbVolumes.ItemPart(i, 1) <> 'VOL' + inttostr(i + 1))
         then begin
        Tellem(true, 'Cataloging volume ' + inttostr(i + 1));
        //*
        lbVolumes.itemindex := i;
        edCFVolume.Value := lbVolumes.itemindex + 1;
        currCFSec := TRUNC((edCFVolume.Value - 1)) * 1600;
        frmMain.edSectorNum.Value :=  currCFsec;
        //frmMain.Tellem(true, 'reading CF sector #' + inttostr(currCFSec));
        if not ExtendedRead(currCFdrv, currCFsec, 1, @TrueCFSector) then
        begin
          MessageDlg('Error reading drive at sector #' + inttostr(currCFSec), mtInformation, [mbOk], 0);
          exit;
        end;
        //
        //frmMain.Tellem(true, 'converting CF sector #' + inttostr(currCFSec));
        ii := 0;
        jj := 0;
        while ii < 512 do begin
          CFSector[jj] := TrueCFSector[ii];
          ii := ii + 2;
          inc(jj);
        end;
        frmMain.ShowSec(CFSector);
        frmMain.edCFSector.Value := currCFSec;
        //*
        // Get Ready
        //
        ClearTIFilesFileInfo;
        ClearDisketteInfo;
        ClearCurrentDiskette;
        //*
        if ReadCFDiskImage then begin
        //
        //* Output Disk Info Line by Line
        //
        for iii := 0 to CurrentDisk.Info.FileCount - 1 do begin
          //* spit it out
          if CurrentDisk.Info.Sides = 2 then SS := 'DS' else SS := 'SS';
          if CurrentDisk.Info.Density = 2 then
              SS := SS + 'DD' else SS := SS + 'SD';
          //
          used := 0;
          //
          //*iterate through files
          //
          for jj := 0 to CurrentDisk.Info.FileCount - 1 do
            used := used + CurrentDisk.Directory[jj].SectorCount;
          //
          if CurrentDisk.Info.FileCount > 0 then
              Writeln(F, inttostr(i + 1) + ','    //<<-- each line
              + CurrentDisk.Info.Diskname + ','
              + SS + ','
              + Inttostr(CurrentDisk.Info.SectorCount) + ','
              + Inttostr(used) + ','
              + CurrentDisk.Directory[iii].FileName + ','
              + inttostr(CurrentDisk.Directory[iii].SectorCount) + ','
              + DecodeType(CurrentDisk.Directory[iii].StatusFlag,
                         CurrentDisk.Directory[iii].Recordlength));
          end;
        end;
    end;
  //*
  end;
  CloseFile(F);
  CurrCFSec := SavedSec;
  Screen.Cursor := crDefault;
  Tellem(true, ' CSV generation complete!');
  end;
end;

//*****************************************************
//*  CF Lists Drag and Drop                           *
//*****************************************************

procedure TfrmMain.lbVolumesDragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
begin
   if Source is TJvFileListBox then Accept := True;
end;


procedure TfrmMain.lbFilesDragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
begin
   if Source is TLMDListBox then Accept := True;
end;

procedure TfrmMain.lbFilesDragDrop(Sender, Source: TObject; X, Y: Integer);
var Volnum, StartCFSec, SavedSec: integer;
begin
  SavedSec := CurrCFSec;
  //* Save Dialog
  dlgSaveImage.Filter := 'WIN994A images|*.TIDisk|V9T9 images|*.V9|All Files|*.*';
  dlgSaveImage.InitialDir := lbDirs.Directory;
  dlgSaveImage.FileName :=
     StripIllegalChars(TRIM(lbVolumes.ItemPart(lbVolumes.ItemIndex, 1)))
                                  + '.TIDisk';
  if dlgSaveImage.Execute then begin
    //* Write Volume to PC
    //*Save_Cursorr := Screen.Cursor;
    Screen.Cursor := crHourGlass;    { Show hourglass cursor. }
    Volnum := lbVolumes.ItemIndex + 1;
    Tellem(true, 'Calling CFVoltoImageFile with VolNum = ' + inttostr(VolNum));
    CFVoltoImageFile(Volnum, dlgSaveImage.FileName);
    end;
  CurrCFSec := SavedSec;
  //*
  Tellem(true, 'Writing CF Volume #' + inttostr(VolNum) + ' Complete!');
  Screen.Cursor := crDefault;
end;

procedure TfrmMain.lbVolumesDragDrop(Sender, Source: TObject; X, Y: Integer);
var  iTemp : integer;
  ptTemp : TPoint;
  szTemp : String;
  msg: string;
begin
  if lbVolumes.ItemIndex = -1 then exit;

  { change the x,y coordinates into a TPoint record }
  ptTemp.x:=x;
  ptTemp.y:=y;
  //*
  //* ALways remember that the 'Sender' is the control being dropped onto?!?!?
  //*
  //ShowMessage(lbVOlumes.Items[lbVOlumes.ItemAtPos(pttemp, true)]);
  //*
  msg :=  'This procedure will write '
           + TjVFileListBox(Source).Filename
           + ' to Volume #'
           + lbVolumes.ItemPart(lbVOlumes.ItemAtPos(pttemp, true), 0)
           + ' of the CF card mounted as '
           + listRdrives.items[listRdrives.ItemIndex] + '.';
           //
  if ckDisplayAfterWrite.checked then
           msg := msg
           + #13 +#10 + #13 +#10
           +'The image in the volume will be displayed '
           + 'after a successful write.';
           //
  msg := msg + #13 +#10 + #13 + #10
           + 'Are you Sure?';
  if MessageDlg(msg, mtConfirmation, [mbYes, mbNo], 0, mbYes) = mrYes then
    if ckDisplayAfterWrite.Checked then
       ImageFiletoCF(lbVolumes.ItemAtPos(pttemp, true) + 1,
               TjvFileListBox(Source).FileName, true)
    else ImageFiletoCF(lbVolumes.ItemAtPos(pttemp, true) + 1,
               TjvFileListBox(Source).FileName, false);
end;

//*****************************************************
//*  Action Management                                *
//*****************************************************

procedure TfrmMain.actionMainUpdate(Action: TBasicAction;
  var Handled: Boolean);
begin
   pnlTopRight.Visible := ckShowDIrTree.Checked;
   pnlBottom.Visible := pagesTI.ActivePage <> tabCF7;
   //actSaveAsText.Enabled := CurrentFile.FormattedFlag;
   //actPrintText.Enabled := CurrentFile.FormattedFlag;
   actFormatFORTH.enabled := (CurrentDisk.Info.SectorCount > 0) and
        (CurrentSector mod 4 = 0) and ckFORTHDisk.Checked;
   memFORTH.Visible := ckFORTHDisk.Checked;
   //*
   actFormatCompanion.Enabled :=
     (not CurrentFile.Header.ProgType)
     and CurrentFile.CompanionFlag;
   //*
   actFormatDV80.Enabled :=
     (((not CurrentFile.Header.ProgType)
     and (CurrentFile.Header.DataType = 'DIS')
     and (CurrentFile.SectorsRead)
     and (not CurrentFile.CompanionFlag)));
   //*
   actFormatBASIC.Enabled :=
     ((CurrentFile.Header.ProgType)
     and (CurrentFile.SectorsRead)
     and (CurrentFile.BasicFlag));
   //*
   //actCatalog.Enabled := CurrentDisk.SectorsRead;
   actDisplayFile.Enabled := CurrentDisk.SectorsRead;
   actExtractFile.Enabled :=
     (not CurrentFile.StandAlone) and CurrentFile.SectorsRead;
   //*
   actNarrowList.Enabled :=
     ((CurrentFile.Header.ProgType)
     and (CurrentFile.FormattedFlag)
     and (CurrentFile.BasicFlag));
   //*
   actFormatFont.Enabled := memFormatted.SelLength > 0;
   //*
   actCFtoPC.Enabled := lbVolumes.Focused;
   //*
   actPCtoCF.Enabled := actCFtoPC.Enabled;
   actFormatCFVol.Enabled := actPCtoCF.Enabled;
   actFormatAllVols.Enabled := actPCtoCF.Enabled;
   actCSVReport.Enabled := actPCtoCF.Enabled;
   //*
   //*  some interface stuff
   //*
   cbFonts.Enabled := CurrentFile.SectorsRead or ckFORTHDisk.Checked
      or (PagesTI.activepage = tabCF7);
   spinFontSize.Enabled := cbFonts.Enabled;
end;

end.
