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

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

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

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

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

}

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, Menus, ExtCtrls,
  ComCtrls, StdCtrls, uSynHighlighterXB, SynHighlighterVB, SynEdit,
  SynCompletion, SynGutterCodeFolding, IniFiles, LCLType, ColorBox, MaskEdit,
  Grids, Buttons, ExtDlgs, EditBtn, CheckLst, tidbit, uSXB2XB, uTokenize, Types
  {$IFNDEF Darwin},SynExportHTML, ExtendedNotebook {$ENDIF};

type

  { TForm1 }

  TForm1 = class(TForm)
    BbIntLoad: TBitBtn;
    BbIntCompile: TBitBtn;
    BbIntRun: TBitBtn;
    bbBgImage: TBitBtn;
    bbAsmHide: TBitBtn;
    BtnErase: TBitBtn;
    BnPreScan: TButton;
    cbCommand: TCheckBox;
    cbDebug: TCheckBox;
    CBIntAutoCompile: TCheckBox;
    CBIntAutoRun: TCheckBox;
    cbMrg: TCheckBox;
    cbStandardLib: TCheckBox;
    cbtkn: TCheckBox;
    CBIntAutoLoad: TCheckBox;
    cbTiBas: TCheckBox;
    cbLcLink: TCheckBox;
    clbLib: TCheckListBox;
    ColorBoxBG: TColorBox;
    ColorBoxFG: TColorBox;
    cbCol: TComboBox;
    cbRow: TComboBox;
    cbMemBase: TComboBox;
    cbFormat: TComboBox;
    cbAORG: TComboBox;
    ctab: TStringGrid;
    EdCommand: TEdit;
    EdFileCSet: TFileNameEdit;
    EdFileLib: TFileNameEdit;
    EdFilePrj: TEdit;
    EdFileSXB: TEdit;
    EdFileVXR: TFileNameEdit;
    EdFileXB: TFileNameEdit;
    EdFileXBM: TFileNameEdit;
    EdFileXBtxt: TFileNameEdit;
    gbParameters: TGroupBox;
    MemoAsm: TMemo;
    FindDialog1: TFindDialog;
    GroupBox1: TGroupBox;
    GBvar: TGroupBox;
    GroupBox2: TGroupBox;
    GroupBox3: TGroupBox;
    GroupBox4: TGroupBox;
    gbSetting: TGroupBox;
    gbAnimation: TGroupBox;
    GroupBoxIntegration: TGroupBox;
    GroupBoxLibraries: TGroupBox;
    GroupBoxPostProcess: TGroupBox;
    hexstr: TEdit;
    CharEd: TImage;
    ImageAni: TImage;
    ImageList1: TImageList;
    ImPreview: TImage;
    Label1: TLabel;
    Label10: TLabel;
    Label11: TLabel;
    Label13: TLabel;
    Label14: TLabel;
    Label15: TLabel;
    Label16: TLabel;
    Label17: TLabel;
    Label18: TLabel;
    lbAniSpeed: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    Label6: TLabel;
    Label7: TLabel;
    Label8: TLabel;
    Label9: TLabel;
    lbCommand: TLabel;
    Log: TMemo;
    MaskEdit1: TMaskEdit;
    MaskEdit2: TMaskEdit;
    MaskEdit3: TMaskEdit;
    MaskEdit4: TMaskEdit;
    MemoIntLoad: TMemo;
    MemoIntRun: TMemo;
    MemoIntCompile: TMemo;
    MenuHelp: TMenuItem;
    MenuHelpManual: TMenuItem;
    MenuHelpWebsite: TMenuItem;
    MenuHelpAbout: TMenuItem;
    MenuCart: TMenuItem;
    MenuItemGemini: TMenuItem;
    MnRecent1: TMenuItem;
    MnRecent2: TMenuItem;
    MnRecent3: TMenuItem;
    MnRecent4: TMenuItem;
    MnSaveAs: TMenuItem;
    MnRecent: TMenuItem;
    MnDumpFont: TMenuItem;
    MnDumpCharset: TMenuItem;
    MnLoadCharset: TMenuItem;
    MnLoadFont: TMenuItem;
    MnTiFilesEdit: TMenuItem;
    MnRepIdent: TMenuItem;
    MnHexBin: TMenuItem;
    MnUtil: TMenuItem;
    MnComment: TMenuItem;
    MnEditSplit1: TMenuItem;
    MenuImport: TMenuItem;
    MnEditSplit2: TMenuItem;
    MnFindNext: TMenuItem;
    MnFind: TMenuItem;
    MnBeauty: TMenuItem;
    MnSpecial: TMenuItem;
    MnHTML: TMenuItem;
    MnHighlight: TMenuItem;
    MenuPref: TMenuItem;
    MenuUndo: TMenuItem;
    MenuNew: TMenuItem;
    MnReplace: TMenuItem;
    MnEdit: TMenuItem;
    MenuItemLogSave: TMenuItem;
    MenuItemLogClear: TMenuItem;
    OpenPictureDialog1: TOpenPictureDialog;
    Panel1: TPanel;
    PanelInt: TPanel;
    PopupMenuChar: TPopupMenu;
    PopupMenuGemini: TPopupMenu;
    PopupMenuLog: TPopupMenu;
    rgCharSize: TRadioGroup;
    ReplaceDialog1: TReplaceDialog;
    SaveDialog1: TSaveDialog;
    sbPaste: TSpeedButton;
    sbInvert: TSpeedButton;
    sbRotRight: TSpeedButton;
    sbMirrorV: TSpeedButton;
    sbRight: TSpeedButton;
    sbLeft: TSpeedButton;
    sbDown: TSpeedButton;
    sbMirrorH: TSpeedButton;
    sbRotLeft: TSpeedButton;
    ScrollBox1: TScrollBox;
    ScrollBox2: TScrollBox;
    sbUp: TSpeedButton;
    sbPlay: TSpeedButton;
    sbCopy: TSpeedButton;
    StaticText1: TStaticText;
    StaticText2: TStaticText;
    StatusBar1: TStatusBar;
    Memstat1,Memstat2: TShape;
    SGvar: TStringGrid;
    sgAsmProcs: TStringGrid;
    SynCompletion1: TSynCompletion;
    {$IFNDEF DARWIN}
    SynExporterHTML1: TSynExporterHTML;  // TSynExporterHTML is broken in Darwin
    {$ENDIF}
    TabControl1: TTabControl;
    TabSheet5: TTabSheet;
    TabSheet6: TTabSheet;
    TabSheet7: TTabSheet;
    TimerAnimation: TTimer;
    TimerHex: TTimer;
    tbGrid: TToggleBox;
    TrackBarAni: TTrackBar;
    TrackBarVerbose: TTrackBar;
    xb: TMemo;
    MainMenu1: TMainMenu;
    MenuSXBtoXB: TMenuItem;
    MenuFile: TMenuItem;
    MenuExit: TMenuItem;
    MenuBuild: TMenuItem;
    MenuTokenize: TMenuItem;
    MenuExport: TMenuItem;
    MenuTIdBit: TMenuItem;
    MenuOpen: TMenuItem;
    MenuSave: TMenuItem;
    OpenDialog1: TOpenDialog;
    PageControl1: TPageControl;
    SynEdit1: TSynEdit;
    SynVBSyn1: TSynVBSyn;
    SynXBSyn1: TSynXBSyn;
    TabSheet1: TTabSheet;
    TabSheet2: TTabSheet;
    TabSheet3: TTabSheet;
    TabSheet4: TTabSheet;
    procedure bbAsmHideClick(Sender: TObject);
    procedure bbBgImageClick(Sender: TObject);
    procedure BbIntCompileClick(Sender: TObject);
    procedure BbIntLoadClick(Sender: TObject);
    procedure BbIntRunClick(Sender: TObject);
    procedure BnPreScanClick(Sender: TObject);
    procedure BtnEraseClick(Sender: TObject);
    procedure cbColChange(Sender: TObject);
    procedure CBIntAutoCompileClick(Sender: TObject);
    procedure CBIntAutoLoadClick(Sender: TObject);
    procedure CBIntAutoRunClick(Sender: TObject);
    procedure cbRowChange(Sender: TObject);
    procedure CharEdMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure CharEdMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer
      );
    procedure CharEdMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure clbLibExit(Sender: TObject);
    procedure clbLibResize(Sender: TObject);
    procedure ColorBoxBGChange(Sender: TObject);
    procedure ColorBoxFGChange(Sender: TObject);
    procedure ColorBoxFGGetColors(Sender: TCustomColorBox; Items: TStrings);
    procedure ctabDrawCell(Sender: TObject; aCol, aRow: Integer; aRect: TRect;
      aState: TGridDrawState);
    procedure ctabEditingDone(Sender: TObject);
    procedure ctabPrepareCanvas(sender: TObject; aCol, aRow: Integer;
      aState: TGridDrawState);
    procedure ctabSelectCell(Sender: TObject; aCol, aRow: Integer;
      var CanSelect: Boolean);
    procedure ctabSelectEditor(Sender: TObject; aCol, aRow: Integer;
      var Editor: TWinControl);
    procedure ctabValidateEntry(sender: TObject; aCol, aRow: Integer;
      const OldValue: string; var NewValue: String);
    procedure EdFileXBChange(Sender: TObject);
    procedure EdFileXBMChange(Sender: TObject);
    procedure FindDialog1Close(Sender: TObject);
    procedure FindDialog1Find(Sender: TObject);
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure FormCreate(Sender: TObject);
    procedure FormDropFiles(Sender: TObject; const FileNames: array of String);
    procedure FormShow(Sender: TObject);
    procedure GroupBox1Resize(Sender: TObject);
    procedure GroupBoxIntegrationResize(Sender: TObject);
    procedure hexstrChange(Sender: TObject);
    procedure hexstrEnter(Sender: TObject);
    procedure hexstrExit(Sender: TObject);
    procedure MemoIntCompileDblClick(Sender: TObject);
    procedure MemoIntLoadDblClick(Sender: TObject);
    procedure MemoIntRunDblClick(Sender: TObject);
    procedure MenuBuildClick(Sender: TObject);
    procedure MenuCartClick(Sender: TObject);
    procedure MenuExitClick(Sender: TObject);
    procedure MenuHelpAboutClick(Sender: TObject);
    procedure MenuHelpManualClick(Sender: TObject);
    procedure MenuHelpWebsiteClick(Sender: TObject);
    procedure MenuImportClick(Sender: TObject);
    procedure MenuItemGeminiClick(Sender: TObject);
    procedure MenuItemLogSaveClick(Sender: TObject);
    procedure MenuItemLogClearClick(Sender: TObject);
    procedure MenuNewClick(Sender: TObject);
    procedure MenuPrefClick(Sender: TObject);
    procedure MenuSXBtoXBClick(Sender: TObject);
    procedure MenuOpenClick(Sender: TObject);
    procedure MenuSaveClick(Sender: TObject);
    procedure MenuTIdBitClick(Sender: TObject);
    procedure MenuTokenizeClick(Sender: TObject);
    procedure MenuUndoClick(Sender: TObject);
    procedure MnBeautyClick(Sender: TObject);
    procedure MnCommentClick(Sender: TObject);
    procedure MnDumpCharsetClick(Sender: TObject);
    procedure MnDumpFontClick(Sender: TObject);
    procedure MnFindClick(Sender: TObject);
    procedure MnHexBinClick(Sender: TObject);
    procedure MnHighlightClick(Sender: TObject);
    procedure MnHTMLClick(Sender: TObject);
    procedure MnLoadCharsetClick(Sender: TObject);
    procedure MnLoadFontClick(Sender: TObject);
    procedure MnRecent1Click(Sender: TObject);
    procedure MnRepIdentClick(Sender: TObject);
    procedure MnReplaceClick(Sender: TObject);
    procedure MnSaveAsClick(Sender: TObject);
    procedure MnSpecialClick(Sender: TObject);
    procedure MnTiFilesEditClick(Sender: TObject);
    procedure PageControl1Change(Sender: TObject);
    procedure PanelIntResize(Sender: TObject);
    procedure RadioGroup1Click(Sender: TObject);
    procedure ReplaceDialog1Replace(Sender: TObject);
    procedure ReplaceDialog1Show(Sender: TObject);
    procedure sbCopyClick(Sender: TObject);
    procedure sbDownClick(Sender: TObject);
    procedure sbInvertClick(Sender: TObject);
    procedure sbLeftClick(Sender: TObject);
    procedure sbMirrorHClick(Sender: TObject);
    procedure sbMirrorVClick(Sender: TObject);
    procedure sbPasteClick(Sender: TObject);
    procedure sbPlayClick(Sender: TObject);
    procedure sbRightClick(Sender: TObject);
    procedure sbRotLeftClick(Sender: TObject);
    procedure sbRotRightClick(Sender: TObject);
    procedure sbUpClick(Sender: TObject);
    procedure SGvarResize(Sender: TObject);
    procedure SGvarValidateEntry(sender: TObject; aCol, aRow: Integer;
      const OldValue: string; var NewValue: String);
    procedure StatusBar1Resize(Sender: TObject);
    procedure SynCompletion1CodeCompletion(var Value: string;
      SourceValue: string; var SourceStart, SourceEnd: TPoint;
      KeyChar: TUTF8Char; Shift: TShiftState);
    procedure SynEdit1Change(Sender: TObject);
    procedure SynEdit1DblClick(Sender: TObject);
    procedure SynEdit1KeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure SynEdit1MouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure TabControl1Change(Sender: TObject);
    procedure TabSheet4Show(Sender: TObject);
    procedure TabSheet5Hide(Sender: TObject);
    procedure TabSheet5Resize(Sender: TObject);
    procedure TabSheet5Show(Sender: TObject);
    procedure TabSheet6Hide(Sender: TObject);
    procedure TabSheet6Show(Sender: TObject);
    procedure tbGridClick(Sender: TObject);
    procedure TimerAnimationTimer(Sender: TObject);
    procedure TimerHexTimer(Sender: TObject);
    procedure TrackBarAniChange(Sender: TObject);
    procedure TrackBarVerboseChange(Sender: TObject);
    procedure xbDblClick(Sender: TObject);
  private

  public

  end;

var
  Form1: TForm1;
  bgbmp: TImage;
  sxbfile : String;
  prj,prefs : TIniFile;
  clfg, clbg : TColor;
  hexmanmode,vxrloaded : boolean;
  DefDev : String[8];
  AssPath, FIAD, AssInc, asmproclist: string;
  AssTimeOut: Integer;
  AssList: boolean;
  fnconv : Integer;
  bt : packed array[0..127,0..63] of boolean;
  btx,bty, btm : Integer;
  cidx : Array[0..15,0..7] of integer;
  cpage,cbase: Integer;
  FindPos : Integer;
  DataDSK : array[0..4] of record
    lbl : String[10];
    pth : String[128];
  end;
  Recent : TStringList;
  CompileOnly : boolean;
  CartDevPath, CartRelPath: String[128];
  CartDevBase, CartRelBase: Longint;
  CartRelPad : Boolean;
  NavStack : TStringList;


Procedure WriteLogLine(l:integer; s:ShortString);

implementation

{$R *.lfm}

{ TForm1 }

uses math,ctype,SynEditTypes, LCLIntf, fileutil, strutils, Process,Clipbrd,
  uAbout, uPrefs, uImport, uHighlight, uWinKeySend,uTICharset,
  uHex2Bin, uImageConvert, uRepIdent, uTiFilesEdit,uCartPatch,uGeminiDlg;

const hexUpdate = 1000;

Procedure WriteLogLine(l:integer; s:ShortString);
begin
  if l<=Form1.TrackBarVerbose.Position then Form1.log.Lines.Append(s);
  if l=0 then inc(StatRec.errors);
  if l=1 then inc(StatRec.warnings);
end;

Procedure SaveIntegration(sect:String;sl:TStrings;auto:TCheckBox);
VAR i : integer;
begin
  prj.EraseSection(sect);
  for i:=0 to min(sl.Count-1,19) do
    if sl.Strings[i]<>'' then prj.WriteString(sect,'Line'+IntToStr(i),sl.Strings[i]);
  prj.writebool(sect,'Auto',auto.Checked);
end;

Procedure ReadIntegration(sect:String;sl:TStrings;auto:TCheckBox);
VAR i : integer;
    s : String;
begin
  sl.Clear;
  for i:=0 to 19 do begin
    s:=prj.ReadString(sect,'Line'+IntToStr(i),'');
    if s<>'' then sl.Add(s);
  end;
  auto.Checked := prj.readbool(sect,'Auto',false);
end;

procedure IncludeAssembler(fn:string);
var infile,outfile,incfile: textfile;
    s,fntemp,fninc : string;
begin
  fntemp := ChangeFileExt(fn,'.tmp');
  fninc  := ChangeFileExt(fn,'.inc');
  assign(infile,fn);
  rename(infile,fntemp);
  reset(infile);
  assign(outfile,fn);
  rewrite(outfile);
  assign(incfile,fninc);
  reset(incfile);

  while not eof(infile) do begin
    readln(infile,s);
    writeln(outfile,s);
    if s='ENDCC' then while not eof(incfile) do begin
      readln(incfile,s);
      writeln(outfile,s);
    end;
  end;

  close(outfile);
  close(incfile);
  close(infile);
  deleteFile(fntemp);
end;

Procedure UpdateRecent(s:string);
begin
  if s<>'' then begin
    if Recent.IndexOf(s)<>-1 then
      Recent.Delete(Recent.IndexOf(s));
    Recent.Insert(0,s);
  end;
  while Recent.Count>4 do Recent.Delete(4);
  form1.MnRecent1.Caption:=Recent.Strings[0];
  form1.MnRecent2.Caption:=Recent.Strings[1];
  form1.MnRecent3.Caption:=Recent.Strings[2];
  form1.MnRecent4.Caption:=Recent.Strings[3];
end;


Function LoadFile(sxb : String):boolean;
var flag : boolean;
    libs : string;
    i : integer;
begin
  loadfile := false;
  if fileexists(sxb) then with form1 do begin
    MenuNewClick(NIL);
    sxbfile  := sxb;
    UpdateRecent(sxb);
    Form1.SynEdit1.Lines.LoadFromFile(sxb);
    Form1.Caption:='TI Code Editor - '+sxbfile;
    LoadFile := true;
    EdFileSXB.Text:=sxb;
    EdFilePrj.Text := ChangeFileExt(sxb,'.xbp');
    prj := TiniFile.Create(EdFilePrj.Text);
    EdFileXBtxt.Text := prj.ReadString('Files','XBtxt',ChangeFileExt(sxb,'.xb'));
    hexstr.Text:='';
    if FIAD='' then begin
      EdFileXB.Text    := prj.ReadString('Files','XB',ChangeFileExt(sxb,''));
      EdFileXBM.Text   := prj.ReadString('Files','Merge',ChangeFileExt(sxb,'')+'-M');
    end else begin
      EdFileXB.Text    := prj.ReadString('Files','XB',FIAD + '\' + ChangeFileExt(ExtractFileName(sxbfile),''));
      EdFileXBM.Text   := prj.ReadString('Files','Merge',FIAD + '\' + ChangeFileExt(ExtractFileName(sxbfile),'')+'-M');
    end;
    EdFileVXR.Text   := prj.ReadString('Files','VXRef',ChangeFileExt(sxb,'')+'.vxr');
    EdFileCSet.Text  := prj.ReadString('Files','CharSet',ChangeFileExt(sxb,'')+'.cset');
    cbtkn.checked    := prj.ReadBool('Build','Token',True);
    cbMrg.checked    := prj.ReadBool('Build','Merge',True);
    StatRec.size     := prj.ReadInteger('Build','Size',0);
    MaskEdit1.Text   := prj.ReadString('Param','LineStart','100');
    MaskEdit2.Text   := prj.ReadString('Param','LineInc','10');
    MaskEdit3.Text   := prj.ReadString('Param','LineGOSUB','20000');
    MaskEdit4.Text   := prj.ReadString('Param','LineSUB','30000');
    cbMemBase.ItemIndex:= cbMemBase.Items.IndexOf(prj.ReadString('Param','MemBase','auto'));
    cbTiBas.checked  := prj.ReadBool('Param','TiBas',false);
    cbDebug.checked  := prj.ReadBool('Param','Debug',True);
    GeminiMode       := prj.ReadInteger('Param','GeminiMode',1);
    TrackBarVerbose.Position := prj.ReadInteger('Param','Verbose',3);
    cbFormat.ItemIndex:=prj.ReadInteger('Param','Format',0);
    EdFileLib.Text   := prj.ReadString('Lib','User','');
    cbStandardLib.checked := prj.ReadBool('Lib','Standard',true);
    cbLcLink.checked := prj.ReadBool('Lib','LCLink',false);
    libs             := prj.ReadString('Lib','Libs','');
    for i:=0 to clbLib.Count-1 do if pos(clbLib.Items.Strings[i],libs)>0
       then clbLib.Checked[i] := true
       else clbLib.Checked[i] := false;


    EdCommand.Text   := prj.ReadString('PostProc','Command','');
    cbCommand.checked:= prj.ReadBool('PostProc','Active',False);

    ReadIntegration('IntLoad',MemoIntLoad.Lines,CBIntAutoLoad);
    ReadIntegration('IntRun',MemoIntRun.Lines,CBIntAutoRun);
    ReadIntegration('IntCompile',MemoIntCompile.Lines,CBIntAutoCompile);

    CartDevPath  := prj.ReadString('CartPatch','CartDevPath','');
    CartRelPath  := prj.ReadString('CartPatch','CartRelPath','');
    CartDevBase  := prj.ReadInteger('CartPatch','CartDevBase',-1);
    CartRelBase  := prj.ReadInteger('CartPatch','CartRelBase',-1);
    CartRelPad   := prj.ReadBool('CartPatch','CartRelPad',false);
    if not LoadCSet(EdFileCSet.Text) then
      WriteLogLine(2,'Charset File '+EdFileCSet.Text+' not loaded.');;

    WriteLogLine(-1,'File '+sxbfile+' loaded.');
    vxrloaded:=false;
    TabSheet6Show(NIL);
    ctabSelectCell(nil,1,1,flag);   // initialize the char-editor
    form1.ctab.Repaint;
    StatusBar1Resize(NIL);
  end else Application.MessageBox(pChar('File '+sxb+' not found.'), 'Error',0 );
end;

Procedure SaveFile;
var i:integer;
    libs:shortstring;
begin
  with Form1 do begin
    if sxbfile = '' then begin
      if trim(SynEdit1.Text)='' then exit;
      SaveDialog1.Title:='Save new file';
      SaveDialog1.FileName := '';
      if SaveDialog1.Execute then begin
        sxbfile := SaveDialog1.FileName;
        if pos('.SXB',UpperCase(sxbfile))=0 then sxbfile:=sxbfile+'.sxb';
        Form1.Caption:='TI Code Editor - '+sxbfile;
        EdFileSXB.Text:=sxbfile;
        EdFilePrj.Text := ChangeFileExt(sxbfile,'.xbp');
        prj := TiniFile.Create(EdFilePrj.Text);
        EdFileXBtxt.Text := ChangeFileExt(sxbfile,'.xb');
        EdFileVXR.Text   := ChangeFileExt(sxbfile,'')+'.vxr';
        EdFileCSet.Text  := ChangeFileExt(sxbfile,'')+'.cset';
        if FIAD='' then begin
          EdFileXB.Text    := ChangeFileExt(sxbfile,'');
          EdFileXBM.Text   := ChangeFileExt(sxbfile,'')+'-M';
        end else begin
          EdFileXB.Text    := FIAD + '\' + ChangeFileExt(ExtractFileName(sxbfile),'');
          EdFileXBM.Text   := FIAD + '\' + ChangeFileExt(ExtractFileName(sxbfile),'')+'-M';
        end;
      end;
    end;
    if sxbfile = '' then application.MessageBox('Save file cancelled.','Warning',MB_OK)
    else begin
      SynEdit1.Lines.SaveToFile(sxbfile);
      xb.Lines.SaveToFile(EdFileXBtxt.Text);
      // if no package selected ... select 0 = Empty
      // build string of selected libraries
      libs:='';
      for i:=0 to clbLib.Count-1 do
        if clbLib.Checked[i] then libs:=libs+';'+clbLib.Items.Strings[i];

      SaveCSet(EdFileCSet.Text);
      // writing INI-File
      prj.WriteString('Files','XBtxt',EdFileXBtxt.Text);
      prj.WriteString('Files','XB',EdFileXB.Text);
      prj.WriteString('Files','SXB',EdFileSXB.Text);
      prj.WriteString('Files','Merge',EdFileXBM.Text);
      prj.WriteString('Files','VXRef',EdFileVXR.Text);
      prj.WriteString('Files','CharSet',EdFileCSet.Text);
      prj.WriteBool('Build','Token',cbtkn.checked);
      prj.WriteBool('Build','Merge',cbMrg.checked);
      prj.WriteInteger('Build','Size',StatRec.size);
      prj.WriteString('Param','LineStart',MaskEdit1.Text);
      prj.WriteString('Param','LineInc',MaskEdit2.Text);
      prj.WriteString('Param','LineGOSUB',MaskEdit3.Text);
      prj.WriteString('Param','LineSUB',MaskEdit4.Text);
      prj.WriteString('Param','MemBase',cbMemBase.Text);
      prj.WriteBool('Param','TiBas',cbTiBas.checked);
      prj.WriteInteger('Param','Verbose',TrackBarVerbose.Position);
      prj.WriteInteger('Param','Format',cbFormat.ItemIndex);
      prj.WriteBool('Param','Debug',cbDebug.checked);
      prj.WriteInteger('Param','GeminiMode',GeminiMode);
      prj.WriteString('Lib','User',EdFileLib.Text);
      prj.WriteBool('Lib','Standard',cbStandardLib.checked);
      prj.WriteBool('Lib','LCLink',cbLcLink.checked);
      prj.WriteString('Lib','Libs',libs);
      prj.WriteString('PostProc','Command',EdCommand.Text);
      prj.WriteBool('PostProc','Active',cbCommand.checked);

      if MemoIntLoad.Lines.Count=0    then MemoIntLoadDblClick(NIL);
      if MemoIntRun.Lines.Count=0     then MemoIntRunDblClick(NIL);
      if MemoIntCompile.Lines.Count=0 then MemoIntCompileDblClick(NIL);
      SaveIntegration('IntLoad',MemoIntLoad.Lines,CBIntAutoLoad);
      SaveIntegration('IntRun',MemoIntRun.Lines,CBIntAutoRun);
      SaveIntegration('IntCompile',MemoIntCompile.Lines,CBIntAutoCompile);

      if CartDevPath<>'' then prj.WriteString('CartPatch','CartDevPath',CartDevPath);
      if CartRelPath<>'' then prj.WriteString('CartPatch','CartRelPath',CartRelPath);
      if CartDevBase<>-1 then prj.WriteInteger('CartPatch','CartDevBase',CartDevBase);
      if CartRelBase<>-1 then begin
        prj.WriteInteger('CartPatch','CartRelBase',CartRelBase);
        prj.WriteBool('CartPatch','CartRelPad',CartRelPad);
      end;

      prj.UpdateFile;
      UpdateRecent(sxbfile);
    end;
  end;
end;

procedure UpdateStatusEdit;
var x : Integer;
begin
  with form1 do begin
    StatusBar1.Panels.Items[1].Text:='Line '+IntToStr(SynEdit1.LogicalCaretXY.Y)+'  Col '+IntToStr(SynEdit1.LogicalCaretXY.X);
    x := length(SynEdit1.SelText);
    if x>0 then Statusbar1.Panels.Items[4].Text:=IntToStr(x)+' char selected.'
           else Statusbar1.Panels.Items[4].Text:='';
  end;
end;


Procedure UpdateAutoCompleteReadLib(l:String;ts:TStringList);
var lib :TStringList;
    i,j : Integer;
    s,t : String;
    lbl : boolean;
    td : TStringDynArray;
begin
  if l='' then exit;
  lib := TStringList.Create;
  if FileExists(l) then begin
    lib.LoadFromFile(l);
    for s in lib do begin
      t := UpperCase(trim(s));
      if LeftStr(t,9) = 'INTERNAL:' then begin
         td := SplitString(copy(t,10,255),' ');
         for t in td do ts.add(t+' - call');
      end
      else if LeftStr(t,5) = 'CALL ' then begin
         t := trim(copy(t,6,255));
         i := 1;
         while (i<length(t)) and isalnum(t[i]) do inc(i);
         ts.Add(trim(copy(t,1,i))+' - call');
      end
      else if LeftStr(t,4) = 'SUB ' then begin
         t := trim(copy(t,5,255));
         i := 1;
         while (i<length(t)) and isalnum(t[i]) do inc(i);
         ts.Add(trim(copy(t,1,i))+' - call');
      end
      else if LeftStr(t,4) = 'ASM ' then begin
         t := trim(copy(t,5,255));
         i := 1;
         while (i<length(t)) and isalnum(t[i]) do inc(i);
         ts.Add(trim(copy(t,1,i))+' - call (ASM)');
      end else begin
        i:=pos(':',t);
        if i>0 then begin
          lbl:=true;
          for j:=1 to i-1 do if (not isalnum(t[j])) and (t[j]<>'_') then lbl:=false;
          if lbl then ts.Add(copy(t,1,i-1)+' - label');
        end;
      end;
    end;
  end else WriteLogLine(2,'Library '+l+' not found.');
  lib.Destroy;
end;



Procedure UpdateAutoComplete;
var ts : TStringList;
    i  : Integer;
    s  : String;
begin
  ts := TStringList.Create;
  // XB Standard Statements and Routines
  ts.Duplicates:=dupIgnore;
  ts.StrictDelimiter:=true;
  ts.Delimiter:=';';
  ts.DelimitedText:='ABS;ACCEPT;ALL;AND;APPEND;ASC;AT;ATN;BASE;BEEP;BIN$;BREAK;CALL;CHAR( - call;CHARPAT( - call;'+
     'CHARSET( - call;CHR$;CLEAR - call;CLOSE;COINC( - call;COLOR( - call;CONST;COS;DATA;DEF;DELETE;DELSPRITE( - call;'+
     'DIGIT;DIM;DISPLAY;DISTANCE( - call;ELSE;END;ENDWHILE;EOF;ERASE;ERR( - call;ERROR;EXP;FIXED;FOR;GCHAR( - call;'+
     'GO;GOSUB;GOTO;HCHAR( - call;IF;IMAGE;IN;INIT( - call;INPUT;INT;INTERNAL;JOYST( - call;KEY( - call;LEN;LET;'+
     'LINK( - call;LINPUT;LOAD( - call;LOCATE( - call;LOG;MAGNIFY( - call;MAX;MIN;MOTION( - call;NEXT;NOT;NUMERIC;'+
     'ON;OPEN;OPTION;OR;OUTPUT;PATTERN( - call;PEEK( - call;PERMANENT;PI;POS;POSITION( - call;PRINT;RANDOMIZE;READ;'+
     'REC;RELATIVE;REM;REPEAT;RESTORE;RETURN;RND;RPT$;RUN;SAY( - call;SCREEN( - call;SEG$;SEQUENTIAL;SGN;SIN;SIZE;'+
     'SOUND( - call;SPGET( - call;SPRITE( - call;SQR;STEP;STOP;STR$;SUB;SUBEND;SUBEXIT;TAB;TAN;THEN;TO;TRACE;UALPHA;'+
     'UNBREAK;UNTIL;UNTRACE;UPDATE;USING;VAL;VALIDATE;VARIABLE;VCHAR( - call;VERSION( - call;WARNING;WHILE;WEND;XOR';

  ts.Sort;
  ts.Sorted:=true;
  // Used Variables min 3 char
  for i:=1 to form1.SGvar.RowCount-1 do begin
    s:=form1.SGvar.Cells[1,i];
    if length(s)>2 then ts.Add(s+' - variable');
  end;

  // Load User-Library
  UpdateAutoCompleteReadLib(Form1.EdFileLib.Text,ts);
  // Load Package-Files
  for i:=0 to form1.clbLib.Count-1 do if form1.clbLib.Checked[i] then
    UpdateAutoCompleteReadLib(ExtractFileDir(Paramstr(0))+'\lib\'+form1.clbLib.Items.Strings[i],ts);
  // Load Standard-Library
  if form1.cbStandardLib.Checked
     then UpdateAutoCompleteReadLib(ExtractFileDir(Paramstr(0))+'\lib\StdLib.xblib',ts);
  // Load local SUB-Routines and Labels
  UpdateAutoCompleteReadLib(form1.EdFileSXB.Text,ts);

  form1.SynCompletion1.ItemList.Clear;
  form1.SynCompletion1.ItemList.AddStrings(ts,true);
  ts.Destroy;
end;

function GetHex(x,y:integer):String;
var s,t : string;
    i : integer;
begin
  t:='';
  s:='';
  for i:=0 to 3 do if bt[x+i,y]   then t:=t+'1' else t:=t+'0';
  for i:=0 to 3 do if bt[x+i+4,y] then s:=s+'1' else s:=s+'0';
  Result := nibble2hex(t)+nibble2hex(s);
end;


procedure SetHex(x,y:integer; s:string);
var t: string;
    i: integer;
begin
  t:=hex2bin(s);
  for i:=0 to 7 do if copy(t,i+1,1)='1' then bt[x+i,y]:= true else bt[x+i,y]:= false;
end;

Procedure DrawPixel(x,y:Integer);
var dx : Integer;
    dbx,dby :real;
    src,dst: TRect;
begin
  with form1.CharEd do begin
    dx := width DIV (btx+1);
    if bt[x,y] then Canvas.Brush.Color:=clfg
               else Canvas.Brush.Color:=clbg;
    Form1.CharEd.Canvas.Pen.Color := clGray;
    canvas.Rectangle(x*dx,y*dx,(x+1)*dx-1,(y+1)*dx-1);
    if (form1.ColorBoxBG.ItemIndex=0) and (bgbmp.Picture.Bitmap.Width>0) and not bt[x,y] then begin
      dbx := bgbmp.Picture.Bitmap.Width / (btx+1);
      dby := bgbmp.Picture.Bitmap.Height / (bty+1);
      dst:=TRect.Create(x*dx+1,y*dx+1,(x+1)*dx-2,(y+1)*dx-2);
      src:=TRect.Create(round(x*dbx),round(y*dby),round((x+1)*dbx-2),round((y+1)*dby-2));
      canvas.CopyRect(dst,bgbmp.Picture.Bitmap.Canvas,src);
    end;
  end;  // with CharEd
  with form1.ImPreview do begin
    if bt[x,y] then Canvas.pen.Color:=clfg
               else Canvas.pen.Color:=clbg;
    canvas.Rectangle(x*2+1,y*2+1,2*x+3,2*y+3);
  end;  // with Preview

end;

Procedure DrawCharEd;
var x,y,dx:Integer;
begin
  Form1.CharEd.Canvas.Brush.Style:=bsSolid;
  Form1.CharEd.Canvas.Brush.color:=clGray;

  // Set the border color
  Form1.CharEd.Canvas.Pen.Color := clGray;
  Form1.CharEd.Canvas.Pen.Width := 1;
  Form1.CharEd.Canvas.Pen.Style := psSolid;
  Form1.ImPreview.Canvas.Pen.Width  := 1;
  Form1.ImPreview.Canvas.Pen.Style  := psSolid;
  Form1.ImPreview.Canvas.Brush.Style:= bsSolid;

  form1.CharEd.Picture.Clear;
  // form1.ImPreview.Picture.clear;

  for x:=0 to btx do
    for y:=0 to bty do DrawPixel(x,y);

  // Draw red 8x8 grid if selected
  if Form1.tbGrid.Checked then begin
    dx := Form1.CharEd.width DIV (btx+1) * 8;
    Form1.CharEd.Canvas.Pen.Color := clRed;
    Form1.CharEd.Canvas.Brush.Style:=bsClear;
    for x:=0 to (btx-7) DIV 8 do
      for y:=0 to (bty-7) DIV 8 do
         Form1.CharEd.Canvas.Rectangle(x*dx,y*dx,(x+1)*dx,(y+1)*dx);
  end;

end;

Procedure ResizeCharEd;
var x,y,dx:integer;
begin
  dx := 25;
  while dx*(btx+1) > form1.Width-300 do dec(dx);
  x := dx * (btx+1);
  y := dx * (bty+1);

  form1.CharEd.Width  := x;
  form1.CharEd.Height := y;
  form1.CharEd.Picture.Bitmap.Width  := x;
  form1.CharEd.Picture.Bitmap.Height := y;
  form1.CharEd.Left   := (form1.Width-300 - x) DIV 2;

  form1.ImPreview.Picture.Bitmap.Height:=bty*2+5;
  form1.ImPreview.Picture.Bitmap.Width :=btx*2+5;
  form1.ImPreview.Width:=btx*2+5;
  form1.ImPreview.Height:=bty*2+5;
  form1.ImPreview.Canvas.Brush.Color:=clblue;
  form1.ImPreview.Canvas.pen.Color:=clBlack;

  form1.ImPreview.Canvas.Rectangle(0,0,btx*2+4,bty*2+4);

end;

procedure CalcCharIndex;
var i,j,k,ctemp : integer;
begin
  for i:=0 to 15 do
    for j:=0 to 7 do cidx[i,j] := -1;
  ctemp := cbase;
  case form1.rgCharSize.ItemIndex of
    0 : cidx[0,0] := cbase;
    1 : begin
          cidx[0,0] := cbase;
          cidx[0,1] := cbase+1;
          cidx[1,0] := cbase+2;
          cidx[1,1] := cbase+3;
        end;
    2 : for i:=0 to (btx-7) DIV 8 do
          for j:=0 to (bty-7) DIV 8 do begin
             cidx[i,j] := ctemp;
             inc(ctemp);
          end;
    3 : for j:=0 to (bty-7) DIV 8 do
           for i:=0 to (btx-7) DIV 8 do begin
             cidx[i,j] := ctemp;
             inc(ctemp);
          end;
  end;
  for i:=0 to 15 do
    for j:=0 to 7 do
      if cidx[i,j]<>-1 then
        for k:=0 to 7 do SetHex(i*8,j*8+k,copy(cset[cPage,cidx[i,j]].pat,k*2+1,2));
  DrawCharEd;
end;

procedure SetGridHex(c,r:integer);
var p,i,offset: Integer;
    s: String;
begin
  s:='';
  p := cidx[c,r];
  if p=-1 then exit;
  for i:=0 to 7 do s:=s+GetHex(8*c,8*r+i);
  CallCharUpdate(cPage,p);
  if (cset[cPage,p].pat='') and (s='0000000000000000') then s:='';
  cset[cPage,p].pat:=s;
  if cPage=0 then offset := 32 else offset := 0;
  try
    form1.ctab.Cells[2,p+1-offset]:=s;
    form1.ctab.InvalidateCell(1,p+1-offset);
  except
    // when started by clicking on an SXB file without XBP file an error occurs
    // on setting up the environment
  end;
end;

Procedure GroupChar;
var i,sz,pg : Integer;
begin
    pg := Form1.TabControl1.TabIndex;
  // ungroup old setting to single char
  if cset[pg,cbase].size<>-1 then begin
    i:=cset[pg,cbase].strt;
    while cset[pg,i].strt=cset[pg,cbase].strt do begin
      cset[pg,i].strt := i;
      cset[pg,i].size := 0;
      cset[pg,i].cx   := 0;
      cset[pg,i].cy   := 0;
      inc(i);
    end;
  end;

  // set new Group
  case form1.rgCharSize.ItemIndex of
    0 : sz := 1;
    1 : sz := 4;
    2,3 : sz := (form1.cbCol.ItemIndex+1)*(form1.cbRow.ItemIndex+1);
  end;
  for i:=1 to sz do begin
    cset[pg,cbase+i-1].size := form1.rgCharSize.ItemIndex;
    cset[pg,cbase+i-1].strt := cbase;
    cset[pg,cbase+i-1].cx   := form1.cbCol.ItemIndex+1;
    cset[pg,cbase+i-1].cy   := form1.cbRow.ItemIndex+1;
  end;
  form1.ctab.Refresh;
end;


procedure CheckTIFILES(fn:string);
var buf : array[0..65535] of byte;
    f : file of byte;
    sz: word;
begin
  WriteLogLine(5,'Check Assembly file ' +fn+ ' for TIFILES header.');
  sz := FileSize(fn);
  assign(f,fn);
  reset(f);
  blockread(f,buf,sz);
  if TEncoding.ASCII.GetString(Buf,1,7) = 'TIFILES' then begin
    WriteLogLine(3,'Found TIFILES-Header in Assembly ... remove header.');
//    rewrite(f);
//    blockwrite(f,buf[128],sz-128);
  end;
  closefile(f);
end;

Function SendCommand(sl : TStrings):Boolean;
var h : THandle;
    AProcess: TProcess;
    buf : TstringList;
    i,j,r : Integer;
    s : ShortString;
begin
  buf  := TStringList.Create;
  val(FormPrefs.EdIntDel.Text, WinKeySendDelay, r);
  if r<>0 then WinKeySendDelay:=20;
  h := GetWindowHandle(FormPrefs.RGIntTarget.ItemIndex, FormPrefs.EdIntStr.Text);
  if h>0 then begin
    for i:=0 to sl.Count-1 do
      if UpperCase(LeftStr(trim(sl.Strings[i]),11))=':WAITFILE. ' then begin
          s := copy(trim(sl.Strings[i]),12,255);
          r :=0;
          while (FileSize(s)<>0) and (r<50) do begin // -1 for non-existant file, >0 for closed file
            inc(r);
            sleep(100);
            if r=1 then WriteLogLine(4,'   Wait for ASM-file to be opened...')
                   else WriteLogLine(5,'   '+inttostr(r*100)+' ms.');
          end;
          WriteLogLine(2,'Wait for file '+s+' to be written...  (max. '+inttostr(AssTimeOut)+' sec.)');
          r :=0;
          while (FileSize(s)=0) and (r<AssTimeOut*2) do begin
            inc(r);
            sleep(500);
            if odd(r) then WriteLogLine(5,'   ... '+inttostr(r DIV 2)+' sec.');
          end;
          if r=AssTimeOut*2 then begin
            WriteLogLine(1,'Warning: Compiler Time-Out!');
            exit;
          end;
      end else if UpperCase(LeftStr(trim(sl.Strings[i]),9))=':DIALOG. ' then begin
          WriteLogLine(5,'Display Dialog.');
          r:=MessageDlg('TiCodEd Integration',copy(trim(sl.Strings[i]),10,255),mtConfirmation,[mbYes,mbCancel],0);
          if r=mrCancel then exit;
      end else if UpperCase(LeftStr(trim(sl.Strings[i]),12))=':ASSEMBLER. ' then begin
          if fileexists(ChangeFileExt(copy(trim(sl.Strings[i]),13,255),'.inc')) then begin
            WriteLogLine(3,'Include '+ChangeFileExt(copy(trim(sl.Strings[i]),13,255),'.inc')+' in assembler source');
            IncludeAssembler(copy(trim(sl.Strings[i]),13,255));
          end;
          if form1.cbAORG.ItemIndex<>0 then begin
            WriteLogLine(3,'Inserting AORG directive.');
            buf.Clear;
            buf.LoadFromFile(copy(trim(sl.Strings[i]),13,255));
            buf.Insert(1,'       '+Form1.cbAORG.Text);
            buf.SaveToFile(copy(trim(sl.Strings[i]),13,255));
            buf.Clear;
            WriteLogLine(3,Form1.cbAORG.Text+' inserted.');
          end;

          CheckTIFILES(copy(trim(sl.Strings[i]),13,255));

          AProcess := TProcess.Create(nil);
          AProcess.PipeBufferSize:=65536;
          AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes, poNoConsole, poStderrToOutPut];
          if PosUnquoted('xas99',AssPath)>0 then begin // Ralph's xas99.py
            //s := 'CMD.EXE /C ' + AssPath + ' -I "'+ AssInc +'" -R -s ';
            s := AssPath + ' -I "'+ AssInc +'" -R -s ';
            if AssList then s := s +' -L '+ChangeFileExt(copy(trim(sl.Strings[i]),13,255),'.lst');
            if form1.TrackBarVerbose.Position<4 then s := s +' --quiet-unused-syms ';
            s := s + ' ' + copy(trim(sl.Strings[i]),13,255) + ' -o '+ ChangeFileExt(copy(trim(sl.Strings[i]),13,255),'.obj');
           end else begin // Fred's xa99.exe
            s := 'CMD.EXE /C ' + AssPath + ' -I "'+copy(trim(sl.Strings[i]),13,255) + '" -R -V';
            if AssList then s := s +' -L -S';
            if form1.TrackBarVerbose.Position>=4 then s := s +' -P';
          end;
          AProcess.{%H-}CommandLine  := s;
          WriteLogLine(3,'Starting Assembler.');
          WriteLogLine(3,'   '+s);

          AProcess.Execute;

          r := AProcess.ExitCode;
          if r=0 then WriteLogLine(3,'Assembler successfully completed.')
                 else WriteLogLine(1,'ERROR: Assembler completed with error '+inttostr(r));
          if r=0 then r:=5 else r:=3;
          buf.LoadFromStream(AProcess.Output);
          form1.Log.Lines.BeginUpdate;
          for j:=0 to buf.Count-1 do WriteLogLine(r,'   '+buf.Strings[j]);
          form1.Log.Lines.EndUpdate;
          form1.Log.Update;
          form1.Log.ScrollBy(0,-30);
          if r=3 then begin // abort integration on error
            ShowMessage('Assembler error! Check LST file.');
            exit;
          end;
      end
    else if copy(sl.Strings[i],1,1)='>' then Result := sendstring(h,copy(sl.Strings[i],2,255))
                                        else Result := sendkeys(h,sl.Strings[i]);
  end else begin
             WriteLogLine(1,'Warning: Window Handle for Integration not found. Emulator running?');
             Result := false;
           end;
   buf.free;
end;




procedure TForm1.MenuSXBtoXBClick(Sender: TObject);
var xbout, PkgFile,LibFile,SxbOptions,asmfile,s,proclist: String;
    LineStart,LineInc,LineGOSUB,LineSUB,rc,i :Integer;
    al : Textfile;
    sl : TStringList;
begin
  LineStart := StrtoInt(trim(MaskEdit1.Text));
  LineInc   := StrtoInt(trim(MaskEdit2.Text));
  LineGOSUB := StrtoInt(trim(MaskEdit3.Text));
  LineSUB   := StrtoInt(trim(MaskEdit4.Text));
  SaveFile;
  xbout    := EdFileXBtxt.text;
  asmfile  := AssInc+'\custom.txt';

  PkgFile  := '';
  for i:=0 to clbLib.Count-1 do if clbLib.Checked[i] then
     PkgFile := PkgFile+ProgramDirectory+'lib\'+clbLib.Items.Strings[i]+';';

  LibFile  := EdFileLib.text;

  if cbDebug.checked then SxbOptions := '-debug'
                     else SxbOptions := '';
  if cbStandardLib.checked then SxbOptions := SxbOptions +' -STDLIB ';
  if cbLcLink.checked      then SxbOptions := SxbOptions +' -LCLINK ';
  SxbOptions := SxbOptions +'-ASMFILE="'+asmfile+'"';

  WriteLogLine(-1,'Save '+sxbfile+' and convert to '+xbout+'.');
  WriteLogLine(2,'Start LineNr: '+MaskEdit1.Text+', Increment: '+MaskEdit2.Text+'.');
  if PkgFile='' then WriteLogLine(2,'Package File: <none>.')
                else WriteLogLine(2,'Package File: '+PkgFile+'.');
  if trim(Libfile) <> '' then
    if FileExists(LibFile) then WriteLogLine(2,'Library File: '+LibFile+'.')
                           else WriteLogLine(1,'Warning: Library File: '+LibFile+' does not exist.');
  WriteLogLine(3,'');
  rc := SXBtoXB(sxbfile,xbout,LineStart,LineInc,LineGOSUB,LineSUB,@WriteLogLine,PkgFile,LibFile,SxbOptions);
  xb.Lines.LoadFromFile(xbout);
  xb.ReadOnly:=true;
  if rc=1 then begin
    TabSheet7.TabVisible:=true;
    CompileOnly:=true;
    MemoAsm.Lines.LoadFromFile(asmfile);
    assignfile(al,AssInc+'\custom.lst');
    reset(al);
    i:=0;
    while not eof(al) do begin
      readln(al,s);
      sgAsmProcs.Cells[i MOD 16,i DIV 16]:=s;
      if i=0 then proclist:='10000 DATA '+s
             else proclist:=proclist+','+s;
      inc(i);
    end;
    closefile(al);
    if proclist<>asmproclist then if Application.MessageBox('Patch the Compiler?','Custom ASM',MB_ICONQUESTION + MB_YESNO)=IDYES then begin
       WriteLogLine(2,'Patching the compiler..');
       sl := TStringList.Create;
       sl.add('OLD DSK1.COMPILER :RET.');
       sl.add(':WAIT2.');
       sl.add(proclist+':RET.');
       sl.add(':WAIT1.');
       sl.add('SAVE DSK1.COMPILER :RET.');
       sl.add(':WAIT2.');
       SendCommand(sl);
       sl.destroy;
       WriteLogLine(2,'Patching complete: '+ProcList);
    end;
    asmproclist:=proclist;
  end;
end;

procedure TForm1.MenuExitClick(Sender: TObject);
begin
  close;
  // Application.Terminate;
end;

procedure TForm1.MenuHelpAboutClick(Sender: TObject);
begin
  FormAbout.ShowModal;
end;

procedure TForm1.MenuHelpManualClick(Sender: TObject);
VAR s :  String;
begin
   WriteLogLine(2,'Opening TiCodEd manual.');
   s:=Application.ExeName;
   s:=ChangeFileExt(s,'.pdf');
   {$IFDEF DARWIN}
   s:=StringReplace(s,'TICodEd.app/Contents/MacOS/','',[rfreplaceall,rfignorecase]);
   OpenDocument(s);
   {$ENDIF}
   if FileExists(s)
      then OpenURL('file:///'+s)
      else WriteLogLine(0,'Manual '+s+' not found.');
end;


procedure TForm1.MenuHelpWebsiteClick(Sender: TObject);
begin
   WriteLogLine(2,'Calling TiCodEd Website.');
   if NOT OpenURL('https://www.lizardware.de/') then WriteLogLine(2,'Failed.')
end;

procedure TForm1.MenuImportClick(Sender: TObject);
var rc,i,ln: Integer;
    s : String;
begin
  RegisterDbg(@WriteLogLine);
  SaveFile;
  rc := FormImport.ShowModal;
  WriteLogLine(5,'Modal Result: '+IntToStr(rc));
  if rc=mrOK then begin
      MenuNewClick(Sender);
      WriteLogLine(3,'Remove line-numbers and adjust indention.');
      with FormImport.xbtext do begin
        for i:=0 to Lines.Count-1 do begin
          s:=lines.Strings[i];
          val(s,ln,rc);
          if rc>1 then s := ' ' + copy(s,rc,255);
          if trim(uppercase(s))='RETURN' then s:='RETURN';
          if trim(uppercase(s))='END' then s:='END';
          if trim(uppercase(s))='SUBEND' then s:='SUBEND';
          if leftstr(trim(uppercase(s)),4)='SUB ' then begin
            s:=trim(s);
            SynEdit1.Lines.Add('')
          end;

          SynEdit1.Lines.Add(s);
        end;
        WriteLogLine(3,'Lines transfered to SXB: '+IntToStr(Lines.Count));
      end;
  end;
end;

procedure TForm1.MenuItemGeminiClick(Sender: TObject);
begin
  FormGemini.ShowModal;
end;

procedure TForm1.MenuNewClick(Sender: TObject);
begin
  // SaveFile;
  sxbfile:='';
  Form1.Caption:='TI Code Editor - New File';
  EdFileSXB.Text   := '';
  EdFilePrj.Text   := '';
  EdFileXBtxt.Text := '';
  EdFileXB.Text    := '';
  EdFileXBM.Text   := '';
  EdFileVXR.Text   := '';
  EdFileCSet.Text  := '';
  EdFileLib.Text   := '';
  EdCommand.Text   := '';
  cbFormat.ItemIndex:=0;
  SynEdit1.Lines.Clear;
  xb.Lines.Clear;
  log.Lines.Clear;
  SGvar.Clear;
  MemoIntLoad.Clear;
  MemoIntrun.Clear;
  MemoIntCompile.Clear;
  initCSet;
  StatRec.warnings:=0; StatRec.errors := 0;
  UpdateAutoComplete;
  MaskEdit1.Text   := '100';
  MaskEdit2.Text   := '10';
  TrackBarVerbose.Position := 2;
  CompileOnly:=false;
end;

procedure TForm1.MenuPrefClick(Sender: TObject);
begin
  FormPrefs.EdAssembler.Text := AssPath;
  FormPrefs.DirEditInc.Text := AssInc;
  FormPrefs.cbAsmList.Checked:= AssList;
  FormPrefs.SpinEditTimeout.Value:= AssTimeOut;
  FormPrefs.DirEditFIAD.Text:=FIAD;
  FormPrefs.edGeminiAPI.Text:=GeminiAPIKey;
  FormPrefs.edGeminiURL.Text:=GeminiURL;
  FormPrefs.rgWinFile.ItemIndex:=fnconv;


  FormPrefs.ShowModal;

  Log.Font.Size:=FontSize;
  SynEdit1.Font.Size:=FontSize;
  xb.Font.Size:=FontSize;
  Prefs.WriteInteger('Font','Size',FontSize);

  // Save Integration Settings
  Prefs.WriteInteger('Integration','Target',FormPrefs.RGIntTarget.ItemIndex);
  Prefs.WriteString('Integration','TargetID',FormPrefs.EdIntStr.text);
  Prefs.WriteString('Integration','Delay',FormPrefs.EdIntDel.Text);
  Prefs.WriteInteger('Integration','Device',FormPrefs.CbDevice.ItemIndex);
  Prefs.WriteBool('Integration','XBCopy',FormPrefs.cbXBtoClipboard.Checked);
  Prefs.WriteInteger('Integration','FileConv',FormPrefs.rgWinFile.ItemIndex);
  Prefs.WriteBool('Environment','Mem32k',FormPrefs.CbMemoryExp.Checked);
  Prefs.WriteString('Environment','FIAD',FormPrefs.DirEditFIAD.Text);
  Prefs.WriteString('Assembler','Path',FormPrefs.EdAssembler.Text);
  Prefs.WriteString('Assembler','Include',FormPrefs.DirEditInc.Text);
  Prefs.WriteBool('Assembler','List',FormPrefs.cbAsmList.Checked);
  Prefs.WriteInteger('Assembler','TimeOut',FormPrefs.SpinEditTimeout.Value);
  Prefs.WriteString('Gemini','URL',FormPrefs.edGeminiURL.Text);
  Prefs.WriteString('Gemini','API-Key',FormPrefs.edGeminiAPI.Text);

  DefDev := FormPrefs.CbDevice.Text;
  AssPath:= FormPrefs.EdAssembler.Text;
  AssInc := FormPrefs.DirEditInc.Text;
  AssList:= FormPrefs.cbAsmList.Checked;
  fnconv := FormPrefs.rgWinFile.ItemIndex;
  AssTimeOut   := FormPrefs.SpinEditTimeout.Value;
  FIAD         := FormPrefs.DirEditFIAD.Text;
  GeminiAPIKey := FormPrefs.edGeminiAPI.Text;
  GeminiURL    := FormPrefs.edGeminiURL.Text;

  if FormPrefs.RGIntTarget.ItemIndex=0 then GroupBoxIntegration.Visible:=false
                                       else GroupBoxIntegration.Visible:=true;

end;

procedure TForm1.MenuItemLogSaveClick(Sender: TObject);
VAR lfn : String;
begin
  lfn := ChangeFileExt(sxbfile,'.log');
  log.Lines.SaveToFile(lfn);
  ShowMessage('Logfile '+lfn+' written.');
end;

procedure TForm1.MenuItemLogClearClick(Sender: TObject);
begin
  log.Lines.Clear;
  StatRec.warnings:=0; StatRec.errors := 0;
end;


procedure TForm1.FormCreate(Sender: TObject);
var ax,ay,i: Integer;
    ext : string[4];
begin
  {$IFDEF WINDOWS}
    ext := '.ini';
  {$ELSE}
    ext := '.rc';
  {$ENDIF}
  prefs := TiniFile.Create(ChangeFileExt(Paramstr(0),ext));
  FontSize := Prefs.ReadInteger('Font','Size',14);
  Log.Font.Size:=FontSize;
  SynEdit1.Font.Size:=FontSize;
  xb.Font.Size:=FontSize;
  asmproclist:='';

  Left   := Prefs.ReadInteger('Window','Left',1);
  Top    := Prefs.ReadInteger('Window','Top',1);
  Height := Prefs.ReadInteger('Window','Height',640);
  Width  := Prefs.ReadInteger('Window','Width',800);

  AssPath   := Prefs.ReadString('Assembler','Path','');
  AssInc    := Prefs.ReadString('Assembler','Include','');
  AssList   := Prefs.ReadBool('Assembler','List',false);
  AssTimeout:= Prefs.ReadInteger('Assembler','Timeout',30);
  FIAD      := Prefs.ReadString('Environment','FIAD','');
  fnconv    := Prefs.ReadInteger('Integration','FileConv',1);

  GeminiURL    := Prefs.ReadString('Gemini','URL','https://generativelanguage.googleapis.com/v1/models/');
  GeminiAPIKey := Prefs.ReadString('Gemini','API-Key','');
  GeminiModel  := Prefs.ReadString('Gemini','Model','');


  for i:=0 to 4 do begin
    DataDSK[i].lbl:=Prefs.ReadString('DataDSK','DSK'+IntToStr(i),'DSK'+IntToStr(i));
    DataDSK[i].pth:=Prefs.ReadString('DataDSK','Path'+IntToStr(i),'');
    if not DirectoryExists(ExtractFileDir(DataDSK[i].pth)) then DataDSK[i].pth := '';
  end;
  // Load "Recent Files"
  recent:=TStringlist.Create;
  Recent.Add(Prefs.ReadString('Recent','File1',''));
  Recent.Add(Prefs.ReadString('Recent','File2',''));
  Recent.Add(Prefs.ReadString('Recent','File3',''));
  Recent.Add(Prefs.ReadString('Recent','File4',''));
  UpdateRecent('');

  SynXBSyn1 := TSynXBSyn.Create(SynEdit1);
  SynEdit1.Highlighter:=SynXBSyn1;
  SynXBSyn1.CommentAttri    := SynVBSyn1.CommentAttri;
  SynXBSyn1.IdentifierAttri := SynVBSyn1.IdentifierAttri;
  SynXBSyn1.KeyAttri        := SynVBSyn1.KeyAttri;
  SynXBSyn1.StringAttri     := SynVBSyn1.StringAttri;
  SynXBSyn1.NumberAttri     := SynVBSyn1.NumberAttri;
  SynXBSyn1.SymbolAttri     := SynVBSyn1.SymbolAttri;

  // Read available Package Files
  chdir(ProgramDirectory+'lib');
  FindAllFiles(clbLib.Items,'','*.xbpkg',false,faDirectory) ;
  chdir(ProgramDirectory);

  sxbfile := '';
  log.Lines.Clear;
  StatRec.warnings:=0; StatRec.errors := 0;
  SynEdit1.Clear;
  InitCSet; // from uTICharset
  vxrloaded := false;
  NavStack  := TStringList.Create;


  Memstat1 :=TShape.Create(StatusBar1);
  Memstat1.Shape   := stRectangle;
  Memstat1.height  := 24;
  Memstat1.Width   := StatusBar1.Width DIV 6;
  Memstat1.left    := (StatusBar1.Width * 5 DIV 6) - 20;
  Memstat1.top     := 4;
  Memstat1.brush.Color  := clMenu;
  Memstat1.pen.Color  := clGray;
  Memstat1.name    := 'MemStatus';
  Memstat1.Align   := alCustom;
  Memstat1.Parent  := StatusBar1;
  Memstat1.ShowHint:= true;


  Memstat2 :=TShape.Create(StatusBar1);
  Memstat2.Shape   := stRectangle;
  Memstat2.height  := 24;
  Memstat2.Width   := 40;
  Memstat2.left    := (StatusBar1.Width * 5 DIV 6) - 20;
  Memstat2.top     := 4;
  Memstat2.brush.Color  := clgreen;
  Memstat2.pen.Color  := clGray;
  Memstat2.name    := 'MemStatus2';
  Memstat2.Align   := alCustom;
  Memstat2.Parent  := StatusBar1;
  Memstat2.ShowHint:= true;

  sxbfile := Paramstr(1);
  if (sxbfile='') or not(FileExists(sxbfile))
     then sxbfile := ''
     else loadfile(sxbfile);


  btx:=7; bty:=7 ;
  for ax:=0 to 127 do
    for ay:=0 to 63 do bt[ax,ay] := false;
  clfg := ColorBoxFG.Selected;
  clbg := ColorBoxBG.Selected;
  ResizeCharEd;
  DrawCharEd;



  bgbmp := TImage.Create(NIL);
  cpage:=2;
  cbase :=0;
  CalcCharIndex;
  {$IFDEF DARWIN}
  MnHTML.Visible:=false;
  {$ENDIF}
end;

procedure TForm1.ColorBoxFGGetColors(Sender: TCustomColorBox; Items: TStrings);
begin
  with Sender do begin
    // items.Clear;
    if tag=1 then AddItem('  1 Transparent',TObject($ffffff));
    AddItem('  2 Black',TObject($000000));
    AddItem('  3 Medium Green',TObject($42c821));
    AddItem('  4 Light Green',TObject($78dc5e));
    AddItem('  5 Blue',TObject($ed5554));
    AddItem('  6 Light Blue',TObject($fc767d));
    AddItem('  7 Dark Red',TObject($4d52d4));
    AddItem('  8 Cyan',TObject($f5eb42));
    AddItem('  9 Red',TObject($5455fc));
    AddItem('10 Light Red',TObject($7879ff));
    AddItem('11 Dark Yellow',TObject($54c1d4));
    AddItem('12 Light Yellow',TObject($80cee6));
    AddItem('13 Dark Green',TObject($3bb021));
    AddItem('14 Magenta',TObject($ba5bc9));
    AddItem('15 Gray',TObject($cccccc));
    AddItem('16 White',TObject($ffffff));
  end;
end;

procedure TForm1.ctabDrawCell(Sender: TObject; aCol, aRow: Integer;
  aRect: TRect; aState: TGridDrawState);
var src : TRect;
begin
  if (aCol=1) and (aRow>0) then begin
    if cPage=0 then aRow:=aRow+32;
    src:=TRect.Create(cPage*8,(aRow-1)*8,cPage*8+8,(aRow)*8);
    ctab.Canvas.CopyRect(aRect,csPrev.Canvas,src);;
  end;
end;

procedure TForm1.ctabEditingDone(Sender: TObject);
begin
  TimerHex.Enabled:=true;
  if hexmanmode then begin
    TimerHexTimer(NIL);
    hexmanmode:=false;
    hexstr.Color:=clWhite;
    form1.ActiveControl:=ColorBoxFG;
  end;
end;

procedure TForm1.ctabPrepareCanvas(sender: TObject; aCol, aRow: Integer;
  aState: TGridDrawState);
var cstart,offset : integer;
begin
   ctab.Canvas.Font.Color:=clBlack;
   cstart := cset[cpage,cbase].strt;
   if cpage=0 then offset := 32
              else offset := 0;
   if (aRow=cbase-offset+1) then ctab.Canvas.Brush.Color :=clRed
   else if (cstart>=0) and (aRow>0) and (cset[cpage,aRow-1+offset].strt = cstart)
            then ctab.Canvas.Brush.Color := clYellow;
   if (aRow>0) and (cset[cpage,aRow-1+offset].size>0) and (aCol=0) then begin
     ctab.Canvas.Font.Bold:=true;
     if cset[cpage,aRow-1+offset].strt = aRow-1 then ctab.Canvas.Brush.Color := clMenu;
   end;
end;

procedure TForm1.ctabSelectCell(Sender: TObject; aCol, aRow: Integer;
  var CanSelect: Boolean);
var c,pg :integer;
begin
  if aCol<3 then begin
    c := max(aRow-1,0);
    pg := TabControl1.TabIndex;
    if pg=0 then c:=c+32; // VB tab starts with 32
    cbase := c;
    if cset[pg,c].size=-1 then begin   // unused char selected
      GroupChar;
    end else begin  // already assigned
      cbase := cset[pg,c].strt;
      cbCol.ItemIndex      := cset[pg,c].cx-1;
      cbRow.ItemIndex      := cset[pg,c].cy-1;
      rgCharSize.ItemIndex := cset[pg,c].size;
      btx:=(cbCol.ItemIndex+1)*8-1;
      bty:=(cbRow.ItemIndex+1)*8-1;
      if cset[pg,cbase].size=0 then begin
        btx := 7; bty := 7;
      end else  if cset[pg,cbase].size=1 then begin
        btx := 15; bty := 15;
      end;
    end;
    ResizeCharEd;
    CalcCharIndex;
    ctab.Refresh;
  end;
end;

procedure TForm1.ctabSelectEditor(Sender: TObject; aCol, aRow: Integer;
  var Editor: TWinControl);
begin
    TimerHex.Enabled:=false;
end;

procedure TForm1.ctabValidateEntry(sender: TObject; aCol, aRow: Integer;
  const OldValue: string; var NewValue: String);
var s: string;
    i,offset: integer;
begin
  if cPage=0 then offset:=32 else offset:=0;
  if (NewValue='') then begin
     s := GetTIChar(aRow+offset-1);
     if (s='') or (s='0000000000000000') then NewValue := ''
                                         else NewValue := s;
  end;
  s:='';
  for i:=1 to length(NewValue) do
     if isxdigit(NewValue[i]) and (NewValue[i]<>' ') then s:=s+NewValue[i];
  if s<>'' then  s := UpperCase(copy(s + DupeString('0',16),1,16));

  // now adjust array
  NewValue := s;
  cset[cpage,max(0,aRow+offset-1)].pat:=s;
  CallCharUpdate(cPage,max(0,aRow+offset-1));
  form1.ctab.InvalidateCell(1,max(0,aRow-1));
  CalcCharIndex;
end;

procedure TForm1.EdFileXBChange(Sender: TObject);
var s : string;
begin
  s := ExtractFileName(EdFileXB.Text);
  if (length(s)>9) or ((fnconv=1) and (pos('.',s)>0)) then
    begin
      EdFileXB.Font.Color:=clRed;
      if fnconv=1 then EdFileXB.Hint:='Filename too long for TI processing or contains a dot.'
                  else EdFileXB.Hint:='Filename too long for TI processing.';
      EdFileXB.ShowHint:=true;
    end
  else
    begin
      EdFileXB.Font.Color:=clBlack;
      EdFileXB.ShowHint:=false;
    end;
end;

procedure TForm1.EdFileXBMChange(Sender: TObject);
var s : string;
begin
  s := ExtractFileName(EdFileXBM.Text);
  if (length(s)>9) or ((fnconv=1) and (pos('.',s)>0)) then
    begin
      EdFileXBM.Font.Color:=clRed;
      if fnconv=1 then EdFileXBM.Hint:='Filename too long for TI processing or contains a dot.'
                  else EdFileXBM.Hint:='Filename too long for TI processing.';
      EdFileXBM.ShowHint:=true;
    end
  else
    begin
      EdFileXBM.Font.Color:=clBlack;
      EdFileXBM.ShowHint:=false;
    end;
end;

procedure TForm1.FindDialog1Close(Sender: TObject);
begin
  SGvar.Options := SGvar.Options - [goDrawFocusSelected];
end;

procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
var i : integer;
begin
  Prefs.WriteInteger('Window','Left',Left);
  Prefs.WriteInteger('Window','Top',Top);
  Prefs.WriteInteger('Window','Height',Height);
  Prefs.WriteInteger('Window','Width',Width);

  for i:=0 to 4 do begin
    Prefs.WriteString('DataDSK','DSK'+IntToStr(i),DataDSK[i].lbl);
    Prefs.WriteString('DataDSK','Path'+IntToStr(i),DataDSK[i].pth);
  end;

  Prefs.WriteString('Recent','File1',Recent.Strings[0]);
  Prefs.WriteString('Recent','File2',Recent.Strings[1]);
  Prefs.WriteString('Recent','File3',Recent.Strings[2]);
  Prefs.WriteString('Recent','File4',Recent.Strings[3]);

  Prefs.WriteString('Gemini','URL',GeminiURL);
  Prefs.WriteString('Gemini','API-Key',GeminiAPIKey);
  Prefs.WriteString('Gemini','Model',GeminiModel);

  Prefs.Destroy;
  if SynEdit1.Lines.Count>0 then SaveFile;
end;

procedure TForm1.ColorBoxBGChange(Sender: TObject);
begin
  clbg := ColorBoxBG.Selected ;
  DrawCharEd;
end;

procedure TForm1.BbIntLoadClick(Sender: TObject);
var r : boolean;
begin
  WriteLogLine(2,'Integration: Send LOAD command.');
  r := SendCommand(MemoIntLoad.Lines);
    if r then PageControl1.ActivePage:=TabSheet1
         else WriteLogLine(1,'LOAD command failed.');
end;

procedure TForm1.BbIntCompileClick(Sender: TObject);
var r : boolean;
begin
  WriteLogLine(2,'Integration: Send COMPILE command.');
  PanelInt.Repaint;
  r := SendCommand(MemoIntCompile.Lines);
    if r then PageControl1.ActivePage:=TabSheet1
         else WriteLogLine(1,'COMPILE command failed.');
end;

procedure TForm1.BbIntRunClick(Sender: TObject);
var r : boolean;
begin
  if CompileOnly then begin
     application.MessageBox('Embedded Assembler for compiling, RUN not possible.','Error');
     WriteLogLine(1,'Embedded Assembler for compiling, RUN not possible.');
     exit;
  end;
  WriteLogLine(2,'Integration: Send RUN command.');
  r := SendCommand(MemoIntRun.Lines);
  if r then PageControl1.ActivePage:=TabSheet1
       else WriteLogLine(1,'RUN command failed.');
end;

procedure TForm1.BnPreScanClick(Sender: TObject);
var i : Integer;
    s : String;
begin
  if SGvar.RowCount=0 then exit;
  s := '';
  for i:=1 to SGvar.RowCount-1 do
    if SGvar.Cells[2,i]='' then s := s + ',' + SGvar.Cells[1,i]
                           else s := s + ',' + SGvar.Cells[2,i];
  s := RightStr(s,Length(s)-1);
  Clipboard.AsText := s;
  application.MessageBox(pchar(s),'Copied to Clipboard:');
end;

procedure TForm1.bbBgImageClick(Sender: TObject);
var fn:String;
begin
  if bgbmp.tag=0 then
    if OpenPictureDialog1.Execute then begin
      fn := OpenPictureDialog1.FileName;
      bgbmp.Picture.LoadFromFile(fn);
      bgbmp.tag := 1;
      DrawCharEd;
    end else
    application.MessageBox('Image loading aborted','Image')
  else begin
    bgbmp.Canvas.Clear;
    bgbmp.tag := 0;
    DrawCharEd;
  end;
end;

procedure TForm1.bbAsmHideClick(Sender: TObject);
begin
   TabSheet7.TabVisible:=false;
end;


procedure TForm1.BtnEraseClick(Sender: TObject);
var ax,ay:integer;
begin
  if Application.MessageBox(pChar('Erase current pattern?'), 'Erase',MB_YESNO+MB_ICONQUESTION) = IDYES then begin
    for ax:=0 to 127 do
      for ay:=0 to 63 do bt[ax,ay] := false;
    DrawCharEd;
  end;
end;

procedure TForm1.cbColChange(Sender: TObject);
begin
  btx:=(cbCol.ItemIndex+1)*8-1;
  if ((bty=7) and (btx>7)) or ((bty=15) and ((btx+1) MOD 16 = 0))
      then gbAnimation.Visible:=true
      else gbAnimation.Visible:=false;

  ResizeCharEd;
  GroupChar;
  CalcCharIndex;
end;

procedure TForm1.CBIntAutoCompileClick(Sender: TObject);
begin
  if CBIntAutoCompile.checked then begin
    CBIntAutoload.Checked:=false;
    CBIntAutoRun.Checked:=false;
  end;
end;

procedure TForm1.CBIntAutoLoadClick(Sender: TObject);
begin
  if CBIntAutoLoad.checked then begin
    CBIntAutoCompile.Checked:=false;
    CBIntAutoRun.Checked:=false;
  end;
end;

procedure TForm1.CBIntAutoRunClick(Sender: TObject);
begin
  if CBIntAutoRun.checked then begin
    CBIntAutoLoad.Checked:=false;
    CBIntAutoCompile.Checked:=false;
  end;
end;

procedure TForm1.cbRowChange(Sender: TObject);
begin
  bty:=(cbRow.ItemIndex+1)*8-1;
  if ((bty=7) and (btx>7)) or ((bty=15) and ((btx+1) MOD 16 = 0))
      then gbAnimation.Visible:=true
      else gbAnimation.Visible:=false;

  ResizeCharEd;
  GroupChar;
  CalcCharIndex;
end;

procedure TForm1.CharEdMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
VAR dx : Integer;
begin
  if hexmanmode then begin
    TimerHexTimer(NIL);
    hexmanmode:=false;
    hexstr.Color:=clWhite;
    form1.ActiveControl:=ColorBoxFG;
  end;
  case button of
    mbLeft:  btm:=1;
    mbRight: btm:=2;
    else     btm:=-1;
  end;
  dx := form1.CharEd.width DIV (btx+1);
  x := x DIV dx;
  y := y DIV dx;
  if bt[X ,y] then bt[X,y]:=false
              else bt[X,y]:=true;
  DrawPixel(X, y);
  TimerHex.Interval:=hexUpdate;;
end;

procedure TForm1.CharEdMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
VAR dx : Integer;
begin
  if btm = -1 then exit;
  dx := form1.CharEd.width DIV (btx+1);
  x := max(min(x DIV dx,127),0);  // make sure x,y in bounds
  y := max(min(y DIV dx,63),0);
  if (btm=1) and (bt[x,y]=false) then begin
     bt[x,y]:=true;
     DrawPixel(x, y);
  end else
  if (btm=2) and (bt[x,y]=true) then begin
     bt[x,y]:=false;
     DrawPixel(x, y);
  end;
    TimerHex.Interval:=hexUpdate;;
end;

procedure TForm1.CharEdMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  btm := -1;
end;

procedure TForm1.clbLibExit(Sender: TObject);
begin
  UpdateAutoComplete;
end;

procedure TForm1.clbLibResize(Sender: TObject);
begin
  clbLib.Columns:=clbLib.Width DIV 250;
end;

procedure TForm1.ColorBoxFGChange(Sender: TObject);
begin
  clfg := ColorBoxFG.Selected;
  DrawCharEd;
end;

procedure TForm1.FormDropFiles(Sender: TObject; const FileNames: array of String  );
var fn : String;
begin
  fn := FileNames[Low(FileNames)];
  if Application.MessageBox(pChar('Load file '+fn+' ?'), 'Drop File',MB_YESNO+MB_ICONQUESTION) = IDYES then begin
    SaveFile;
    LoadFile(fn);
  end;
end;

procedure TForm1.FormShow(Sender: TObject);
begin
  // Read Integration Settings
  //{$IFDEF WINDOWS}
  //FormPrefs.RGIntTarget.ItemIndex   := Prefs.ReadInteger('Integration','Target',0);
  //{$ELSE}
  //FormPrefs.RGIntTarget.ItemIndex   := 0;
  //{$ENDIF}
  FormPrefs.RGIntTarget.ItemIndex   := Prefs.ReadInteger('Integration','Target',0);
  FormPrefs.EdIntStr.text           := Prefs.ReadString('Integration','TargetID','TIWndClass');
  FormPrefs.EdIntDel.Text           := Prefs.ReadString('Integration','Delay','20');
  FormPrefs.CbDevice.ItemIndex      := Prefs.ReadInteger('Integration','Device',3);
  FormPrefs.cbXBtoClipboard.Checked := Prefs.ReadBool('Integration','XBCopy',false);
  FormPrefs.CbMemoryExp.Checked     := Prefs.ReadBool('Environment','Mem32k',true);
  FormPrefs.DirEditFIAD.Text        := Prefs.ReadString('Environment','FIAD','');
  DefDev := FormPrefs.CbDevice.Text;
  if FormPrefs.RGIntTarget.ItemIndex=0 then GroupBoxIntegration.Visible:=false
                                       else GroupBoxIntegration.Visible:=true;
  // Prepare Char-Grid
  TabControl1.TabIndex:=2;
  TabControl1Change(Sender);
  PageControl1.ActivePageIndex:=0;
end;

procedure TForm1.GroupBox1Resize(Sender: TObject);
begin
  EdFileSXB.Width   := GroupBox1.Width - 450;
  EdFileXBtxt.Width := GroupBox1.Width - 450;
  EdFileXB.Width    := GroupBox1.Width - 450;
  EdFileXBM.Width   := GroupBox1.Width - 450;
  EdFileVXR.Width   := GroupBox1.Width - 450;
  EdFileCSet.Width  := GroupBox1.Width - 450;
  EdFilePrj.Width   := GroupBox1.Width - 450;
  EdCommand.Width   := GroupBox1.Width - 450;
end;

procedure TForm1.GroupBoxIntegrationResize(Sender: TObject);
var x : Integer;
begin
  x := (width - 80) DIV 3;
  MemoIntLoad.Left     := 10;
  MemoIntLoad.Width    := x;
  MemoIntRun.Left      := x + 20;
  MemoIntRun.Width     := x;
  MemoIntCompile.left  := 2 * x + 30;
  MemoIntCompile.Width := x;
  CBIntAutoLoad.Left   := 20;
  CBIntAutoRun.Left    := x + 30;
  CBIntAutoCompile.Left:= 2 * x + 40;
end;

procedure TForm1.hexstrChange(Sender: TObject);
begin
  if hexmanmode then TimerHex.Interval:=hexUpdate;
end;

procedure TForm1.hexstrEnter(Sender: TObject);
begin
  hexmanmode:=true;
  hexstr.Color:=clYellow;
end;

procedure TForm1.hexstrExit(Sender: TObject);
begin
  hexmanmode:=false;
  hexstr.Color:=clWhite;
end;

procedure TForm1.MemoIntCompileDblClick(Sender: TObject);
var xbm : String;
begin
  if MemoIntCompile.Lines.Count=0 then begin
    xbm := StringReplace(trim(EdFileXBM.text),'-M','',[rfIgnoreCase]);
    //if fnconv=0 then xbm := xbm + '.txt'
    //            else xbm := xbm + '/txt';
    MemoIntCompile.Lines.Add('RUN :S+:OEM7.:S-DSK1.COMPILER:S+:OEM7.:S- :RET.');
    MemoIntCompile.Lines.Add(':WAIT3.:F3.');
    MemoIntCompile.Lines.Add(DefDev+'.'+ ExtractFileName(EdFileXBM.text)+' :RET.');
    MemoIntCompile.Lines.Add(':RET.');
    MemoIntCompile.Lines.Add('Y:RET.');
    MemoIntCompile.Lines.Add('N:RET.');
    MemoIntCompile.Lines.Add('Y:RET.');
    if AssPath<>'' then begin // when XA99 is active
      MemoIntCompile.Lines.Add(':WAITFILE. ' + XBM +'.txt');
      MemoIntCompile.Lines.Add(':ASSEMBLER. '+ XBM +'.txt');
      MemoIntCompile.Lines.Add(':RET.:WAIT1.');
      MemoIntCompile.Lines.Add('N:RET.:WAIT1.:RET.:WAIT1.');
      MemoIntCompile.Lines.Add(':RET.:WAIT1.:RET.');
    end;
  end;
end;


procedure TForm1.MemoIntLoadDblClick(Sender: TObject);
begin
  if MemoIntLoad.Lines.Count=0 then begin
    MemoIntLoad.Lines.Add('OLD '+DefDev+'.'+ ExtractFileName(EdFileXB.text)+' :RET.');
  end;
end;

procedure TForm1.MemoIntRunDblClick(Sender: TObject);
begin
  if MemoIntRun.Lines.Count=0 then
    MemoIntRun.Lines.Add('RUN :S+:OEM7.:S-'+DefDev+'.'+ ExtractFileName(EdFileXB.text)+':S+:OEM7.:S- :RET.');
end;

procedure TForm1.MenuBuildClick(Sender: TObject);
VAR cs, outs : String;
begin
  log.Lines.Clear;
  StatRec.errors   := 0;
  StatRec.warnings := 0;
  WriteLogLine(-1,'Build Process started.');
  WriteLogLine(-1,'Step 1: Convert Structured Extended Basic to Standard Extended Basic.');
  WriteLogLine(3,'');
  MenuSXBtoXBClick(Sender);

  WriteLogLine(3,'');
  WriteLogLine(-1,'Step 2: Save Standard Extended Basic in TI internal format.');
  WriteLogLine(3,'');
  MenuTokenizeClick(Sender);

  WriteLogLine(-1,'');
  WriteLogLine(-1,'Errors:   '+IntToStr(StatRec.errors ));
  WriteLogLine(-1,'Warnings: '+IntToStr(StatRec.warnings));

  PageControl1.ActivePage:=TabSheet3;
  if (StatRec.errors=0) and (FormPrefs.RGIntTarget.ItemIndex>0)
             then begin PanelInt.Visible:=true; PanelInt.SetFocus; end
             else PanelInt.Visible:=false;
  StatusBar1.Hint:='Build time: '+ DateTimeToStr(now);
  StatusBar1Resize(Sender);
  prj.WriteInteger('Build','Size',StatRec.size);

  if StatRec.warnings+StatRec.errors >0 then
     Application.MessageBox(pChar('Errors: '+IntToStr(StatRec.errors )+',  Warnings: '+IntToStr(StatRec.warnings)+'.'), 'Attention',MB_OK+MB_ICONHAND);

   if (StatRec.errors=0) and (Trim(EdCommand.Text)<>'') and (cbCommand.checked) then begin
      WriteLogLine(-1,'');
      WriteLogLine(-1,'Exectuting Post Processing command.');
      WriteLogLine(2,'-> '+EdCommand.Text+' <');
      cs := Trim(EdCommand.Text);
      cs:=stringReplace(cs , '%SXB%',  EdFileSXB.text  ,[rfReplaceAll, rfIgnoreCase]);
      cs:=stringReplace(cs , '%XBT%',  EdFileXBtxt.text,[rfReplaceAll, rfIgnoreCase]);
      cs:=stringReplace(cs , '%XB%',   EdFileXB.text   ,[rfReplaceAll, rfIgnoreCase]);
      cs:=stringReplace(cs , '%XBM%',  EdFileXBM.text  ,[rfReplaceAll, rfIgnoreCase]);
      cs:=stringReplace(cs , '%XBP%',  EdFilePrj.text  ,[rfReplaceAll, rfIgnoreCase]);
      cs:=stringReplace(cs , '%TIME%', TimeToStr(Time) ,[rfReplaceAll, rfIgnoreCase]);
      cs:=stringReplace(cs , '%DATE%', DateToStr(Date) ,[rfReplaceAll, rfIgnoreCase]);
      WriteLogLine(3,'-> '+cs+' <');
      {%H-}RunCommand(cs,outs);
      If outs<>'' THEN WriteLogLine(2,'-> '+outs);
      sleep(2000);
   end;

   if (StatRec.errors=0) and (FormPrefs.cbXBtoClipboard.Checked) then begin
     xb.SelectAll;
     xb.CopyToClipboard;
     xb.ClearSelection;
     WriteLogLine(3,'XB Source copied do Clipboard.');
   end;

   if (StatRec.errors=0) and (FormPrefs.RGIntTarget.ItemIndex>0) then
     if CBIntAutoLoad.Checked    then BbIntLoadClick(Sender) else
     if CBIntAutorun.Checked     then BbIntRunClick(Sender)  else
     if CBIntAutoCompile.Checked then BbIntCompileClick(Sender);
end;

procedure TForm1.MenuCartClick(Sender: TObject);
begin
  PageControl1.ActivePage:=TabSheet3;   // switch to Log
  FormCartPatch.showmodal;
end;

procedure TForm1.MenuOpenClick(Sender: TObject);
begin
  OpenDialog1.Title:='Open existing file';
  if Opendialog1.Execute then begin
    sxbfile := OpenDialog1.FileName;
    if UpperCase(ExtractFileExt(sxbfile)) = '.XBP' then
      begin
         prj := TiniFile.Create(sxbfile);
         sxbfile := prj.ReadString('Files','SXB',ChangeFileExt(sxbfile,'.SXB'));
         prj.Destroy;
      end;
    LoadFile(sxbfile);
    UpdateRecent(sxbfile);
    PageControl1.ActivePage:=TabSheet1;
  end;
end;

procedure TForm1.MenuSaveClick(Sender: TObject);
begin
  SaveFile;
end;

procedure TForm1.MenuTIdBitClick(Sender: TObject);
var xbout: String;
    LineStart,LineInc :Integer;
begin
  LineStart := StrtoInt(trim(MaskEdit1.Text));
  LineInc   := StrtoInt(trim(MaskEdit2.Text));
  log.Lines.Clear;
  MenuSaveClick(Sender);
  xbout := EdFileXBtxt.Text;
  WriteLogLine(2,'Save '+sxbfile+' and convert to '+xbout+'.');
  WriteLogLine(2,'Warning: All Debug Messages are Level 2 (Info)!');
  ExportXB(sxbfile,xbout,LineStart,LineInc,@WriteLogLine,'Any Options');
  xb.Lines.LoadFromFile(xbout);
  xb.ReadOnly:=true;
end;

procedure TForm1.MenuTokenizeClick(Sender: TObject);
VAR xbin, tkout,mrgout,vxr, opt : String;
begin
  xbin  := EdFileXBtxt.Text;
  WriteLogLine(2,'Save '+xbin+' and tokenize to ...');
  SaveFile;
  if cbtkn.checked then tkout := EdFileXB.Text
                   else tkout := '';
  if cbMrg.checked then mrgout :=EdFileXBM.Text
                   else mrgout := '';
  if tkout=''  then WriteLogLine(2,'   Token File ommitted.')
               else WriteLogLine(2,'   Token File: '+tkout+'.');
  if mrgout='' then WriteLogLine(2,'   Merge File ommitted.')
               else WriteLogLine(2,'   Merge File: '+mrgout+'.');

  opt := '-mem='+cbMemBase.text+' ';
  if cbTiBas.Checked then opt := opt + ' -TiBas';
  if cbFormat.ItemIndex=1 then opt := opt + ' -V9T9';;

  vxr := EdFileVXR.Text;
  vxrloaded := false;
  WriteLogLine(2,'');
  tokenize(xbin,tkout,mrgout,vxr,@WriteLogLine,opt);
end;

procedure TForm1.MenuUndoClick(Sender: TObject);
begin
  SynEdit1.Undo;
end;

procedure TForm1.MnBeautyClick(Sender: TObject);
var k : Array[1..150] of String[14];
    s,t,s2 : String;
    i,j,maxk,p1,p2 : Integer;
begin
  if MessageDlg('Confirmation','Do you want to change all Keywords and standard CALLs to uppercase?',
                 mtConfirmation, [mbYes, mbNo],0) = mrNo then exit;
  for i:=1 to 92 do begin
    k[i] := keywords[i+3].key;
  end;
  i:=92; j:=1;
  s := stdcalls + 'REPEAT UNTIL WHILE WEND ENDWHILE CASE OF ENDCASE IN BEGIN BIN$ CONST ASM ASMEND';
  while j<=length(s) do
    if s[j]=' ' then begin
      inc(i);
      k[i]:='';
      inc(j);
    end else begin;
      k[i]:=k[i]+s[j];
      inc(j);
    end;
  maxk:=i;
  for i:=1 to maxk do WriteLogLine(5,k[i]);

  i:=0;
  while i < SynEdit1.Lines.Count do begin
    s  := SynEdit1.Lines[i];
    s2 := s;
    WriteLogLine(5,'before : '+s);
    p1 := posUnquoted('//',s);
    if p1>0 then s:=copy(s,1,p1-1);
    p1 := posUnquoted('!',s);
    if p1>0 then s:=copy(s,1,p1-1);
    p1 := PosUnquoted('REM ',s);
    if p1>0 then s:=copy(s,1,p1-1);
    p1 := Pos('DATA ',UpperCase(s));
    if p1>0 then s:=copy(s,1,p1+3);

    p1:=1;p2:=1;
    while (p2<=length(s)) do begin
      while (p1<=length(s)) and not (isalpha(s[p1])) do
         if s[p1]='"' then begin
           inc(p1);
           while (p1<=length(s)) and (s[p1]<>'"') do inc(p1);
           inc(p1);
        end else inc(p1);
      p2:=p1+1;
      while (p2<=length(s)) and (isalpha(s[p2]) or (s[p2]='$')) do inc(p2);
      t := UpperCase(copy(s,p1,p2-p1));
      WriteLogLine(5,'       : '+t);
      // now check to replace the word found
      j:=1;
      while (j<=maxk) and (t<>k[j]) do inc(j);
      if t=k[j] then s2:=copy(s2,1,p1-1)+t+copy(s2,p2,255);
      p1:=p2+1;
    end;
    WriteLogLine(5,'after  : '+s2);
    SynEdit1.Lines[i] := s2;
    inc(i);
  end;
end;

procedure TForm1.MnCommentClick(Sender: TObject);
var i : Integer;
begin
   // Application.MessageBox(pChar('Line Start '+IntToStr(SynEdit1.BlockBegin.Y)+'- End '+IntToStr(SynEdit1.BlockEnd.Y)), 'Information',MB_ICONINFORMATION);
   for i:=SynEdit1.BlockBegin.Y-1 to SynEdit1.BlockEnd.Y-1 do with SynEdit1.Lines do begin
     if LeftStr(trim(Strings[i]),2) = '//'
        then strings[i] := stringreplace(strings[i],'//','',[rfIgnoreCase])  // uncomment
        else strings[i] := '//' + strings[i];    // comment
   end;
end;

procedure TForm1.MnDumpCharsetClick(Sender: TObject);
begin
  SaveDialog1.DefaultExt:='';
  SaveDialog1.Filter:='';
  SaveDialog1.Title:='Create binary charset file';
  if SaveDialog1.Execute then begin
    DumpFont(TabControl1.TabIndex,0,SaveDialog1.FileName);
    Application.MessageBox(pChar('Charset '+SaveDialog1.FileName+' written.'), 'Information',MB_ICONINFORMATION);
  end;
end;

procedure TForm1.MnDumpFontClick(Sender: TObject);
begin
  SaveDialog1.Filter:='';
  SaveDialog1.DefaultExt:='';
  SaveDialog1.Title:='Create binary font file';
  if SaveDialog1.Execute then begin
    DumpFont(TabControl1.TabIndex,32,SaveDialog1.FileName);
    Application.MessageBox(pChar('Font'+SaveDialog1.FileName+' written.'), 'Information',MB_ICONINFORMATION);
  end;
end;


procedure TForm1.MnHighlightClick(Sender: TObject);
begin
  if FormHighlight.Showmodal=mrOk
     then SynEdit1.SetHighlightSearch(FormHighlight.EdHighlight.Text,[])
     else SynEdit1.SetHighlightSearch('',[])
end;

procedure TForm1.MnHTMLClick(Sender: TObject);
VAR s:String;
    i:integer;
begin
  {$IFNDEF DARWIN}
  s := ChangeFileExt(sxbfile,'.html');
  WriteLogLine(2,'Exporting Source code as '+s);
  for i:=0 to SynEdit1.Lines.Count-1 do
    SynEdit1.Lines[i] := fmtInt(i+1,4)+'  '+SynEdit1.Lines[i];
  SynExporterHTML1.Title:='TiCodEd SXB: '+ s;
  SynExporterHTML1.Highlighter := SynXBSyn1;
  SynExporterHTML1.ExportAll(SynEdit1.Lines);
  SynExporterHTML1.SaveToFile(s);
  for i:=0 to SynEdit1.Lines.Count-1 do
    SynEdit1.Lines[i] := copy(SynEdit1.Lines[i],7,255);
  Application.MessageBox(pChar(s+' written.'), 'Information',MB_ICONINFORMATION);
  {$ENDIF}
end;

procedure TForm1.MnLoadCharsetClick(Sender: TObject);
begin
  OpenDialog1.DefaultExt:='*.*';
  OpenDialog1.Filter:='Any File|*.*';
  if OpenDialog1.Execute then begin
    LoadFont(TabControl1.TabIndex,0,OpenDialog1.FileName);
  end;
  TabControl1Change(NIL);
end;

procedure TForm1.MnLoadFontClick(Sender: TObject);
begin
  OpenDialog1.DefaultExt:='*.*';
  OpenDialog1.Filter:='Any File|*.*';
  if OpenDialog1.Execute then begin
    LoadFont(TabControl1.TabIndex,32,OpenDialog1.FileName);
  end;
  TabControl1Change(NIL);
end;

procedure TForm1.MnRecent1Click(Sender: TObject);
begin
  LoadFile(TMenuItem(Sender).Caption);
end;

procedure TForm1.MnRepIdentClick(Sender: TObject);
var res,i : integer;
    s : String;
begin
  res := FormRepIdent.ShowModal;
  // Application.MessageBox(pchar(inttostr(res)),'Result');
  if res=1 then for i:=0 to SynEdit1.Lines.Count-1 do begin
    s := SynEdit1.Lines.Strings[i];
    ReplaceIdent(FormRepIdent.cbRepFrom.Text,FormRepIdent.EdRepTo.Text,s);
    SynEdit1.Lines.Strings[i] := s;
  end;
end;

procedure TForm1.MnReplaceClick(Sender: TObject);
begin
   ReplaceDialog1.Execute;
end;

procedure TForm1.MnSaveAsClick(Sender: TObject);
var tmp : shortstring;
begin
  tmp := sxbfile;
  sxbfile := '';
  SaveFile;
  if sxbfile='' then sxbfile:=tmp else begin
    // generate new automations with new filenames
    MemoIntLoad.Clear;
    MemoIntLoadDblClick(NIL);
    MemoIntRun.Clear;
    MemoIntRunDblClick(NIL);
    MemoIntCompile.Clear;
    MemoIntCompileDblClick(NIL);
  end;
end;

procedure TForm1.MnSpecialClick(Sender: TObject);
var h: THandle;
begin
  h := GetWindowHandle(FormPrefs.RGIntTarget.ItemIndex, FormPrefs.EdIntStr.Text);
  SendString(h,'100 REM This is a Test');
end;

procedure TForm1.MnTiFilesEditClick(Sender: TObject);
begin
  FormTiFiles.ShowModal;
end;

procedure TForm1.MnFindClick(Sender: TObject);
begin
  FindPos:=1;
  FindDialog1.Execute;
end;

procedure TForm1.MnHexBinClick(Sender: TObject);
begin
  FormHex2Bin.ShowModal;
end;

procedure TForm1.PageControl1Change(Sender: TObject);
begin
  if PageControl1.TabIndex=0 then begin
    MnReplace.Enabled:=true;
    MenuUndo.Enabled:=true;
  end else begin
    MenuUndo.Enabled:=false;
    MnReplace.Enabled:=false;
  end;
end;

procedure TForm1.PanelIntResize(Sender: TObject);
var x : Integer;
begin
  x := (Width - 3 * 128) DIV 4;
  BbIntLoad.Left    := x;
  BbIntRun.Left     := 2 * x + 128;
  BbIntCompile.Left := 3 * x + 256;
end;

procedure TForm1.RadioGroup1Click(Sender: TObject);
begin
  if rgCharSize.ItemIndex=0 then begin
    btx:=7; bty:=7;
    cbRow.Enabled:=false;
    cbCol.Enabled:=false;
    GroupBox3.Visible:=true;
    gbAnimation.Visible:=false;
  end else
  if rgCharSize.ItemIndex=1 then begin
    btx:=15; bty:=15;
    cbRow.Enabled:=false;
    cbCol.Enabled:=false;
    GroupBox3.Visible:=true;
    gbAnimation.Visible:=false;
  end else
  if rgCharSize.ItemIndex>1 then begin
    cbCol.ItemIndex:=max(0,cset[cpage,cbase].cx-1);
    cbRow.ItemIndex:=max(0,cset[cpage,cbase].cy-1);
    btx:=(cbCol.ItemIndex+1)*8-1;
    bty:=(cbRow.ItemIndex+1)*8-1;
    cbRow.Enabled:=true;
    cbCol.Enabled:=true;
    GroupBox3.Visible:=false;
    if ((bty=7) and (btx>7)) or ((bty=15) and ((btx+1) MOD 16 = 0))
      then gbAnimation.Visible:=true
      else gbAnimation.Visible:=false;
  end;
  GroupChar;
  ResizeCharEd;
  CalcCharIndex;
  TimerAnimation.Enabled:=false;
  sbPlay.ImageIndex:=0;
end;

procedure TForm1.FindDialog1Find(Sender: TObject);
var i : Integer;
begin
  with FindDialog1 do case PageControl1.TabIndex of
    0: begin   // SXB
        if frMatchCase IN Options
           then i:=Pos(FindText,SynEdit1.Text , FindPos)
           else i:=Pos(AnsiUpperCase(FindText),AnsiUpperCase(SynEdit1.Text), FindPos);

        if i>0 then begin
          SynEdit1.HideSelection:=false;
          SynEdit1.SelStart:= i;
          SynEdit1.SelEnd := i+Length(FindDialog1.FindText);
          FindPos := i+1;
        end else FindPos := 1;
      end;
    1: begin  // XB
          if frMatchCase IN Options
             then i:=Pos(FindText,xb.Text, FindPos)
             else i:=Pos(AnsiUpperCase(FindText),AnsiUpperCase(xb.Text), FindPos);

          if i>0 then begin
            xb.HideSelection:=false;
            xb.SelStart  := i-1;
            xb.SelLength := Length(FindDialog1.FindText);
            FindPos := i+1;
          end else FindPos := 1;
       end;
    2: begin  // Assembler
          if frMatchCase IN Options
             then i:=Pos(FindText,MemoAsm.Text, FindPos)
             else i:=Pos(AnsiUpperCase(FindText),AnsiUpperCase(MemoAsm.Text), FindPos);

          if i>0 then begin
            MemoAsm.HideSelection:=false;
            MemoAsm.SelStart  := i-1;
            MemoAsm.SelLength := Length(FindDialog1.FindText);
            FindPos := i+1;
          end else FindPos := 1;
       end;
    3: begin  // Log
          if frMatchCase IN Options
             then i:=Pos(FindText,Log.Text, FindPos)
             else i:=Pos(AnsiUpperCase(FindText),AnsiUpperCase(Log.Text), FindPos);

          if i>0 then begin
            log.HideSelection:=false;
            log.SelStart  := i-1;
            log.SelLength := Length(FindDialog1.FindText);
            FindPos := i+1;
          end else FindPos := 1;
       end;
    5: begin    // VAR
         i:=Findpos;
         SGvar.Options := SGvar.Options + [goDrawFocusSelected];
         while (i<sgvar.RowCount*4) and (Pos(uppercase(FindText),Uppercase(SGvar.Cells[i MOD 4,i DIV 4]))=0) do inc(i);
         if i>=sgvar.RowCount*4 then FindPos:=1
         else begin
            SGvar.Col:=i MOD 4;
            SGvar.Row:=i DIV 4;
            FindPos := i+1;
         end;
       end;
    end;
end;

procedure TForm1.ReplaceDialog1Replace(Sender: TObject);
var sso: TSynSearchOptions;
    trf: TReplaceFlags;
    i : Integer;
begin

  if PageControl1.TabIndex=0 then with ReplaceDialog1 do begin
    sso := [];
    if frMatchCase   IN Options then include(sso,ssoMatchCase);
    if frReplace     IN Options then include(sso,ssoReplace);
    if frReplaceAll  IN Options then include(sso,ssoReplaceAll);
    if frWholeWord   IN Options then include(sso,ssoWholeWord);
    if frEntireScope IN Options then include(sso,ssoEntireScope);
    if frFindNext    IN Options then include(sso,ssoFindContinue);

    if (frReplaceAll  IN Options) and (trim(ReplaceDialog1.ReplaceText)='') then
      if MessageDlg('Confirmation','Do you really want to replace all >'+ReplaceDialog1.FindText+'< with >'+ReplaceDialog1.ReplaceText+'< ?'
                    ,mtConfirmation, [mbYes, mbNo],0) = mrNo then exit;

    SynEdit1.SearchReplace(ReplaceDialog1.FindText,ReplaceDialog1.ReplaceText,sso);
  end else if PageControl1.TabIndex=1 then with ReplaceDialog1 do begin
    trf := [];
    if not (frMatchCase IN Options) then include(trf,rfIgnoreCase);
    if  frReplaceAll    IN Options  then include(trf,rfReplaceAll);
    for i:=0 to xb.Lines.Count-1 do begin
      xb.Lines.Strings[i] := StringReplace(xb.Lines.Strings[i],ReplaceDialog1.FindText,ReplaceDialog1.ReplaceText,trf);
    end;
  end;
end;


procedure TForm1.ReplaceDialog1Show(Sender: TObject);
var fo :  TFindOptions;
begin
  case PageControl1.TabIndex of
    0 : fo := [frDisableUpDown,frHidePromptOnReplace];
    1 : fo := [frDisableUpDown,frDisableWholeWord,frHidePromptOnReplace];
  end;
  ReplaceDialog1.Options := fo;
end;

procedure TForm1.sbCopyClick(Sender: TObject);
begin
  Clipboard.AsText:=hexstr.Text;
end;

procedure TForm1.sbDownClick(Sender: TObject);
var x,y : integer;
begin
  for y := 63 downto 1 do
    for x := 0 to 127 do bt[x,y] := bt[x,y-1];
  for x:=0 to 127 do bt[x,0] := false;
  DrawCharEd;
end;

procedure TForm1.sbInvertClick(Sender: TObject);
begin
  ImageConvert(5);
  DrawCharEd;
end;

procedure TForm1.sbLeftClick(Sender: TObject);
var x : integer;
begin
  for x := 1 to 127 do bt[x-1] := bt[x];
  for x:=0 to 63 do bt[127,x] := false;
  DrawCharEd;
end;

procedure TForm1.sbMirrorHClick(Sender: TObject);
begin
  ImageConvert(2);
  DrawCharEd;
end;

procedure TForm1.sbMirrorVClick(Sender: TObject);
begin
  ImageConvert(1);
  DrawCharEd;
end;

procedure TForm1.sbPasteClick(Sender: TObject);
begin
  hexstr.Text:=Clipboard.AsText;
  hexmanmode:=true;
end;

procedure TForm1.sbPlayClick(Sender: TObject);
begin
  if TimerAnimation.enabled=false then begin
    TimerAnimation.enabled:=true;
    sbPlay.ImageIndex := 1;
  end else begin
    TimerAnimation.enabled:=false;
    sbPlay.ImageIndex := 0;
  end;
end;

procedure TForm1.sbRightClick(Sender: TObject);
var x : integer;
begin
  for x := 127 downto 1 do bt[x] := bt[x-1];
  for x:=0 to 63 do bt[0,x] := false;
  DrawCharEd;
end;

procedure TForm1.sbRotLeftClick(Sender: TObject);
begin
  ImageConvert(3);
  DrawCharEd;
end;

procedure TForm1.sbRotRightClick(Sender: TObject);
begin
  ImageConvert(4);
  DrawCharEd;
end;

procedure TForm1.sbUpClick(Sender: TObject);
var x,y : integer;
begin
  for y := 1 to 63 do
    for x := 0 to 127 do bt[x,y-1] := bt[x,y];
    for x:=0 to 127 do bt[x,63] := false;
  DrawCharEd;
end;

procedure TForm1.SGvarResize(Sender: TObject);
begin
  sgvar.ColWidths[0]:=40;
  sgvar.ColWidths[1]:=240;
  sgvar.ColWidths[2]:=130;
  sgvar.AutoSizeColumn(3);
end;

procedure TForm1.SGvarValidateEntry(sender: TObject; aCol, aRow: Integer;
  const OldValue: string; var NewValue: String);
VAR ok : Boolean;
    s : String;
    i : Integer;
begin
  s:=trim(uppercase(NewValue));
  ok:=true;
  for i:=1 to SGvar.RowCount-1 do begin
    if (i<>aRow) and (s<>'') and ((UpperCase(SGvar.cells[2,i])=s) or (UpperCase(SGvar.cells[1,i])=s)) then ok:=false
  end;
  if ok then if (((RightStr(s,1)= '$') and (RightStr(trim(SGvar.cells[1,aRow]),1)<>'$')) or
                 ((RightStr(s,1)<>'$') and (RightStr(trim(SGvar.cells[1,aRow]),1)= '$'))) and (s<>'')
             then begin
                     Application.MessageBox(pChar('String / Number missmatch.'), 'Error',MB_ICONSTOP);
                     NewValue := OldValue;
                  end
             else NewValue := S
        else begin
           Application.MessageBox(pChar('Variable '+s+' already in use.'), 'Error',MB_ICONSTOP);
           NewValue := OldValue
         end;
end;

procedure TForm1.StatusBar1Resize(Sender: TObject);
VAR le,wi,fi,pc,mem : Integer;
begin
  if prefs.ReadBool('Environment','Mem32k',true) then mem:=24488
                                                 else mem:=11840;
  le := Round(width * 0.8);
  wi := Round(width * 0.15);
  fi := Round(StatRec.size/mem * wi);
  pc := Round(StatRec.size/mem * 100);
  if fi<3  then fi:=3 else if fi>wi then fi:=wi;

  Memstat1.Width   := wi;
  Memstat1.left    := le;

  Memstat2.Width   := fi;
  Memstat2.left    := le;

  if pc < 85 then      Memstat2.brush.Color  := clGreen
  else if pc < 95 then Memstat2.brush.Color  := clYellow
                  else Memstat2.brush.Color  := clRed;
  Memstat1.Hint := IntToStr(pc)+'% used ('+IntToStr(StatRec.size)+'/'+IntToStr(mem)+' bytes)';
  Memstat2.Hint := Memstat1.Hint;

  StatusBar1.Panels.Items[2].Text:='Errors: '+IntToStr(StatRec.errors);
  StatusBar1.Panels.Items[3].Text:='Warnings: '+IntToStr(StatRec.warnings);

end;

procedure TForm1.SynCompletion1CodeCompletion(var Value: string;
  SourceValue: string; var SourceStart, SourceEnd: TPoint; KeyChar: TUTF8Char;
  Shift: TShiftState);
begin
  if (pos('- call',value)>0) and
     (pos('CALL',uppercase(copy(form1.SynEdit1.Lines.Strings[SourceStart.y-1],max(1,SourceStart.x-5),4)))=0)
     then value := 'CALL '+Value;
  if pos('-',value)>0 then value := trim(copy(value,1,pos('-',value)-1));
  if SourceValue=AnsiLowerCase(SourceValue) then value:=AnsiLowerCase(value)
end;

procedure TForm1.SynEdit1Change(Sender: TObject);
begin
  UpdateStatusEdit;
end;

procedure TForm1.SynEdit1DblClick(Sender: TObject);
var i : integer;
    s : string;
begin
  s  := AnsiUpperCase(SynEdit1.SelText);
  i:=Pos(s+':',AnsiUpperCase(SynEdit1.Text));
  if i>0 then begin
    NavStack.Add(inttostr(SynEdit1.LogicalCaretXY.Y));
    SynEdit1.SelStart:= i;
    SynEdit1.SelEnd  := i+Length(s)+1;
    SynEdit1.TopLine := SynEdit1.TopLine+16;
  end else begin
    i:=Pos('SUB '+s,AnsiUpperCase(SynEdit1.Text));
    if i>0 then begin
      NavStack.Add(inttostr(SynEdit1.LogicalCaretXY.Y));
      SynEdit1.SelStart:= i;
      SynEdit1.SelEnd  := i+Length(s)+4;
      SynEdit1.TopLine := SynEdit1.TopLine+16;
    end else if (s='RETURN') or (s='SUBEND') or (s='SUBEXIT') then begin
       SynEdit1.topline := max(1,strtointdef(NavStack.Pop,1)-5);
    end;
  end;
end;

procedure TForm1.SynEdit1KeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
    UpdateStatusEdit;
end;

procedure TForm1.SynEdit1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  UpdateStatusEdit;
end;

procedure TForm1.TabControl1Change(Sender: TObject);
var i,c1,c2 : Integer;
    s : String;
begin
  // Stop updates
  TimerHex.Enabled:=false;
  hexmanmode := false;

  // setup string grid for new tab
  cPage:=TabControl1.TabIndex;
  if cPage = 0 then begin
    c1 := 32; c2 := 159;
  end else begin
    c1 := 0;  c2 := 255;
  end;
  ctab.BeginUpdate;
  ctab.Clear;
  ctab.RowCount:=1;
  for i:=c1 to c2 do begin
    s := intToStr(i);
    if (i>32) and (i<128) then s :=s + ': '+char(i);
    ctab.InsertRowWithValues(i+1-c1,[s,' ',cset[cpage,i].pat]);
  end;
  {$IFDEF WINDOWS}
  ctab.ColWidths[0]:=60;
  ctab.ColWidths[1]:=32;
  ctab.ColWidths[2]:=180;
  TabControl1.Tabs[0] := 'Extended Basic';
  {$ELSE not Windows}
  ctab.ColWidths[0]   := 50;
  ctab.ColWidths[1]   := 32;
  ctab.ColWidths[2]   := 140;
  ctab.DefaultRowHeight:= 32;
  TabControl1.Height  := 32;
  Panel1.Width        := 240;
  {$ENDIF}

  ctab.EndUpdate(true); // redraw the updated grid
  // setup drawing area with new values
  CalcCharIndex;
  // restart updates
  TimerHex.Enabled:=true;
  // remove focus from hexstr in order to avoid copying of strings
  if PageControl1.ActivePageIndex=3 then ctab.SetFocus;
end;

procedure TForm1.TabSheet4Show(Sender: TObject);
begin
  EdFileXBChange(NIL);
  EdFileXBMChange(NIL);
end;

procedure TForm1.TabSheet5Hide(Sender: TObject);
begin
  TimerHex.Enabled:=false;
end;

procedure TForm1.TabSheet5Resize(Sender: TObject);
begin
  ResizeCharEd;
  DrawCharEd;
end;

procedure TForm1.TabSheet5Show(Sender: TObject);
begin
  TimerHex.Enabled:=true;
end;

procedure TForm1.TabSheet6Hide(Sender: TObject);
VAR s : String;
    f : Textfile;
    i : Integer;
begin
  s:=trim(EdFileVXR.text);
  if (s<>'') and FileExists(s) then begin
    system.assign(f,s);
    rewrite(f); i:=1;
    for i:=1 to SGvar.RowCount-1 do with SGvar do writeln(f,PadRight(cells[1,i],32) + PadRight(trim(cells[2,i]),9) + cells[3,i]);
    system.close(f);
  end;
end;

procedure TForm1.TabSheet6Show(Sender: TObject);
VAR s,t:String;
    f : Textfile;
    i,j : Integer;
begin
  if vxrloaded then exit;
  s:=trim(EdFileVXR.text);
  if (s<>'') and FileExists(s) then begin
    system.assign(f,s);
    reset(f); i:=1;
    SGvar.RowCount:=1;
    while not eof(f) do begin
      readln(f,t);
      SGvar.InsertRowWithValues(i,[IntToStr(i),trim(copy(t,1,32)),trim(copy(t,33,8)),trim(copy(t,40,255))]);
      inc(i);
    end;
    system.close(f);
    vxrloaded := true;
  end else if s<>'' then Application.MessageBox(pChar('File '+s+' not found.'), 'Warning',MB_ICONWARNING );
  // check for errors
  for i:=1 to SGvar.RowCount-1 do
    for j:=1 to SGvar.RowCount-1 do
      if (trim(SGvar.cells[1,i]) = trim(SGvar.cells[2,j])) and (i<>j) then
        Application.MessageBox(pChar(trim(SGvar.cells[2,j])+' used as variable and short-name.'), 'Error',MB_ICONSTOP);
  UpdateAutoComplete;
end;

procedure TForm1.tbGridClick(Sender: TObject);
begin
  DrawCharEd;
end;

procedure TForm1.TimerAnimationTimer(Sender: TObject);
var dx : Integer;
    src,dst: TRect;
begin
    dx := TimerAnimation.tag;
    dst:=TRect.Create(0,0,form1.ImageAni.Width,form1.ImageAni.Height);
    src:=TRect.Create(2*(bty+1)*dx+1,1,2*(bty+1)*(dx+1)+1,2*(bty+1)+1);
    ImageAni.canvas.CopyRect(dst,ImPreview.Picture.Bitmap.Canvas,src);

    inc(dx);
    if dx*(bty+1)>=(btx+1) then TimerAnimation.tag:=0
                           else TimerAnimation.tag:=TimerAnimation.tag+1;
end;

procedure TForm1.TimerHexTimer(Sender: TObject);
var s,t:String;
    c,i,j,sel1,sel2:integer;
begin
  if hexmanmode then begin
    // clean entry first
    s := upperCase(hexstr.text);
    sel1 := hexstr.SelStart;
    sel2 := hexstr.SelLength;
    t := '';
    //remove CALL CHAR
    i:=PosUnquoted('CALL CHAR',s);
    if i>0 then begin
      while (i<length(s)) and (s[i]<>',') do inc(i);
      s := copy(s,i,256)
    end;
    s := ReplaceStr(s,'DATA','');
    for i:=1 to length(s) do
       if isxdigit(s[i]) and (s[i]<>' ') then t:=t+s[i];
     // Fill to exact 16/64 chars
    if rgCharSize.ItemIndex=0 then i:=16 else i:=64;
    t := copy(t +  DupeString('0',64),1,i);
    hexstr.text:=t;
    hexstr.SelStart  := sel1;
    hexstr.SelLength := sel2;

    // now adjust array and graphic
    if rgCharSize.ItemIndex=0 then c:=7 else c:=15;
    for i:=0 to c do SetHex(0,i,copy(t,i*2+1,2));
    if c=15 then for i:=0 to c do SetHex(8,i,copy(t,i*2+33,2));
    DrawCharEd;
  end else
  if GroupBox3.Visible then begin
     s:='';
     if rgCharSize.ItemIndex=0 then c:=7 else c:=15;
     for i:=0 to c do s:=s+GetHex(0,i);
     if c=15 then for i:=0 to c do s:=s+GetHex(8,i);
     hexstr.Text:=s;
  end;
  // update string grid
  s:='';
  case form1.rgCharSize.ItemIndex of
   0 : begin
         SetGridHex(0,0);
       end;
   1 : begin
         SetGridHex(0,0);
         SetGridHex(1,0);
         SetGridHex(0,1);
         SetGridHex(1,1);
       end;
   2,3 : begin
         for i:=0 to cbCol.ItemIndex do
           for j:=0 to cbRow.ItemIndex do
             SetGridHex(i,j);
       end;
  end;

end;

procedure TForm1.TrackBarAniChange(Sender: TObject);
begin
  TimerAnimation.Interval:=TrackBarAni.Position;
  lbAniSpeed.Caption:=IntToStr(TrackBarAni.Position)+' ms';
end;


procedure TForm1.TrackBarVerboseChange(Sender: TObject);
begin
  case TrackBarVerbose.Position of
    0 : TrackBarVerbose.Hint:='Error';
    1 : TrackBarVerbose.Hint:='Warning';
    2 : TrackBarVerbose.Hint:='Information';
    3 : TrackBarVerbose.Hint:='Verbose';
    4 : TrackBarVerbose.Hint:='Very Verbose';
    5 : TrackBarVerbose.Hint:='Unbelievable Verbose';
  end;
end;


procedure TForm1.xbDblClick(Sender: TObject);
begin
  xb.ReadOnly:=false;
end;



end.

