
{ Copyright (c) 1989,90 by Borland International, Inc. }

unit TCRun;
{ Turbo Pascal 6.0 object-oriented example run module.
  This unit is used by TCALC.PAS.
  See TCALC.DOC for an more information about this example.
}

{$N+,S-}

interface

uses Crt, Dos, TCUtil, TCLStr, TCScreen, TCHash, TCCell, TCCellSp, TCSheet,
     TCInput, TCParser, TCMenu;

const
  FreeListItems = 1000;
  MenuHeapSpace = 1000;
  MaxSpreadsheets = (MinScreenRows - EmptyRowsAtTop - EmptyRowsAtBottom) div
                    4;
  LegalJustification = ['L', 'C', 'R'];
  HelpLine = 'F2\Save\F3\Load\F7\Formula\F8\AutoCalc\F9\Recalc\F10\Menu\Ins\Block\Alt-X\Exit';
  TitleString = 'TurboCalc - Turbo Pascal Demo Program';
  MainMenuString = 'Spreadsheet, Block, Column, Row, Format, Goto, Edit, Options, Quit';
  SpreadsheetMenuString = 'Load, Save, Zap, Write, Open, Close, Next, Print';
  OpenMenuString = 'Load, New';
  BlockMenuString = 'Copy, Delete, Format, Restore default format';
  ColumnMenuString = 'Insert, Delete, Width';
  RowMenuString = 'Insert, Delete';
  UtilityMenuString1 = 'Screen lines, Recalc, Formula display, Autocalc';
  UtilityMenuString2 = 'Recalc, Formula display, Autocalc';
  PromptFileLoad = 'File to load';
  PromptGotoCell = 'Go to cell';
  PromptCopyCell = 'Copy to cell';
  PromptColLiteral = 'Copy formula columns literally';
  PromptRowLiteral = 'Copy formula rows literally';
  PromptCopySpreadsheet = 'Copy to spreadsheet number (0 = current)';
  PromptFormatPlaces = 'Number of decimal places';
  PromptFormatJustification = 'Justification - (L)eft, (C)enter, (R)ight';
  PromptFormatDollar = 'Dollar format';
  PromptFormatCommas = 'Put commas in numbers';
  ErrFreeList = 'The free list is full';
  MsgBlockCopy = 'Copying block';

type
  ProgramObject = object
    SSData, CurrSS : SpreadsheetPtr;
    TotalSheets : Byte;
    CellInput : InputField;
    MainMenu : Menu;
    SpreadsheetMenu : Menu;
    OpenMenu : Menu;
    BlockMenu : Menu;
    ColumnMenu : Menu;
    RowMenu : Menu;
    UtilityMenu : Menu;
    Stop : Boolean;
    constructor Init;
    destructor Done;
    procedure GetCommands;
    procedure SetDisplayAreas;
    procedure DisplayAll;
    function AddSheet(Name : PathStr) : Boolean;
    procedure DeleteSheet;
  end;

var
  Vars : ProgramObject;

procedure Run;

implementation

const
  RedrawYes = True;
  RedrawNo = False;

{$F+}

function RunHeapError(Size : Word) : Integer;
{ Prints an error if the heap runs out of memory }
begin
  if size > 0 then
    Scr.PrintError(ErrNoMemory);
  RunHeapError := 1;
end; { RunHeapError }

{$F-}

procedure InitMenus; forward;

constructor ProgramObject.Init;
{ Sets up the program }
var
  Counter : Word;
  Good : Boolean;
begin { ProgramObject.Init }
  if MaxAvail < MenuHeapSpace then
    Abort(ErrNoMemory);
  InitMenus;
  RegisterCellTypes;             { Register cell types for stream access }
  TotalSheets := 0;
  SSData := nil;
  CurrSS := nil;
  Stop := False;
  if ParamCount = 0 then         { Load spreadsheets named on command line }
  begin
    if not AddSheet('') then
      Abort(ErrNoMemory);
  end
  else begin
    Counter := 1;
    repeat
      Good := AddSheet(ParamStr(Counter));
      Inc(Counter);
    until (not Good) or (Counter > Min(ParamCount, MaxSpreadsheets));
  end;
  SetDisplayAreas;
  DisplayAll;
  with CurrSS^ do
  begin
    MakeCurrent;
    DisplayCell(CurrPos);
  end; { with }
end; { ProgramObject.Init }

destructor ProgramObject.Done;
{ Releases all memory used by the program }
begin
  CurrSS^.MakeNotCurrent;
  while SSData <> nil do
  begin
    CurrSS := SSData;
    SSData := SSData^.Next;
    with CurrSS^ do
    begin
      MakeCurrent;
      DisplayCell(CurrPos);
      CheckForSave;
      MakeNotCurrent;
      DisplayCell(CurrPos);
      Dispose(CurrSS, Done);
    end; { with }
  end;
  MainMenu.Done;
  SpreadsheetMenu.Done;
  OpenMenu.Done;
  BlockMenu.Done;
  ColumnMenu.Done;
  RowMenu.Done;
  UtilityMenu.Done;
end; { ProgramObject.Done }

function GetFormat(var Format : Byte) : Boolean;
{ Reads a format value from the keyboard }
var
  Places : Byte;
  J : Justification;
  ESCPressed, Good, Dollar, Commas : Boolean;
  Ch : Char;
begin
  GetFormat := False;
  Dollar := GetYesNo(PromptFormatDollar, ESCPressed);
  if ESCPressed then
    Exit;
  if Dollar then
  begin
    Places := 2;
    J := JRight;
  end
  else begin
    Places := GetNumber(PromptFormatPlaces, 0,
                        Vars.CurrSS^.MaxDecimalPlaces, Good);
    if not Good then
      Exit;
    Ch := GetLegalChar(PromptFormatJustification, LegalJustification,
                       ESCPressed);
    if ESCPressed then
      Exit;
    case Ch of
      'L' : J := JLeft;
      'C' : J := JCenter;
      'R' : J := JRight;
    end; { case }
  end;
  Commas := GetYesNo(PromptFormatCommas, ESCPressed);
  if ESCPressed then
    Exit;
  Format := Places + (Ord(J) shl 4) + (Ord(Dollar) shl 6) +
            (Ord(Commas) shl 7);
  GetFormat := True;
end; { GetFormat }

procedure EditInput(Ch : Word; Editing : Boolean);
{ Edits the data on the input line }
var
  Good, FirstEdit, Deleted : Boolean;
  P : CellPos;
begin
  with Vars, CurrSS^ do
  begin
    if not CellInput.Init(1, 0, -1, 0, NotUpper) then
      Exit;
    with CellInput.InputData^ do
    begin
      if Editing then
      begin
        Good := True;
        CellHash.Search(CurrPos)^.EditString(MaxDecimalPlaces,
                        CellInput.InputData)
      end
      else
        Good := FromString(Chr(Ch));
      if not Good then
      begin
        CellInput.Done;
        Exit;
      end;
      FirstEdit := True;
      Parser.Init(@CellHash, CellInput.InputData, MaxCols, MaxRows);
      repeat
        if FirstEdit then
          CellInput.Edit(0)
        else
          CellInput.Edit(Parser.Position);
        if Length > 0 then
        begin
          Parser.Parse;
          if Parser.TokenError = 0 then
          begin
            DeleteCell(CurrPos, Deleted);
            Good := AddCell(Parser.CType, CurrPos, Parser.ParseError,
                            Parser.ParseValue, CellInput.InputData);
          end;
        end;
        FirstEdit := False;
      until (Length = 0) or (Parser.TokenError = 0);
      if Length > 0 then
      begin
        SetChanged(WasChanged);
        if AutoCalc then
          Update(DisplayYes);
        P := CurrPos;
        for P.Col := CurrPos.Col to ScreenBlock.Stop.Col do
          DisplayCell(P);
      end;
      CellInput.InputArea.Clear;
    end; { with }
    CellInput.Done;
    DisplayMemory;
  end; { with }
end; { EditInput }

procedure OpenSpreadsheet(Name : String);
{ Opens a new spreadsheet }
begin
  with Vars do
  begin
    if not AddSheet(Name) then
      Exit;
    SetDisplayAreas;
    DisplayAll;
    with CurrSS^ do
    begin
      MakeCurrent;
      DisplayCell(CurrPos);
    end; { with }
  end; { with }
end; { OpenSpreadsheet }

procedure ClearCurrBlock;
{ Turns off the block and redisplays the cells in it }
begin
  with Vars.CurrSS^ do
  begin
    if BlockOn then
    begin
      BlockOn := False;
      DisplayBlock(CurrBlock);
    end;
  end;
end; { ClearCurrBlock }

{$F+}

procedure ReplaceSpreadsheet;
{ Load a spreadsheet over the current one }
var
  S : PathStr;
  ESCPressed : Boolean;
begin
  with Vars.CurrSS^ do
  begin
    S := ReadString(PromptFileLoad, Pred(SizeOf(PathStr)), ESCPressed);
    if S = '' then
      Exit;
    CheckForSave;
    Done;
    if FromFile(S) then
    begin
      SetChanged(NotChanged);
      SetScreenColStart(1);
      SetScreenRowStart(1);
      Display;
      MakeCurrent;
      DisplayCell(CurrPos);
    end;
  end; { with }
end; { ReplaceSpreadsheet }

procedure NameSaveSpreadsheet;
{ Save a spreadsheet to a file other that its default }
var
  St : PathStr;
  ESCPressed : Boolean;
begin
  with Vars.CurrSS^ do
  begin
    St := ReadString(PromptFileSave, Pred(SizeOf(PathStr)), ESCPressed);
    if St = '' then
      Exit;
    if FileExists(St) then
    begin
      if not GetYesNo(PromptOverwriteFile, ESCPressed) then
        Exit;
    end;
    ToFile(St);
    DisplayFileName;
  end; { with }
end; { NameSaveSpreadsheet }

procedure SaveCurrSpreadsheet;
{ Save a spreadsheet to its default file }
begin
  with Vars.CurrSS^ do
  begin
    if FileName = '' then
      NameSaveSpreadsheet
    else
      ToFile(FileName);
  end; { with }
end; { SaveCurrSpreadsheet }

procedure ZapSpreadsheet;
{ Clear the current spreadsheet from memory }
var
  S : PathStr;
begin
  with Vars.CurrSS^ do
  begin
    CheckForSave;
    S := FileName;
    Done;
    Init(0, DefaultMaxCols, DefaultMaxRows, DefaultMaxDecimalPlaces,
         DefaultDefaultDecimalPlaces, DefaultDefaultColWidth);
    MakeCurrent;
    FileName := S;
    SetScreenColStart(1);
    SetScreenRowStart(1);
    Display;
  end; { with }
end; { ZapSpreadsheet }

procedure CloseSpreadsheet;
{ Delete a spreadsheet, closing the window that it is in }
begin
  with Vars, CurrSS^ do
  begin
    if TotalSheets = 1 then
      Exit;
    DeleteSheet;
  end; { with }
end; { CloseSpreadsheet }

procedure NextSpreadsheet;
{ Move to the next spreadsheet }
begin
  with Vars do
  begin
    if TotalSheets = 1 then
      Exit;
    with CurrSS^ do
    begin
      MakeNotCurrent;
      DisplayCell(CurrPos);
    end; { with }
    CurrSS := CurrSS^.Next;
    if CurrSS = nil then
      CurrSS := SSData;
    with CurrSS^ do
    begin
      MakeCurrent;
      DisplayCell(CurrPos);
    end; { with }
  end; { with }
end; { NextSpreadsheet }

procedure NewSpreadsheet;
{ Create a new spreadsheet, opening a window for it and loading it }
var
  S : PathStr;
  ESCPressed : Boolean;
begin
  with Vars do
  begin
    if TotalSheets >= MaxSpreadsheets then
      Exit;
    S := ReadString(PromptFileLoad, Pred(SizeOf(PathStr)), ESCPressed);
    if S = '' then
      Exit;
    OpenSpreadsheet(S);
  end; { with }
end; { NewSpreadsheet }

procedure NewBlankSpreadsheet;
{ Create a new blank spreadsheet, opening a window for it }
begin
  with Vars do
  begin
    if TotalSheets >= MaxSpreadsheets then
      Exit;
    OpenSpreadsheet('');
  end; { with }
end; { NewBlankSpreadsheet }

procedure PrintSpreadsheet;
{ Print a spreadsheet to a file or a printer }
begin
  Vars.CurrSS^.Print;
end; { PrintSpreadsheet }

procedure CopyBlock;
{ Copy a block of cells from one spreadsheet to the same or a different
  spreadsheet }
var
  P, N, C : CellPos;
  Good, ESCPressed, ColLit, RowLit, AnyChanged, Deleted : Boolean;
  CP : CellPtr;
  L : LStringPtr;
  CopyTo : SpreadsheetPtr;
  CopySheet : Byte;
  Counter : Word;
begin
  with Vars, CurrSS^, CurrBlock do
  begin
    if not BlockOn then
      Exit;
    if TotalSheets > 1 then
      CopySheet := GetNumber(PromptCopySpreadsheet, 0, TotalSheets, Good)
    else
      CopySheet := 1;
    if not Good then
      Exit;
    if not GetCellPos(PromptCopyCell, MaxCols, MaxRows, ColSpace,
                      RowNumberSpace, P) then
      Exit;
    ColLit := GetYesNo(PromptColLiteral, ESCPressed);
    if ESCPressed then
      Exit;
    RowLit := GetYesNo(PromptRowLiteral, ESCPressed);
    if ESCPressed then
      Exit;
    Scr.PrintMessage(MsgBlockCopy);
    if CopySheet = 0 then
      CopyTo := CurrSS
    else begin
      CopyTo := SSData;
      for Counter := 2 to CopySheet do
        CopyTo := CopyTo^.Next;
    end;
    AnyChanged := False;
    C.Row := P.Row;
    N.Row := Start.Row;
    L := New(LStringPtr, Init);
    Good := L <> nil;
    while Good and (N.Row <= Stop.Row) do
    begin
      C.Col := P.Col;
      N.Col := Start.Col;
      while Good and (N.Col <= Stop.Col) do
      begin
        if (Longint(P.Col) + N.Col - Start.Col <= MaxCols) and
           (Longint(P.Row) + N.Row - Start.Row <= MaxRows) then
        begin
          CopyTo^.DeleteCell(C, Deleted);
          if Deleted then
            AnyChanged := True;
          CP := CellHash.Search(N);
          if CP <> Empty then
          begin
            AnyChanged := True;
            with CP^ do
              Good := CopyTo^.AddCell(CellType, C, HasError, CurrValue,
                                      CopyString(ColLit, RowLit,
                                      Longint(C.Col) - N.Col, L));
            if Good and ((not ColLit) or (not RowLit)) then
            begin
              CP := CopyTo^.CellHash.Search(C);
              if CP^.ShouldUpdate then
              begin
                if not ColLit then
                  FixFormulaCol(CP, Longint(C.Col) - N.Col, MaxCols,
                                MaxRows);
                if not RowLit then
                  FixFormulaRow(CP, Longint(C.Row) - N.Row, MaxCols,
                                MaxRows);
              end;
            end;
          end;
        end;
        Inc(C.Col);
        Inc(N.Col);
      end;
      Inc(C.Row);
      Inc(N.Row);
    end;
    if AnyChanged then
    begin
      if CopySheet = 0 then
        BlockOn := False;
      with CopyTo^ do
      begin
        SetLastPos(LastPos);
        SetChanged(WasChanged);
        if AutoCalc then
          Update(DisplayNo);
        DisplayAllCells;
        DisplayMemory;
      end; { with }
      if CopySheet <> 0 then
        ClearCurrBlock;
    end
    else
      ClearCurrBlock;
    Scr.ClearMessage;
  end; { with }
  if L <> nil then
    Dispose(L, Done);
end; { CopyBlock }

procedure DeleteBlock;
{ Delete a block of cells }
var
  Deleted : Boolean;
begin
  with Vars.CurrSS^, CurrBlock do
  begin
    if not BlockOn then
      Exit;
    DeleteBlock(CurrBlock, Deleted);
    if Deleted then
    begin
      BlockOn := False;
      SetLastPos(LastPos);
      SetChanged(WasChanged);
      if AutoCalc then
        Update(DisplayNo);
      DisplayMemory;
      DisplayAllCells;
    end
    else
      ClearCurrBlock;
  end; { with }
end; { DeleteBlock }

procedure FormatBlock;
{ Format a block of cells }
var
  Format : Byte;
begin
  with Vars.CurrSS^ do
  begin
    if not BlockOn then
      Exit;
    if not GetFormat(Format) then
      Exit;
    with CurrBlock do
    begin
      if not FormatHash.Add(Start, Stop, Format) then
        Exit;
      SetChanged(WasChanged);
      DisplayAllCells;
      DisplayMemory;
    end; { with }
  end; { with }
end; { FormatBlock }

procedure FormatDefault;
{ Change the format of a block of cells to the default }
begin
  with Vars.CurrSS^ do
  begin
    if not BlockOn then
      Exit;
    with CurrBlock do
    begin
      if not FormatHash.Delete(Start, Stop) then
        Exit;
      SetChanged(WasChanged);
      DisplayAllCells;
      DisplayMemory;
    end; { with }
  end; { with }
end; { FormatDefault }

procedure ColInsert;
{ Insert a column into the spreadsheet }
begin
  Vars.CurrSS^.InsertColumn;
end; { ColInsert }

procedure ColDelete;
{ Delete a column from the spreadsheet }
begin
  Vars.CurrSS^.DeleteColumn;
end; { ColDelete }

procedure ChangeColWidth;
{ Change the width of a column }
begin
  Vars.CurrSS^.ChangeWidth;
end; { ChangeColWidth }

procedure RowInsert;
{ Insert a row into the spreadsheet }
begin
  Vars.CurrSS^.InsertRow;
end; { RowInsert }

procedure RowDelete;
{ Delete a row from the spreadsheet }
begin
  Vars.CurrSS^.DeleteRow;
end; { RowDelete }

procedure ToggleMaxLines;
{ Toggle 43/50-line mode }
begin
  with Vars do
  begin
    Scr.ToggleMaxLinesMode;
    SetCursor(NoCursor);
    SetDisplayAreas;
    DisplayAll;
  end; { with }
end; { ToggleMaxLines }

procedure Recalc;
{ Recalculate all of the cells }
begin
  Vars.CurrSS^.Update(DisplayYes);
end; { Recalc }

procedure ToggleFormulas;
{ Toggle formula display on and off }
begin
  with Vars.CurrSS^ do
    ToggleFormulaDisplay;
end; { ToggleFormulas }

procedure ToggleAutoCalc;
{ Toggle AutoCalc on and off }
begin
  with Vars.CurrSS^ do
  begin
    if AutoCalc then
    begin
      AutoCalc := False;
      DisplayInfo;
    end
    else begin
      AutoCalc := True;
      DisplayInfo;
      Update(DisplayYes);
    end;
  end;
end; { ToggleAutoCalc }

procedure FormatCell;
{ Format a single cell }
var
  Format : Byte;
  P : CellPos;
  CP : CellPtr;
  Good : Boolean;
begin
  with Vars.CurrSS^ do
  begin
    if not GetFormat(Format) then
      Exit;
    if not FormatHash.Add(CurrPos, CurrPos, Format) then
      Exit;
    CP := CellHash.Search(CurrPos);
    SetChanged(WasChanged);
    OverwriteHash.Delete(CurrPos);
    if CP <> Empty then
      Good := OverwriteHash.Add(CP, CP^.Overwritten(CellHash, FormatHash,
                                WidthHash, LastPos, MaxCols, GetColWidth,
                                DisplayFormulas));
    P := CurrPos;
    for P.Col := CurrPos.Col to ScreenBlock.Stop.Col do
      DisplayCell(P);
    DisplayMemory;
  end; { with }
end; { FormatCell }

procedure GotoCell;
{ Go to a selected cell }
var
  P, OldPos : CellPos;
begin
  with Vars.CurrSS^ do
  begin
    if not GetCellPos(PromptGotoCell, MaxCols, MaxRows, ColSpace,
                      RowNumberSpace, P) then
      Exit;
    if not ScreenBlock.CellInBlock(P) then
    begin
      CurrPos := P;
      SetScreenColStart(CurrPos.Col);
      SetScreenRowStart(CurrPos.Row);
      Display;
    end
    else begin
      OldPos := CurrPos;
      CurrPos := P;
      DisplayCell(OldPos);
      DisplayCell(CurrPos);
    end;
  end; { with }
end; { GotoCell }

procedure EditCell;
{ Edit the current cell }
begin
  EditInput(0, EditYes);
end; { EditCell }

procedure Quit;
{ Exit from the program }
begin
  Vars.Stop := True;
end; { Quit }

{$F-}

procedure ExtendCurrBlock(Redraw : Boolean);
{ Extend the current block and redraw any cells that are affected }
var
  OldBlock : Block;
begin
  with Vars.CurrSS^ do
  begin
    if BlockOn then
    begin
      Move(CurrBlock, OldBlock, SizeOf(CurrBlock));
      if CurrBlock.ExtendTo(CurrPos) then
      begin
        if Redraw then
          DisplayBlockDiff(OldBlock, CurrBlock);
      end
      else
        ClearCurrBlock;
    end;
  end; { with }
end; { ExtendCurrBlock }

procedure ToggleCurrBlock;
{ Turn the block on and off }
begin
  with Vars.CurrSS^ do
  begin
    if not BlockOn then
    begin
      BlockOn := True;
      CurrBlock.Init(CurrPos);
    end
    else
      ClearCurrBlock;
  end; { with }
end; { ToggleCurrBlock }

procedure RemoveCell;
{ Delete a cell }
var
  P : CellPos;
  Deleted : Boolean;
begin
  with Vars.CurrSS^ do
  begin
    DeleteCell(CurrPos, Deleted);
    if Deleted then
    begin
      SetLastPos(CurrPos);
      SetChanged(WasChanged);
      if AutoCalc then
        Update(DisplayYes);
      P.Row := CurrPos.Row;
      for P.Col := CurrPos.Col to ScreenBlock.Stop.Col do
        DisplayCell(P);
      DisplayMemory;
    end;
  end; { with }
end; { RemoveCell }

procedure MoveHome;
{ Move to the home position (1, 1) }
var
  OldPos : CellPos;
begin
  with Vars.CurrSS^ do
  begin
    OldPos := CurrPos;
    CurrPos.Col := 1;
    CurrPos.Row := 1;
    if not ScreenBlock.CellInBlock(CurrPos) then
    begin
      ExtendCurrBlock(RedrawNo);
      SetScreenColStart(1);
      SetScreenRowStart(1);
      SetBlankArea;
      Display;
    end
    else begin
      ExtendCurrBlock(RedrawYes);
      DisplayCell(OldPos);
      DisplayCell(CurrPos);
    end;
  end; { with }
end; { MoveHome }

procedure MoveEnd;
{ Move to the last position used }
var
  OldPos : CellPos;
begin
  with Vars.CurrSS^ do
  begin
    OldPos := CurrPos;
    CurrPos := LastPos;
    if not ScreenBlock.CellInBlock(CurrPos) then
    begin
      ExtendCurrBlock(RedrawNo);
      SetScreenColStop(CurrPos.Col);
      SetScreenRowStop(CurrPos.Row);
      SetBlankArea;
      Display;
    end
    else begin
      ExtendCurrBlock(RedrawYes);
      DisplayCell(OldPos);
      DisplayCell(CurrPos);
    end;
  end; { with }
end; { MoveEnd }

procedure MoveUp;
{ Move up a row }
var
  OldPos : CellPos;
begin
  with Vars.CurrSS^ do
  begin
    if CurrPos.Row > 1 then
    begin
      OldPos := CurrPos;
      Dec(CurrPos.Row);
      ExtendCurrBlock(RedrawYes);
      if CurrPos.Row < ScreenBlock.Start.Row then
      begin
        DisplayCell(OldPos);
        SetScreenRowStart(CurrPos.Row);
        DisplayRows;
        DisplayArea.Scroll(Down, 1);
        DisplayRow(CurrPos.Row);
      end
      else begin
        DisplayCell(OldPos);
        DisplayCell(CurrPos);
      end;
    end;
  end; { with }
end; { MoveUp }

procedure MoveDown;
{ Move down a row }
var
  OldPos : CellPos;
begin
  with Vars.CurrSS^ do
  begin
    if CurrPos.Row < MaxRows then
    begin
      OldPos := CurrPos;
      Inc(CurrPos.Row);
      if CurrPos.Row > ScreenBlock.Stop.Row then
      begin
        ExtendCurrBlock(RedrawNo);
        DisplayCell(OldPos);
        SetScreenRowStop(CurrPos.Row);
        DisplayRows;
        DisplayArea.Scroll(Up, 1);
        DisplayRow(CurrPos.Row);
      end
      else begin
        ExtendCurrBlock(RedrawYes);
        DisplayCell(OldPos);
        DisplayCell(CurrPos);
      end;
    end;
  end; { with }
end; { MoveDown }

procedure MovePgUp;
{ Move up a page }
var
  OldPos : CellPos;
begin
  with Vars.CurrSS^ do
  begin
    if CurrPos.Row > 1 then
    begin
      OldPos := CurrPos;
      CurrPos.Row := Max(1, Longint(CurrPos.Row) - TotalRows);
      ExtendCurrBlock(RedrawNo);
      if CurrPos.Row < ScreenBlock.Start.Row then
      begin
        SetScreenRowStart(CurrPos.Row);
        DisplayRows;
        DisplayAllCells;
      end
      else begin
        DisplayCell(OldPos);
        DisplayCell(CurrPos);
      end;
    end;
  end; { with }
end; { MovePgUp }

procedure MovePgDn;
{ Move down a page }
var
  OldPos : CellPos;
begin
  with Vars.CurrSS^ do
  begin
    if CurrPos.Row < MaxRows then
    begin
      OldPos := CurrPos;
      CurrPos.Row := Min(MaxRows, Longint(CurrPos.Row) + TotalRows);
      ExtendCurrBlock(RedrawNo);
      if CurrPos.Row > ScreenBlock.Start.Row then
      begin
        SetScreenRowStart(CurrPos.Row);
        DisplayRows;
        DisplayAllCells;
      end
      else begin
        DisplayCell(OldPos);
        DisplayCell(CurrPos);
      end;
    end;
  end; { with }
end; { MovePgDn }

procedure MoveLeft;
{ Move left a column }
var
  C : Word;
  OldPos : CellPos;
  OldSCol : Word;
begin
  with Vars.CurrSS^ do
  begin
    if CurrPos.Col > 1 then
    begin
      OldPos := CurrPos;
      Dec(CurrPos.Col);
      ExtendCurrBlock(RedrawYes);
      if CurrPos.Col < ScreenBlock.Start.Col then
      begin
        OldSCol := ScreenBlock.Start.Col;
        C := GetColStart(1);
        DisplayCell(OldPos);
        SetScreenColStart(CurrPos.Col);
        SetBlankArea;
        DisplayCols;
        DisplayArea.Scroll(Right,
          GetColStart(OldSCol - ScreenBlock.Start.Col) - GetColStart(0));
        if not NoBlankArea then
          BlankArea.Clear;
        for C := ScreenBlock.Start.Col to CurrPos.Col do
          DisplayCol(C);
      end
      else begin
        DisplayCell(OldPos);
        DisplayCell(CurrPos);
      end;
    end;
  end; { with }
end; { MoveLeft }

procedure MoveRight;
{ Move right a column }
var
  C : Word;
  OldPos : CellPos;
  SaveColStart : array[0..79] of Byte;
  OldSCol : Word;
begin
  with Vars.CurrSS^ do
  begin
    if CurrPos.Col < MaxCols then
    begin
      OldPos := CurrPos;
      Inc(CurrPos.Col);
      if CurrPos.Col > ScreenBlock.Stop.Col then
      begin
        ExtendCurrBlock(RedrawNo);
        for C := 0 to Pred(MaxScreenCols) do
          SaveColStart[C] := GetColStart(C);
        OldSCol := ScreenBlock.Start.Col;
        DisplayCell(OldPos);
        C := ColWidth(ScreenBlock.Start.Col);
        SetScreenColStop(CurrPos.Col);
        SetBlankArea;
        DisplayCols;
        DisplayArea.Scroll(Left,
          SaveColStart[ScreenBlock.Start.Col - OldSCol] - ColStart^[0]);
        if not NoBlankArea then
          BlankArea.Clear;
        for C := CurrPos.Col to ScreenBlock.Stop.Col do
          DisplayCol(C);
      end
      else begin
        ExtendCurrBlock(RedrawYes);
        DisplayCell(OldPos);
        DisplayCell(CurrPos);
      end;
    end;
  end; { with }
end; { MoveRight }

procedure MovePgLeft;
{ Move left a page }
var
  OldPos : CellPos;
begin
  with Vars.CurrSS^ do
  begin
    if CurrPos.Col > 1 then
    begin
      OldPos := CurrPos;
      CurrPos.Col := Max(1, Pred(ScreenBlock.Start.Col));
      ExtendCurrBlock(RedrawNo);
      if CurrPos.Col < ScreenBlock.Start.Col then
      begin
        SetScreenColStop(CurrPos.Col);
        SetBlankArea;
        DisplayCols;
        if not NoBlankArea then
          BlankArea.Clear;
        DisplayAllCells;
      end
      else begin
        DisplayCell(OldPos);
        DisplayCell(CurrPos);
      end;
    end;
  end; { with }
end; { MovePgLeft }

procedure MovePgRight;
{ Move right a page }
var
  OldPos : CellPos;
begin
  with Vars.CurrSS^ do
  begin
    if CurrPos.Col < MaxCols then
    begin
      OldPos := CurrPos;
      CurrPos.Col := Min(MaxCols, Succ(ScreenBlock.Stop.Col));
      ExtendCurrBlock(RedrawNo);
      if CurrPos.Col > ScreenBlock.Start.Col then
      begin
        SetScreenColStart(CurrPos.Col);
        SetBlankArea;
        DisplayCols;
        if not NoBlankArea then
          BlankArea.Clear;
        DisplayAllCells;
      end
      else begin
        DisplayCell(OldPos);
        DisplayCell(CurrPos);
      end;
    end;
  end; { with }
end; { MovePgRight }

procedure HandleInput(Ch : Word);
{ Process the initial input from the keyboard }
begin
  EditInput(Ch, EditNo);
end; { HandleInput }

procedure ProgramObject.GetCommands;
{ Read the keyboard and process the next command }
var
  Ch : Word;
begin 
  repeat
    CurrSS^.DisplayCellData;
    ClearInputBuffer;
    Ch := GetKey;
    case Ch of
      F2 : SaveCurrSpreadsheet;
      AltF2 : NameSaveSpreadsheet;
      F3 : ReplaceSpreadsheet;
      AltF3 : NewSpreadsheet;
      F4 : DeleteSheet;
      F6 : NextSpreadsheet;
      F7 : ToggleFormulas;
      F8 : ToggleAutoCalc;
      F9 : Recalc;
      F10 : MainMenu.RunMenu;
      AltX : Stop := True;
      InsKey : ToggleCurrBlock;
      DelKey : RemoveCell;
      HomeKey : MoveHome;
      EndKey : MoveEnd;
      UpKey : MoveUp;
      DownKey : MoveDown;
      LeftKey : MoveLeft;
      RightKey : MoveRight;
      PgUpKey : MovePgUp;
      PgDnKey : MovePgDn;
      CtrlLeftKey : MovePgLeft;
      CtrlRightKey : MovePgRight;
      Ord(' ')..Ord('~') : HandleInput(Ch);
    end;
  until Stop;
end; { ProgramObject.GetCommands }

procedure ProgramObject.SetDisplayAreas;
{ Set the display areas of the various spreadsheets }
var
  S : SpreadsheetPtr;
  Total, StartRow, Amt : Word;
begin
  S := SSData;
  StartRow := Succ(EmptyRowsAtTop);
  Amt := (Scr.CurrRows - EmptyRowsAtTop - EmptyRowsAtBottom) div
         TotalSheets;
  Total := 1;
  repeat
    if S^.Next = nil then
      Amt := Succ(Scr.CurrRows - EmptyRowsAtBottom - StartRow);
    S^.SetAreas(Total, 1, StartRow, Scr.CurrCols, Pred(StartRow + Amt));
    Inc(StartRow, Amt);
    S := S^.Next;
    Inc(Total);
  until S = nil;
end; { ProgramObject.SetDisplayAreas }

procedure ProgramObject.DisplayAll;
{ Display all of the spreadsheets }
var
  S : SpreadsheetPtr;
begin
  TextAttr := Colors.BlankColor;
  ClrScr;
  WriteColor(TitleString, Colors.TitleColor);
  Scr.PrintHelpLine(HelpLine);
  WriteXY(MemoryString, Scr.CurrCols - Length(MemoryString) - 5, 1,
          Colors.PromptColor);
  S := SSData;
  repeat
    S^.Display;
    S := S^.Next;
  until S = nil;
end; { ProgramObject.DisplayAll }

function ProgramObject.AddSheet(Name : PathStr) : Boolean;
{ Add a new spreadsheet }
var
  A, S : SpreadsheetPtr;
  Good, AllocatingNext : Boolean;
begin
  AddSheet := False;
  if TotalSheets = MaxSpreadsheets then
    Exit;
  S := SSData;
  while (S <> nil) and (S^.Next <> nil) do
    S := S^.Next;
  if SSData <> nil then
  begin
    A := S;
    New(S^.Next);
    S := S^.Next;
    AllocatingNext := True;
  end
  else begin
    New(S);
    AllocatingNext := False;
  end;
  if S = nil then
    Exit;
  if Name = '' then
    Good := S^.Init(0, DefaultMaxCols, DefaultMaxRows,
                    DefaultMaxDecimalPlaces, DefaultDefaultDecimalPlaces,
                    DefaultDefaultColWidth)
  else
    Good := S^.FromFile(Name);
  if not Good then
  begin
    Dispose(S);
    if AllocatingNext then
      A^.Next := nil;
    Exit;
  end;
  if SSData = nil then
    SSData := S;
  if CurrSS <> nil then
    CurrSS^.Current := False;
  CurrSS := S;
  Inc(TotalSheets);
  S^.Next := nil;
  AddSheet := True;
end; { ProgramObject.AddSheet }

procedure ProgramObject.DeleteSheet;
{ Delete a spreadsheet }
var
  S : SpreadsheetPtr;
begin
  if TotalSheets > 1 then
  begin
    S := SSData;
    if S = CurrSS then
      SSData := S^.Next
    else begin
      while S^.Next <> CurrSS do
        S := S^.Next;
      S^.Next := S^.Next^.Next;
    end;
  end;
  with CurrSS^ do
  begin
    CheckForSave;
    Done;
  end; { with }
  if TotalSheets > 1 then
  begin
    FreeMem(CurrSS, SizeOf(Spreadsheet));
    Dec(TotalSheets);
    CurrSS := SSData;
  end
  else
    CurrSS^.Init(0, DefaultMaxCols, DefaultMaxRows,
                 DefaultMaxDecimalPlaces, DefaultDefaultDecimalPlaces,
                 DefaultDefaultColWidth);
  SetDisplayAreas;
  DisplayAll;
  with CurrSS^ do
  begin
    MakeCurrent;
    DisplayCell(CurrPos);
  end; { with }
end; { ProgramObject.DeleteSheet }

procedure InitMenus;
{ Initialize the menu items }
var
  Good : Boolean;
  P : Word;
begin
  with Vars do
  begin
    with MainMenu do
    begin
      Init(MainMenuString, nil);
      Good := AddItemMenu(@SpreadsheetMenu);
      Good := AddItemMenu(@BlockMenu);
      Good := AddItemMenu(@ColumnMenu);
      Good := AddItemMenu(@RowMenu);
      Good := AddItemProc(FormatCell);
      Good := AddItemProc(GotoCell);
      Good := AddItemProc(EditCell);
      Good := AddItemMenu(@UtilityMenu);
      Good := AddItemProc(Quit);
    end; { with }
    with SpreadsheetMenu do
    begin
      Init(SpreadsheetMenuString, @MainMenu);
      Good := AddItemProc(Replacespreadsheet);
      Good := AddItemProc(SaveCurrSpreadsheet);
      Good := AddItemProc(ZapSpreadsheet);
      Good := AddItemProc(NameSaveSpreadsheet);
      Good := AddItemMenu(@OpenMenu);
      Good := AddItemProc(CloseSpreadsheet);
      Good := AddItemProc(NextSpreadsheet);
      Good := AddItemProc(PrintSpreadsheet);
    end; { with }
    with OpenMenu do
    begin
      Init(OpenMenuString, @SpreadsheetMenu);
      Good := AddItemProc(NewSpreadsheet);
      Good := AddItemProc(NewBlankSpreadsheet);
    end; { with }
    with BlockMenu do
    begin
      Init(BlockMenuString, @MainMenu);
      Good := AddItemProc(CopyBlock);
      Good := AddItemProc(DeleteBlock);
      Good := AddItemProc(FormatBlock);
      Good := AddItemProc(FormatDefault);
    end; { with }
    with ColumnMenu do
    begin
      Init(ColumnMenuString, @MainMenu);
      Good := AddItemProc(ColInsert);
      Good := AddItemProc(ColDelete);
      Good := AddItemProc(ChangeColWidth);
    end; { with }
    with RowMenu do
    begin
      Init(RowMenuString, @MainMenu);
      Good := AddItemProc(RowInsert);
      Good := AddItemProc(RowDelete);
    end; { with }
    with UtilityMenu do
    begin
      if Scr.VideoType >= MCGA then
      begin
        Init(UtilityMenuString1, @MainMenu);
        Good := AddItemProc(ToggleMaxLines);
      end
      else
        Init(UtilityMenuString2, @MainMenu);
      Good := AddItemProc(Recalc);
      Good := AddItemProc(ToggleFormulas);
      Good := AddItemProc(ToggleAutoCalc);
    end; { with }
  end; { with }
end; { InitMenus }

procedure Run;
{ The main part of the program - it sets up the spreadsheets, reads commands,
  and them releases all of the memory that it used }
begin
  SetCursor(NoCursor);
  with Vars do
  begin
    Init;
    GetCommands;
    Done;
  end;
end; { Run }

begin
  CheckBreak := False;
  HeapError := @RunHeapError;
end.
