home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / pascal / turbo55 / install / tcalc.arc / TCSHEET.PAS < prev    next >
Pascal/Delphi Source File  |  1989-05-02  |  47KB  |  1,722 lines

  1.  
  2. { Copyright (c) 1989 by Borland International, Inc. }
  3.  
  4. unit TCSheet;
  5. { Turbo Pascal 5.5 object-oriented example spreadsheet routines.
  6.   This unit is used by TCALC.PAS.
  7.   See TCALC.DOC for an more information about this example.
  8. }
  9.  
  10. {$N+,S-}
  11.  
  12. interface
  13.  
  14. uses Crt, Dos, Objects, TCUtil, TCInput, TCScreen, TCLStr, TCHash, TCCell,
  15.      TCCellSp, TCParser;
  16.  
  17. const
  18.   DefaultMaxCols = 65535;
  19.   DefaultMaxRows = 65535;
  20.   DefaultMaxDecimalPlaces = 8;
  21.   DefaultDefaultDecimalPlaces = 4;
  22.   DefaultDefaultColWidth = 10;
  23.   EmptyRowsAtTop = 1;
  24.   EmptyRowsAtBottom = 2;
  25.   MinColWidth = 3;
  26.   CurrentChar = #4;
  27.   ChangedChar = '*';
  28.   PrintNormalCols = 80;
  29.   PrintCompressedCols = 132;
  30.   PrintRows = 66;
  31.   PrintTopMargin = 1;
  32.   PrintBottomMargin = 1;
  33.   PrinterCompressChar = #15;
  34.   EditYes = True;
  35.   EditNo = False;
  36.   DisplayYes = True;
  37.   DisplayNo = False;
  38.   WasChanged = True;
  39.   NotChanged = False;
  40.   AutoCalcLetter = 'A';
  41.   FormulaDisplayLetter = 'F';
  42.   MemoryString = 'Memory: ';
  43.   FileHeader = 'TurboCalc Spreadsheet'^Z;
  44.   ErrorString = 'ERROR';
  45.   TempFileName = 'TEMP.TMP';  { Temporary file used for rehashing }
  46.   PrinterName = 'PRN';
  47.   PromptFileSave = 'File to save';
  48.   PromptFilePrint = 'File to print to (ENTER = Printer)';
  49.   PromptOverwriteFile = 'The file exists.  Overwrite it';
  50.   PromptCompressPrint = 'Compress the printing';
  51.   PromptBorderPrint = 'Print the borders';
  52.   PromptColumnWidth = 'Column to change';
  53.   PromptNewWidth = 'New width';
  54.   PromptColumnDelete = 'Column to delete';
  55.   PromptColumnInsert = 'Insert new column before column';
  56.   PromptRowDelete = 'Row to delete';
  57.   PromptRowInsert = 'Insert new row before row';
  58.   PromptSaveYN = 'Save spreadsheet';
  59.   ErrNoOpen = 'Cannot open file';
  60.   ErrDiskFull = 'Disk full';
  61.   ErrPrinterError = 'Printer error';
  62.   ErrNotSpreadsheet = 'Not a TurboCalc spreadsheet file';
  63.   MsgRecalc = 'Recalculating cell values';
  64.   MsgSave = 'Saving spreadsheet';
  65.   MsgLoad = 'Loading spreadsheet';
  66.   MsgBlockDelete = 'Deleting block';
  67.  
  68. type
  69.   ColStartArray = array[0..ScreenCols] of Byte;
  70.   ColStartPtr = ^ColStartArray;
  71.   SpreadsheetPtr = ^Spreadsheet;
  72.   Spreadsheet = object
  73.     Number : Byte;
  74.     MaxRows : Word;
  75.     MaxCols : Word;
  76.     MaxDecimalPlaces : Byte;
  77.     MaxColWidth : Byte;
  78.     MaxScreenCols : Byte;
  79.     DefaultColWidth : Byte;
  80.     DefaultDecimalPlaces : Byte;
  81.     RowNumberSpace : Byte;
  82.     ColSpace : Byte;
  83.     Current : Boolean;
  84.     Changed : Boolean;
  85.     CurrPos : CellPos;
  86.     LastPos : CellPos;
  87.     ScreenBlock : Block;
  88.     CurrBlock : Block;
  89.     BlockOn : Boolean;
  90.     FileName : PathStr;
  91.     TotalRows : ScreenRowRange;
  92.     DisplayArea : ScreenArea;
  93.     ColArea : ScreenArea;
  94.     RowArea : ScreenArea;
  95.     InfoArea : ScreenArea;
  96.     DataArea : ScreenArea;
  97.     ContentsArea : ScreenArea;
  98.     BlankArea : ScreenArea;
  99.     NoBlankArea : Boolean;
  100.     ColStart : ColStartPtr;
  101.     DisplayFormulas : Boolean;
  102.     AutoCalc : Boolean;
  103.     CellHash : CellHashTable;
  104.     OverwriteHash : OverwriteHashTable;
  105.     WidthHash : WidthHashTable;
  106.     FormatHash : FormatHashTable;
  107.     Next : SpreadsheetPtr;
  108.     constructor Init(InitCells : Longint; InitMaxCols, InitMaxRows : Word;
  109.                      InitMaxDecimalPlaces, InitDefaultDecimalPlaces : Byte;
  110.                      InitDefaultColWidth : Byte);
  111.     destructor Done;
  112.     function GetColStart(Col : Word) : Byte;
  113.     procedure SetAreas(NewNumber : Word; X1 : ScreenColRange;
  114.                        Y1 : ScreenRowRange; X2 : ScreenColRange;
  115.                        Y2 : ScreenRowRange);
  116.     procedure DisplayCols;
  117.     procedure DisplayRows;
  118.     procedure DisplayInfo;
  119.     procedure DisplayAllCells;
  120.     procedure Display;
  121.     procedure DisplayCell(P : CellPos);
  122.     procedure DisplayCellData;
  123.     procedure DisplayCellBlock(C1 : Word; R1 : Word; C2 : Word;
  124.                                R2 : Word);
  125.     procedure DisplayBlock(B : Block);
  126.     procedure DisplayBlockDiff(B1, B2 : Block);
  127.     procedure DisplayCol(Col : Word);
  128.     procedure DisplayRow(Row : Word);
  129.     procedure DisplayMemory;
  130.     procedure DisplayFileName;
  131.     procedure SetChanged(IsChanged : Boolean);
  132.     procedure MakeCurrent;
  133.     procedure MakeNotCurrent;
  134.     procedure Update(UDisplay : Boolean);
  135.     procedure ToggleFormulaDisplay;
  136.     procedure SetScreenColStart(NewCol : Word);
  137.     procedure SetScreenColStop(NewCol: Word);
  138.     procedure SetScreenRowStart(NewRow : Word);
  139.     procedure SetScreenRowStop(NewRow : Word);
  140.     procedure FindScreenColStart;
  141.     procedure FindScreenColStop;
  142.     procedure FindScreenRowStart;
  143.     procedure FindScreenRowStop;
  144.     procedure SetBlankArea;
  145.     function AddCell(CellType : CellTypes; P : CellPos; E : Boolean;
  146.                      V : Extended; I : LStringPtr) : Boolean;
  147.     procedure DeleteCell(P : CellPos; var Deleted : Boolean);
  148.     procedure DeleteBlock(B : Block; var Deleted : Boolean);
  149.     function CellToFString(P : CellPos; var Color : Byte) : String;
  150.     procedure SetLastPos(DPos : CellPos);
  151.     function GetCurrCol : Word;
  152.     function GetCurrRow : Word;
  153.     function ColToX(Col : Word) : Byte;
  154.     function RowToY(Row : Word) : Byte;
  155.     function ColWidth(Col : Word) : Byte;
  156.     function SameCellPos(P1, P2 : CellPos) : Boolean;
  157.     procedure FixOverwrite;
  158.     function FromFile(Name : PathStr) : Boolean;
  159.     procedure ToFile(Name : PathStr);
  160.     procedure CheckForSave;
  161.     procedure ChangeWidth;
  162.     function CellHashStart(TotalCells : Longint) : BucketRange;
  163.     function WidthHashStart(TotalCells : Longint) : BucketRange;
  164.     function OverwriteHashStart(TotalCells : Longint) : BucketRange;
  165.     procedure Print;
  166.     procedure DeleteColumn;
  167.     procedure InsertColumn;
  168.     procedure DeleteRow;
  169.     procedure InsertRow;
  170.   end;
  171.  
  172. function GetColWidth(var WHash : WidthHashTable; C : Word) : Byte;
  173.  
  174. implementation
  175.  
  176. function GetColWidth(var WHash : WidthHashTable; C : Word) : Byte;
  177. { Returns the width of a column }
  178. var
  179.   W : Word;
  180. begin
  181.   W := WHash.Search(C);
  182.   if W = 0 then
  183.     GetColWidth := WHash.GetDefaultColWidth
  184.   else
  185.     GetColWidth := W;
  186. end; { GetColWidth }
  187.  
  188. constructor Spreadsheet.Init(InitCells : Longint; InitMaxCols,
  189.                              InitMaxRows : Word; InitMaxDecimalPlaces,
  190.                              InitDefaultDecimalPlaces : Byte;
  191.                              InitDefaultColWidth : Byte);
  192. { Sets up a new spreadsheet }
  193. begin
  194.   if not CellHash.Init(CellHashStart(InitCells)) then
  195.     Fail;
  196.   if not WidthHash.Init(WidthHashStart(InitCells), InitDefaultColWidth) then
  197.   begin
  198.     CellHash.Done;
  199.     Fail;
  200.   end;
  201.   if not OverwriteHash.Init(OverwriteHashStart(InitCells)) then
  202.   begin
  203.     CellHash.Done;
  204.     WidthHash.Done;
  205.     Fail;
  206.   end;
  207.   if not FormatHash.Init then
  208.   begin
  209.     CellHash.Done;
  210.     WidthHash.Done;
  211.     OverwriteHash.Done;
  212.     Fail;
  213.   end;
  214.   MaxCols := InitMaxCols;
  215.   MaxRows := InitMaxRows;
  216.   RowNumberSpace := Ord(MaxRows >= 10000) + Ord(MaxRows >= 1000) +
  217.                     Ord(MaxRows >= 100) + Ord(MaxRows >= 10) + 2;
  218.   MaxColWidth := ScreenCols - RowNumberSpace;
  219.   MaxScreenCols := MaxColWidth div MinColWidth;
  220.   GetMem(ColStart, MaxScreenCols);
  221.   if ColStart = nil then
  222.   begin
  223.     CellHash.Done;
  224.     WidthHash.Done;
  225.     OverwriteHash.Done;
  226.     FormatHash.Done;
  227.     Fail;
  228.   end;
  229.   CurrPos.Col := 1;
  230.   CurrPos.Row := 1;
  231.   LastPos := CurrPos;
  232.   BlockOn := False;
  233.   FileName := '';
  234.   DisplayFormulas := False;
  235.   AutoCalc := False;
  236.   Current := False;
  237.   Changed := False;
  238.   ScreenBlock.Start.Col := 1;
  239.   ScreenBlock.Start.Row := 1;
  240.   ColSpace := Succ(Ord(MaxCols >= 18279) + Ord(MaxCols >= 703) +
  241.                    Ord(MaxCols >= 27));
  242.   MaxDecimalPlaces := InitMaxDecimalPlaces;
  243.   DefaultColWidth := InitDefaultColWidth;
  244.   DefaultDecimalPlaces := InitDefaultDecimalPlaces;
  245. end; { Spreadsheet.Init }
  246.  
  247. destructor Spreadsheet.Done;
  248. { Removes a spreadsheet from memory }
  249. begin
  250.   CellHash.Done;
  251.   WidthHash.Done;
  252.   OverwriteHash.Done;
  253.   FormatHash.Done;
  254.   FreeMem(ColStart, MaxScreenCols);
  255. end; { Spreadsheet.Done }
  256.  
  257. function Spreadsheet.GetColStart(Col : Word) : Byte;
  258. begin
  259.   GetColStart := ColStart^[Col];
  260. end; { Spreadsheet.GetColStart }
  261.  
  262. procedure Spreadsheet.SetAreas(NewNumber : Word; X1 : ScreenColRange;
  263.                                Y1 : ScreenRowRange; X2 : ScreenColRange;
  264.                                Y2 : ScreenRowRange);
  265. { Sets up a spreadsheet's display areas }
  266. begin
  267.   Number := NewNumber;
  268.   TotalRows := Y2 - Y1 - 2;
  269.   ColArea.Init(X1 + RowNumberSpace, Y1, X2, Y1, Colors.ColColor);
  270.   RowArea.Init(X1, Succ(Y1), Pred(X1 + RowNumberSpace), Y2 - 2,
  271.                Colors.RowColor);
  272.   InfoArea.Init(X1, Y1, Pred(X1 + RowNumberSpace), Y1, Colors.InfoColor);
  273.   DisplayArea.Init(X1 + RowNumberSpace, Succ(Y1), X2, Y2 - 2,
  274.                    Colors.BlankColor);
  275.   DataArea.Init(X1, Pred(Y2), X2, Pred(Y2), Colors.BlankColor);
  276.   ContentsArea.Init(X1, Y2, X2, Y2, Colors.ContentsColor);
  277.   SetScreenColStart(ScreenBlock.Start.Col);
  278.   SetScreenRowStart(ScreenBlock.Start.Row);
  279.   SetBlankArea;
  280. end; { Spreadsheet.SetAreas }
  281.  
  282. procedure Spreadsheet.DisplayCols;
  283. { Shows the column headings }
  284. var
  285.   C : Word;
  286. begin
  287.   ColArea.Clear;
  288.   with ScreenBlock do
  289.   begin
  290.     for C := Start.Col to Stop.Col do
  291.       WriteXY(CenterStr(ColToString(C), ColWidth(C)),
  292.               ColStart^[C - Start.Col], ColArea.UpperLeft.Row,
  293.               Colors.ColColor);
  294.   end; { with }
  295. end; { Spreadsheet.DisplayCols }
  296.  
  297. procedure Spreadsheet.DisplayRows;
  298. { Shows the row headings }
  299. var
  300.   R : Word;
  301. begin
  302.   RowArea.Clear;
  303.   with ScreenBlock do
  304.   begin
  305.     for R := Start.Row to Stop.Row do
  306.       with RowArea do
  307.         WriteXY(LeftJustStr(RowToString(R), RowNumberSpace),
  308.                 UpperLeft.Col, R - Start.Row + UpperLeft.Row,
  309.                 Colors.RowColor);
  310.   end; { with }
  311. end; { Spreadsheet.DisplayRows }
  312.  
  313. procedure Spreadsheet.DisplayInfo;
  314. { Shows the spreadsheet number, current dot, and state of AutoCalc and
  315.   formula display }
  316. begin
  317.   InfoArea.Clear;
  318.   with InfoArea do
  319.     WriteXY(NumToString(Number), UpperLeft.Col, UpperLeft.Row,
  320.                 Colors.InfoColor);
  321.   if Current then
  322.     Write(CurrentChar)
  323.   else
  324.     Write(' ');
  325.   if AutoCalc then
  326.     Write(AutoCalcLetter)
  327.   else
  328.     Write(' ');
  329.   if DisplayFormulas then
  330.     Write(FormulaDisplayLetter)
  331.   else
  332.     Write(' ');
  333. end; { Spreadsheet.DisplayRows }
  334.  
  335. procedure Spreadsheet.DisplayAllCells;
  336. { Displays all of the cells on the screen }
  337. begin
  338.   DisplayArea.Clear;
  339.   DisplayBlock(ScreenBlock);
  340. end; { Spreadsheet.DisplayAllCells }
  341.  
  342. procedure Spreadsheet.DisplayCell(P : CellPos);
  343. { Displays a single cell }
  344. var
  345.   S : String[ScreenCols];
  346.   Color : Byte;
  347. begin
  348.   S := CellToFString(P, Color);
  349.   WriteXY(S, ColToX(P.Col), RowToY(P.Row), Color);
  350. end; { Spreadsheet.DisplayCell }
  351.  
  352. procedure Spreadsheet.DisplayCellData;
  353. { Displays information about a cell - its type and its contents }
  354. var
  355.   CP : CellPtr;
  356. begin
  357.   CP := CellHash.Search(CurrPos);
  358.   with DataArea do
  359.     WriteXY(LeftJustStr(ColToString(CurrPos.Col) +
  360.                 RowToString(CurrPos.Row) + ' ' + CP^.Name, 19),
  361.                 UpperLeft.Col, UpperLeft.Row, Colors.CellDataColor);
  362.   with ContentsArea do
  363.   begin
  364.     Clear;
  365.     WriteXY(LeftJustStr(CP^.DisplayString(DisplayFormulas,
  366.                 MaxDecimalPlaces), Scr.CurrCols), UpperLeft.Col,
  367.                 UpperLeft.Row, Colors.ContentsColor);
  368.   end; { with }
  369. end; { Spreadsheet.DisplayCellData }
  370.  
  371. procedure Spreadsheet.DisplayCellBlock(C1 : Word; R1 : Word;
  372.                                        C2 : Word; R2 : Word);
  373. { Displays all cells within a range of rows and columns }
  374. var
  375.   P : CellPos;
  376. begin
  377.   with ScreenBlock do
  378.   begin
  379.     for P.Row := Max(R1, Start.Row) to Min(R2, Stop.Row) do
  380.     begin
  381.       for P.Col := Max(C1, Start.Col) to Min(C2, Stop.Col) do
  382.         DisplayCell(P);
  383.     end;
  384.   end; { with }
  385. end; { Spreadsheet.DisplayCellBlock }
  386.  
  387. procedure Spreadsheet.DisplayBlock(B : Block);
  388. { Displays all cells within a certain block }
  389. begin
  390.   with B do
  391.     DisplayCellBlock(Start.Col, Start.Row, Stop.Col, Stop.Row);
  392. end; { Spreadsheet.DisplayBlock }
  393.  
  394. procedure Spreadsheet.DisplayBlockDiff(B1, B2 : Block);
  395. { When a block is extended, this will update the screen to show the new
  396.   block }
  397. var
  398.   B : Block;
  399.   DisplayMiddle : Boolean;
  400. begin
  401.   if Compare(B1, B2, SizeOf(Block)) then
  402.     Exit;
  403.   with B do
  404.   begin
  405.     DisplayMiddle := False;
  406.     if B1.Stop.Col <> B2.Stop.Col then
  407.     begin
  408.       B.Start.Row := B1.Start.Row;
  409.       B.Start.Col := Min(Succ(B1.Stop.Col), Succ(B2.Stop.Col));
  410.       B.Stop.Row := Min(B1.Stop.Row, B2.Stop.Row);
  411.       B.Stop.Col := Max(B1.Stop.Col, B2.Stop.Col);
  412.       DisplayBlock(B);
  413.       DisplayMiddle := True;
  414.     end;
  415.     if B1.Stop.Row <> B2.Stop.Row then
  416.     begin
  417.       B.Start.Row := Min(Succ(B1.Stop.Row), Succ(B2.Stop.Row));
  418.       B.Start.Col := B1.Start.Col;
  419.       B.Stop.Row := Max(B1.Stop.Row, B2.Stop.Row);
  420.       B.Stop.Col := Min(B1.Stop.Col, B2.Stop.Col);
  421.       DisplayBlock(B);
  422.       DisplayMiddle := True;
  423.     end;
  424.     if DisplayMiddle then
  425.     begin
  426.       B.Start.Row := Min(Succ(B1.Stop.Row), Succ(B2.Stop.Row));
  427.       B.Start.Col := Min(Succ(B1.Stop.Col), Succ(B2.Stop.Col));
  428.       B.Stop.Row := Max(B1.Stop.Row, B2.Stop.Row);
  429.       B.Stop.Col := Max(B1.Stop.Col, B2.Stop.Col);
  430.       DisplayBlock(B);
  431.     end;
  432.   end; { with }
  433. end; { Spreadsheet.DisplayBlockDiff }
  434.  
  435. procedure Spreadsheet.DisplayCol(Col : Word);
  436. { Display a column of cells }
  437. begin
  438.   with ScreenBlock do
  439.     DisplayCellBlock(Col, Start.Row, Col, Stop.Row);
  440. end; { Spreadsheet.DisplayCol }
  441.  
  442. procedure Spreadsheet.DisplayRow(Row : Word);
  443. { Display a row of cells }
  444. begin
  445.   with ScreenBlock do
  446.     DisplayCellBlock(Start.Col, Row, Stop.Col, Row);
  447. end; { Spreadsheet.DisplayRow }
  448.  
  449. procedure Spreadsheet.DisplayMemory;
  450. { Display the amount of free memory }
  451. begin
  452.   WriteXY(RightJustStr(NumToString(MemAvail), 6), Scr.CurrCols - 5, 1,
  453.           Colors.MemoryColor);
  454. end; { Spreadsheet.DisplayMemory }
  455.  
  456. procedure Spreadsheet.DisplayFileName;
  457. { Display the spreadsheet's file name, and whether or not it has been
  458.   updated }
  459. var
  460.   S : PathStr;
  461. begin
  462.   with DataArea do
  463.   begin
  464.     if FileName = '' then
  465.       S := 'No file'
  466.     else
  467.       S := FExpand(FileName);
  468.     WriteXY(LeftJustStr(S, LowerRight.Col - UpperLeft.Col - 20),
  469.             UpperLeft.Col + 21, UpperLeft.Row, Colors.FileNameColor);
  470.   end; { with }
  471. end; { Spreadsheet.DisplayFileName }
  472.  
  473. procedure Spreadsheet.Display;
  474. { Display the entire spreadsheet }
  475. begin
  476.   DisplayCols;
  477.   DisplayRows;
  478.   DisplayInfo;
  479.   DisplayAllCells;
  480.   DisplayMemory;
  481.   DisplayCellData;
  482.   DisplayFileName;
  483.   SetChanged(Changed);
  484. end; { Spreadsheet.Display }
  485.  
  486. procedure Spreadsheet.SetChanged(IsChanged : Boolean);
  487. { Sets a spreadsheet as being changed or not changed }
  488. var
  489.   C : Char;
  490. begin
  491.   Changed := IsChanged;
  492.   if Changed then
  493.     C := ChangedChar
  494.   else
  495.     C := ' ';
  496.   with DataArea.UpperLeft do
  497.     WriteXY(C, Col + 19, Row, Colors.ChangedColor);
  498. end; { Spreadsheet.SetChanged }
  499.  
  500. procedure Spreadsheet.MakeCurrent;
  501. { Make a spreadsheet the current one }
  502. begin
  503.   Current := True;
  504.   DisplayInfo;
  505. end; { Spreadsheet.MakeCurrent }
  506.  
  507. procedure Spreadsheet.MakeNotCurrent;
  508. { Make a spreadsheet not the current one }
  509. begin
  510.   Current := False;
  511.   DisplayInfo;
  512. end; { Spreadsheet.MakeNotCurrent }
  513.  
  514. procedure Spreadsheet.Update(UDisplay : Boolean);
  515. { Update any cells in the spreadsheet that need updating }
  516. var
  517.   P, U : CellPos;
  518.   CP : CellPtr;
  519.   O : Word;
  520. begin
  521.   Scr.PrintMessage(MsgRecalc);
  522.   with CellHash do
  523.   begin
  524.     for P.Row := 1 to LastPos.Row do
  525.     begin
  526.       for P.Col := 1 to LastPos.Col do
  527.       begin
  528.         CP := Search(P);
  529.         if CP^.ShouldUpdate then
  530.         begin
  531.           with FormulaCellPtr(CP)^ do
  532.           begin
  533.             Parser.Init(@CellHash, Formula, MaxCols, MaxRows);
  534.             Parser.Parse;
  535.             Value := Parser.ParseValue;
  536.             Error := Parser.ParseError;
  537.             O := CP^.Overwritten(CellHash, FormatHash, WidthHash,
  538.                  LastPos, MaxCols, GetColWidth, DisplayFormulas);
  539.             if (OverwriteHash.Change(CP, O)) and UDisplay and
  540.                (CP^.Loc.Col + O >= ScreenBlock.Start.Col) then
  541.             begin
  542.               U := CP^.Loc;
  543.               for U.Col := CP^.Loc.Col to ScreenBlock.Stop.Col do
  544.               begin
  545.                 if ScreenBlock.CellInBlock(U) then
  546.                 DisplayCell(U);
  547.               end;
  548.             end;
  549.           end; { with }
  550.         end;
  551.       end;
  552.     end;
  553.   end; { with }
  554.   if UDisplay then
  555.     DisplayMemory;
  556.   Scr.ClearMessage;
  557. end; { Spreadsheet.Update }
  558.  
  559. procedure Spreadsheet.ToggleFormulaDisplay;
  560. { Change from showing formulas to showing values and vice versa }
  561. var
  562.   CP : CellPtr;
  563.   OChanged : Boolean;
  564. begin
  565.   DisplayFormulas := not DisplayFormulas;
  566.   DisplayInfo;
  567.   OChanged := True;
  568.   with CellHash do
  569.   begin
  570.     CP := FirstItem;
  571.     while (CP <> nil) and OChanged do
  572.     begin
  573.       if CP^.ShouldUpdate then
  574.         OChanged := OverwriteHash.Change(CP, CP^.Overwritten(CellHash,
  575.                                          FormatHash, WidthHash, LastPos,
  576.                                          MaxCols, GetColWidth,
  577.                                          DisplayFormulas));
  578.       CP := NextItem;
  579.     end;
  580.   end; { with }
  581.   DisplayAllCells;
  582.   DisplayMemory;
  583. end; { Spreadsheet.ToggleFormulaDisplay }
  584.  
  585. procedure Spreadsheet.SetScreenColStart(NewCol : Word);
  586. { Find the starting screen column }
  587. begin
  588.   ScreenBlock.Start.Col := NewCol;
  589.   FindScreenColStop;
  590.   FindScreenColStart;
  591. end; { Spreadsheet.SetScreenColStart }
  592.  
  593. procedure Spreadsheet.SetScreenColStop(NewCol : Word);
  594. { Find the ending screen column }
  595. begin
  596.   ScreenBlock.Stop.Col := NewCol;
  597.   FindScreenColStart;
  598.   FindScreenColStop;
  599. end; { Spreadsheet.SetScreenColStop }
  600.  
  601. procedure Spreadsheet.SetScreenRowStart(NewRow : Word);
  602. { Find the starting screen row }
  603. begin
  604.   ScreenBlock.Start.Row := NewRow;
  605.   FindScreenRowStop;
  606. end; { Spreadsheet.SetScreenRowStart }
  607.  
  608. procedure Spreadsheet.SetScreenRowStop(NewRow : Word);
  609. { Find the ending screen row }
  610. begin
  611.   ScreenBlock.Stop.Row := NewRow;
  612.   FindScreenRowStart;
  613. end; { Spreadsheet.SetScreenRowStop }
  614.  
  615. procedure Spreadsheet.FindScreenColStart;
  616. { Find the starting screen column when the ending column is known }
  617. var
  618.   Index, Place : Integer;
  619.   Temp, Width : Byte;
  620. begin
  621.   with ScreenBlock do
  622.   begin
  623.     Index := 0;
  624.     Place := Succ(DisplayArea.LowerRight.Col);
  625.     Width := ColWidth(Stop.Col);
  626.     repeat
  627.       ColStart^[Index] := Place - Width;
  628.       Dec(Place, Width);
  629.       Inc(Index);
  630.       if Stop.Col - Index = 0 then
  631.         Width := 0
  632.       else
  633.         Width := ColWidth(Stop.Col - Index);
  634.     until (Width = 0) or (Place - Width < DisplayArea.UpperLeft.Col);
  635.     Start.Col := Succ(Stop.Col - Index);
  636.     Dec(Index);
  637.     if ColStart^[Index] <> DisplayArea.UpperLeft.Col then
  638.     begin
  639.       Temp := ColStart^[Index] - DisplayArea.UpperLeft.Col;
  640.       for Place := 0 to Index do
  641.         Dec(ColStart^[Place], Temp);
  642.     end;
  643.     if Index > 0 then
  644.     begin
  645.       for Place := 0 to (Pred(Index) shr 1) do
  646.       begin
  647.         Temp := ColStart^[Index - Place];
  648.         ColStart^[Index - Place] := ColStart^[Place];
  649.         ColStart^[Place] := Temp;
  650.       end;
  651.     end;
  652.   end; { with }
  653. end; { Spreadsheet.FindScreenColStart }
  654.  
  655. procedure Spreadsheet.FindScreenColStop;
  656. { Find the ending screen column when the starting column is known }
  657. var
  658.   Index, Place : Byte;
  659.   Width : Byte;
  660. begin
  661.   with ScreenBlock do
  662.   begin
  663.     Index := 0;
  664.     Place := DisplayArea.UpperLeft.Col;
  665.     Width := ColWidth(Start.Col);
  666.     repeat
  667.       ColStart^[Index] := Place;
  668.       Inc(Place, Width);
  669.       Inc(Index);
  670.       if Longint(Index) + Start.Col > MaxCols then
  671.         Width := 0
  672.       else
  673.         Width := ColWidth(Index + Start.Col);
  674.     until (Width = 0) or (Place + Width > Succ(DisplayArea.LowerRight.Col));
  675.     Stop.Col := Pred(Start.Col + Index);
  676.   end; { with }
  677. end; { Spreadsheet.FindScreenColStop }
  678.  
  679. procedure Spreadsheet.FindScreenRowStart;
  680. { Find the starting screen row when the ending row is known }
  681. begin
  682.   with ScreenBlock do
  683.   begin
  684.     if Longint(Stop.Row) - TotalRows < 0 then
  685.     begin
  686.       Start.Row := 1;
  687.       FindScreenRowStop;
  688.     end
  689.     else
  690.       Start.Row := Succ(Stop.Row - TotalRows);
  691.   end; { with }
  692. end; { Spreadsheet.FindScreenRowStart }
  693.  
  694. procedure Spreadsheet.FindScreenRowStop;
  695. { Find the ending screen row when the starting row is known }
  696. begin
  697.   with ScreenBlock do
  698.   begin
  699.     if Longint(Start.Row) + TotalRows > Succ(LongInt(MaxRows)) then
  700.     begin
  701.       Stop.Row := MaxRows;
  702.       FindScreenRowStart;
  703.     end
  704.     else
  705.       Stop.Row := Pred(Start.Row + TotalRows);
  706.   end; { with }
  707. end; { Spreadsheet.FindScreenRowStop }
  708.  
  709. procedure Spreadsheet.SetBlankArea;
  710. { Find the size of the blank area (the area at the right edge of the
  711.   spreadsheet that is not used }
  712. var
  713.   C : Word;
  714. begin
  715.   with BlankArea do
  716.   begin
  717.     Move(DisplayArea, BlankArea, SizeOf(DisplayArea));
  718.     with ScreenBlock do
  719.       C := ColStart^[Stop.Col - Start.Col] + ColWidth(Stop.Col);
  720.     if C > DisplayArea.LowerRight.Col then
  721.       NoBlankArea := True
  722.     else begin
  723.       NoBlankArea := False;
  724.       UpperLeft.Col := C;
  725.     end;
  726.   end; { with }
  727. end; { Spreadsheet.SetBlankArea }
  728.  
  729. function Spreadsheet.AddCell(CellType : CellTypes; P : CellPos; E : Boolean;
  730.                              V : Extended; I : LStringPtr) : Boolean;
  731. { Add a new cell to the spreadsheet }
  732. var
  733.   CP, S : CellPtr;
  734.   OldLastPos : CellPos;
  735.   Good : Boolean;
  736. begin
  737.   AddCell := False;
  738.   case CellType of
  739.     ClValue : CP := New(ValueCellPtr, Init(P, E, V));
  740.     ClFormula : CP := New(FormulaCellPtr, Init(P, E, V, I));
  741.     ClText : CP := New(TextCellPtr, Init(P, I));
  742.     ClRepeat : CP := New(RepeatCellPtr, Init(P, I^.Data^[2]));
  743.   end; { case }
  744.   if CP = nil then
  745.     Exit;
  746.   if not CellHash.Add(CP) then
  747.   begin
  748.     Dispose(CP, Done);
  749.     Exit;
  750.   end;
  751.   OldLastPos := LastPos;
  752.   LastPos.Col := Max(P.Col, LastPos.Col);
  753.   LastPos.Row := Max(P.Row, LastPos.Row);
  754.   if not OverwriteHash.Add(CP, CP^.Overwritten(CellHash, FormatHash,
  755.                            WidthHash, LastPos, MaxCols, GetColWidth,
  756.                            DisplayFormulas)) then
  757.   begin
  758.     LastPos := OldLastPos;
  759.     CellHash.Delete(CP^.Loc, S);
  760.     Dispose(CP, Done);
  761.     Exit;
  762.   end;
  763.   S := OverwriteHash.Search(CP^.Loc);
  764.   if S <> Empty then
  765.     Good := OverwriteHash.Change(S, S^.Overwritten(CellHash, FormatHash,
  766.                                  WidthHash, LastPos, MaxCols, GetColWidth,
  767.                                  DisplayFormulas));
  768.   AddCell := True;
  769. end; { Spreadsheet.AddCell }
  770.  
  771. procedure Spreadsheet.DeleteCell(P : CellPos; var Deleted : Boolean);
  772. { Delete a cell from the spreadsheet }
  773. var
  774.   CP : CellPtr;
  775.   Good : Boolean;
  776. begin
  777.   CellHash.Delete(P, CP);
  778.   if CP <> nil then
  779.   begin
  780.     Dispose(CP, Done);
  781.     OverwriteHash.Delete(P);
  782.     if P.Col > 1 then
  783.     begin
  784.       Dec(P.Col);
  785.       CP := OverwriteHash.Search(P);
  786.       if CP = Empty then
  787.         CP := CellHash.Search(P);
  788.       if CP <> Empty then
  789.         Good := OverwriteHash.Change(CP, CP^.Overwritten(CellHash,
  790.                                      FormatHash, WidthHash, LastPos, MaxCols,
  791.                                      GetColWidth, DisplayFormulas));
  792.     end;
  793.     Deleted := True;
  794.   end
  795.   else
  796.     Deleted := False;
  797. end; { Spreadsheet.DeleteCell }
  798.  
  799. procedure Spreadsheet.DeleteBlock(B : Block; var Deleted : Boolean);
  800. { Delete a block of cells from the spreadsheet }
  801. var
  802.   P : CellPos;
  803.   H, D : HashItemPtr;
  804.   Counter : Word;
  805.   CP : CellPtr;
  806. begin
  807.   Scr.PrintMessage(MsgBlockDelete);
  808.   Deleted := False;
  809.   with CellHash, B do
  810.   begin
  811.     for Counter := 1 to Buckets do
  812.     begin
  813.       H := HashData^[Counter];
  814.       while H <> nil do
  815.       begin
  816.         D := H;
  817.         H := H^.Next;
  818.         Move(D^.Data, CP, SizeOf(CP));
  819.         with CP^ do
  820.         begin
  821.           if CellInBlock(Loc) then
  822.             DeleteCell(Loc, Deleted);
  823.         end; { with }
  824.       end;
  825.     end;
  826.   end; { with }
  827.   Scr.ClearMessage;
  828. end; { DeleteBlock }
  829.  
  830. function Spreadsheet.CellToFString(P : CellPos; var Color : Byte) : String;
  831. { Create a formatted string from a cell }
  832. var
  833.   CP : CellPtr;
  834.   S : String;
  835.   S1 : DollarStr;
  836.   F : FormatType;
  837.   ColorFound : Boolean;
  838.   Colr : Byte;
  839. begin
  840.   ColorFound := True;
  841.   if Current and (SameCellPos(P, CurrPos)) then
  842.     Color := Colors.HighlightColor
  843.   else if BlockOn and (CurrBlock.CellInBlock(P)) then
  844.     Color := Colors.BlockColor
  845.   else
  846.     ColorFound := False;
  847.   CP := CellHash.Search(P);
  848.   if (CP^.HasError) then
  849.   begin
  850.     S := ErrorString;
  851.     S1 := '';
  852.     if ColorFound then
  853.       Inc(Color, Blink)
  854.     else
  855.       Color := Colors.CellErrorColor;
  856.     F := Ord(JCenter) shl JustShift;
  857.   end
  858.   else begin
  859.     S := CP^.FormattedString(OverwriteHash, FormatHash, WidthHash,
  860.                              GetColWidth, P, DisplayFormulas, 1,
  861.                              ColWidth(P.Col), S1, Colr);
  862.     if not ColorFound then
  863.       Color := Colr;
  864.     F := CP^.Format(FormatHash, DisplayFormulas);
  865.   end;
  866.   case Justification((F shr JustShift) and JustPart) of
  867.     JLeft : CellToFString := S1 + LeftJustStr(S, ColWidth(P.Col) -
  868.                                              Length(S1));
  869.     JCenter : CellToFString := S1 + CenterStr(S, ColWidth(P.Col) -
  870.                                               Length(S1));
  871.     JRight : CellToFString := S1 + RightJustStr(S, ColWidth(P.Col) -
  872.                                                 Length(S1));
  873.   end; { case }
  874. end; { Spreadsheet.CellToFString }
  875.  
  876. procedure Spreadsheet.SetLastPos(DPos : CellPos);
  877. { Find the last position used in a spreadsheet }
  878. var
  879.   CP : CellPtr;
  880.   Counter : Word;
  881.   ColFound, RowFound : Boolean;
  882. begin
  883.   with CellHash do
  884.   begin
  885.     ColFound := DPos.Col < LastPos.Col;
  886.     RowFound := DPos.Row < LastPos.Row;
  887.     if (not ColFound) or (not RowFound) then
  888.     begin
  889.       if not ColFound then
  890.         LastPos.Col := 1;
  891.       if not RowFound then
  892.         LastPos.Row := 1;
  893.       CP := FirstItem;
  894.       while CP <> nil do
  895.       begin
  896.         if not ColFound then
  897.         begin
  898.           if CP^.Loc.Col > LastPos.Col then
  899.           begin
  900.             LastPos.Col := CP^.Loc.Col;
  901.             ColFound := LastPos.Col = DPos.Col;
  902.             if ColFound and RowFound then
  903.               Exit;
  904.           end;
  905.         end;
  906.         if not RowFound then
  907.         begin
  908.           if CP^.Loc.Row > LastPos.Row then
  909.           begin
  910.             LastPos.Row := CP^.Loc.Row;
  911.             RowFound := LastPos.Row = DPos.Row;
  912.             if ColFound and RowFound then
  913.               Exit;
  914.           end;
  915.         end;
  916.         CP := NextItem;
  917.       end;
  918.     end;
  919.   end; { with }
  920. end; { Spreadsheet.SetLastPos }
  921.  
  922. function Spreadsheet.GetCurrCol : Word;
  923. { Find the current column }
  924. begin
  925.   GetCurrCol := CurrPos.Col;
  926. end; { Spreadsheet.GetCurrCol }
  927.  
  928. function Spreadsheet.GetCurrRow : Word;
  929. { Find the current row }
  930. begin
  931.   GetCurrRow := CurrPos.Row;
  932. end; { Spreadsheet.GetCurrRow }
  933.  
  934. function Spreadsheet.ColToX(Col : Word) : Byte;
  935. { Find where on the screen a column starts }
  936. begin
  937.   ColToX := ColStart^[Col - ScreenBlock.Start.Col];
  938. end; { Spreadsheet.ColToX }
  939.  
  940. function Spreadsheet.RowToY(Row : Word) : Byte;
  941. { Find where on the screen a row starts }
  942. begin
  943.   RowToY := Row + DisplayArea.UpperLeft.Row - ScreenBlock.Start.Row;
  944. end; { Spreadsheet.RowToY }
  945.  
  946. {$F+}
  947.  
  948. function Spreadsheet.ColWidth(Col : Word) : Byte;
  949. { Returns the width of a column }
  950. var
  951.   Width : Word;
  952. begin
  953.   Width := WidthHash.Search(Col);
  954.   if Width = 0 then
  955.     ColWidth := DefaultColWidth
  956.   else
  957.     ColWidth := Width;
  958. end; { Spreadsheet.ColWidth }
  959.  
  960. {$F-}
  961.  
  962. function Spreadsheet.SameCellPos(P1, P2 : CellPos) : Boolean;
  963. { Returns True if two cells are at the same position }
  964. begin
  965.   SameCellPos := Compare(P1, P2, SizeOf(CellPos));
  966. end; { Spreadsheet.SameCellPos }
  967.  
  968. procedure Spreadsheet.FixOverwrite;
  969. { Fixes the overwrite hash table when the formats have been changed }
  970. var
  971.   CP, D : CellPtr;
  972.   Counter : Word;
  973.   Good : Boolean;
  974. begin
  975.   with CellHash do
  976.   begin
  977.     CP := FirstItem;
  978.     while CP <> nil do
  979.     begin
  980.       if not OverwriteHash.Add(CP, CP^.Overwritten(CellHash, FormatHash,
  981.                                WidthHash, LastPos, MaxCols, GetColWidth,
  982.                                DisplayFormulas)) then
  983.       begin
  984.         CellHash.Delete(CP^.Loc, D);
  985.         Dispose(CP, Done);
  986.         Exit;
  987.       end;
  988.       CP := OverwriteHash.Search(CP^.Loc);
  989.       if CP <> Empty then
  990.         Good := OverwriteHash.Change(CP, CP^.Overwritten(CellHash,
  991.                                      FormatHash, WidthHash, LastPos,
  992.                                      MaxCols, GetColWidth,
  993.                                      DisplayFormulas));
  994.       CP := NextItem;
  995.     end;
  996.   end; { with }
  997. end; { Spreadsheet.FixOverwrite }
  998.  
  999. function Spreadsheet.FromFile(Name : PathStr) : Boolean;
  1000. { Reads a spreadsheet from disk }
  1001. var
  1002.   Header : String[Length(FileHeader)];
  1003.   TotalC : Longint;
  1004.   TotalW : Word;
  1005.   TotalF : Longint;
  1006.   S : SSStream;
  1007.   NewLastPos : CellPos;
  1008. begin
  1009.   FromFile := True;
  1010.   Name := UpperCase(Name);
  1011.   S.Init(Name, SOpen);
  1012.   if S.Status <> 0 then
  1013.   begin
  1014.     Scr.PrintError(ErrNoOpen);
  1015.     Init(0, DefaultMaxCols, DefaultMaxRows, DefaultMaxDecimalPlaces,
  1016.          DefaultDefaultDecimalPlaces, DefaultDefaultColWidth);
  1017.     Exit;
  1018.   end
  1019.   else begin
  1020.     Header[0] := Chr(Length(FileHeader));
  1021.     S.Read(Header[1], Length(FileHeader));
  1022.     if (S.Status <> 0) or (Header <> FileHeader) then
  1023.     begin
  1024.       Scr.PrintError(ErrNotSpreadsheet);
  1025.       S.Done;
  1026.       Init(0, DefaultMaxCols, DefaultMaxRows, DefaultMaxDecimalPlaces,
  1027.            DefaultDefaultDecimalPlaces, DefaultDefaultColWidth);
  1028.       Exit;
  1029.     end;
  1030.     FileName := Name;
  1031.     S.Read(NewLastPos, SizeOf(NewLastPos));
  1032.     S.Read(TotalW, SizeOf(TotalW));
  1033.     S.Read(TotalF, SizeOf(TotalF));
  1034.     S.Read(TotalC, SizeOf(TotalC));
  1035.     if not Init(TotalC, DefaultMaxCols, DefaultMaxRows,
  1036.                 DefaultMaxDecimalPlaces, DefaultDefaultDecimalPlaces,
  1037.                 DefaultDefaultColWidth) then
  1038.     begin
  1039.       S.Done;
  1040.       FromFile := False;
  1041.       Exit;
  1042.     end;
  1043.     LastPos := NewLastPos;
  1044.     Scr.PrintMessage(MsgLoad);
  1045.     FileName := Name;
  1046.     WidthHash.Load(S, TotalW);
  1047.     FormatHash.Load(S, TotalF);
  1048.     CellHash.Load(S, TotalC);
  1049.     S.Done;
  1050.     FixOverwrite;
  1051.     Update(DisplayNo);
  1052.     Scr.ClearMessage;
  1053.   end;
  1054.   FromFile := True;
  1055. end; { Spreadsheet.FromFile }
  1056.  
  1057. procedure Spreadsheet.ToFile(Name : PathStr);
  1058. { Writes a spreadsheet to disk }
  1059. var
  1060.   Header : String[Length(FileHeader)];
  1061.   S : SSStream;
  1062. begin
  1063.   S.Init(Name, SCreate);
  1064.   if S.Status <> 0 then
  1065.   begin
  1066.     Scr.PrintError(ErrNoOpen);
  1067.     Exit;
  1068.   end;
  1069.   Scr.PrintMessage(MsgSave);
  1070.   FileName := Name;
  1071.   Header := FileHeader;
  1072.   S.Write(Header[1], Length(Header));
  1073.   S.Write(LastPos, SizeOf(LastPos));
  1074.   S.Write(WidthHash.Items, 2);
  1075.   S.Write(FormatHash.Items, SizeOf(FormatHash.Items));
  1076.   S.Write(CellHash.Items, SizeOf(CellHash.Items));
  1077.   WidthHash.Store(S);
  1078.   FormatHash.Store(S);
  1079.   CellHash.Store(S);
  1080.   Scr.ClearMessage;
  1081.   S.Done;
  1082.   if S.Status <> 0 then
  1083.     Scr.PrintError(ErrDiskFull)
  1084.   else
  1085.     SetChanged(NotChanged);
  1086. end; { Spreadsheet.ToFile }
  1087.  
  1088. procedure Spreadsheet.CheckForSave;
  1089. { Before prompting for a file name, this will check to see if you want to
  1090.   save the spreadsheet }
  1091. var
  1092.   S : PathStr;
  1093.   GoodFile, ESCPressed : Boolean;
  1094. begin
  1095.   if Changed and (GetYesNo(PromptSaveYN, ESCPressed)) then
  1096.   begin
  1097.     S := FileName;
  1098.     repeat
  1099.       GoodFile := True;
  1100.       if S = '' then
  1101.       begin
  1102.         S := ReadString(PromptFileSave, Pred(SizeOf(PathStr)), ESCPressed);
  1103.         if S = '' then
  1104.           Exit;
  1105.       end;
  1106.       if FileExists(S) then
  1107.       begin
  1108.         GoodFile := GetYesNo(PromptOverwriteFile, ESCPressed);
  1109.         if ESCPressed then
  1110.           Exit;
  1111.         if not GoodFile then
  1112.           S := '';
  1113.       end;
  1114.     until GoodFile;
  1115.     ToFile(S);
  1116.   end;
  1117. end; { Spreadsheet.CheckForSave }
  1118.  
  1119. procedure Spreadsheet.ChangeWidth;
  1120. { Changes the width of a column }
  1121. var
  1122.   W, C : Word;
  1123.   Good : Boolean;
  1124.   P : CellPos;
  1125.   O : Word;
  1126.   CP : CellPtr;
  1127. begin
  1128.   C := GetColumn(PromptColumnWidth, MaxCols, ColSpace);
  1129.   if C = 0 then
  1130.     Exit;
  1131.   W := GetNumber(PromptNewWidth, MinColWidth, MaxColWidth, Good);
  1132.   if not Good then
  1133.     Exit;
  1134.   with WidthHash do
  1135.   begin
  1136.     Delete(C);
  1137.                 if W <> DefaultColWidth then
  1138.                         Good := Add(C, W);
  1139.   end; { with }
  1140.   if not Good then
  1141.     Exit;
  1142.   SetScreenColStart(ScreenBlock.Start.Col);
  1143.   SetChanged(WasChanged);
  1144.   with OverwriteHash do
  1145.   begin
  1146.     Done;
  1147.     Init(OverwriteHashStart(CellHash.Items));
  1148.   end;
  1149.   with CellHash do
  1150.   begin
  1151.     CP := FirstItem;
  1152.     while CP <> nil do
  1153.     begin
  1154.       O := CP^.Overwritten(CellHash, FormatHash, WidthHash, LastPos,
  1155.                            MaxCols, GetColWidth, DisplayFormulas);
  1156.       if O <> 0 then
  1157.         Good := OverwriteHash.Add(CP, O);
  1158.       CP := NextItem;
  1159.     end;
  1160.   end; { with }
  1161.   if CurrPos.Col > ScreenBlock.Stop.Col then
  1162.     SetScreenColStart(CurrPos.Col);
  1163.   Display;
  1164. end; { Spreadsheet.ChangeWidth }
  1165.  
  1166. function Spreadsheet.CellHashStart(TotalCells : Longint) : BucketRange;
  1167. { Formula that determines the number of cell hash table buckets }
  1168. begin
  1169.   CellHashStart := Max(100, Min(MaxBuckets, TotalCells div 10));
  1170. end; { Spreadsheet.CellHashStart }
  1171.  
  1172. function Spreadsheet.WidthHashStart(TotalCells : Longint) : BucketRange;
  1173. { Formula that determines the number of width hash table buckets }
  1174. begin
  1175.   WidthHashStart := 10;
  1176. end; { Spreadsheet.WidthHashStart }
  1177.  
  1178. function Spreadsheet.OverwriteHashStart(TotalCells : Longint) : BucketRange;
  1179. { Formula that determines the number of overwrite hash table buckets }
  1180. begin
  1181.   OverwriteHashStart := 10;
  1182. end; { Spreadsheet.OverwriteHashStart }
  1183.  
  1184. procedure Spreadsheet.Print;
  1185. { Prints a spreadsheet to a file or a printer }
  1186. var
  1187.   S : PathStr;
  1188.   F : Text;
  1189.   PageCols : Byte;
  1190.   PageV, PageH : Word;
  1191.   Finished, GoodFile, Error, Compress, Border, ESCPressed : Boolean;
  1192.   StartCol : Word;
  1193.   StartRow : Word;
  1194.  
  1195.   procedure WString(S : String);
  1196.   begin
  1197.     Writeln(F, S);
  1198.     if IOResult <> 0 then
  1199.     begin
  1200.       if S = PrinterName then
  1201.         Scr.PrintError(ErrPrinterError)
  1202.       else
  1203.         Scr.PrintError(ErrDiskFull);
  1204.       Error := True;
  1205.       Finished := True;
  1206.     end;
  1207.   end; { WString }
  1208.  
  1209.   function RowStartString(Row : Word) : String;
  1210.   begin
  1211.     if (PageH = 1) and Border then
  1212.       RowStartString := LeftJustStr(RowToString(Row), RowNumberSpace)
  1213.     else
  1214.       RowStartString := '';
  1215.   end; { RowStartString }
  1216.  
  1217.   procedure PrintPage;
  1218.   var
  1219.     Counter : Word;
  1220.     S : String;
  1221.     Color, Cols, Rows : Byte;
  1222.     P : CellPos;
  1223.   begin
  1224.     for Counter := 1 to PrintTopMargin do
  1225.     begin
  1226.       WString('');
  1227.       if Error then
  1228.         Exit;
  1229.     end;
  1230.     Rows := Min(PrintRows - PrintTopMargin - PrintBottomMargin,
  1231.                 Succ(MaxRows - StartRow));
  1232.     if Border then
  1233.       Dec(Rows);
  1234.     Cols := 0;
  1235.     Counter := Length(RowStartString(StartRow));
  1236.     while Counter <= PageCols do
  1237.     begin
  1238.       Inc(Counter, ColWidth(Cols + StartCol));
  1239.       Inc(Cols);
  1240.     end;
  1241.     Dec(Cols);
  1242.     Cols := Min(Cols, Succ(MaxCols - StartCol));
  1243.     if Border and (PageV = 1) then
  1244.     begin
  1245.       S := FillString(Length(RowStartString(StartRow)), ' ');
  1246.       for Counter := StartCol to Pred(StartCol + Cols) do
  1247.         S := S + CenterStr(ColToString(Counter), ColWidth(Counter));
  1248.       WString(S);
  1249.       if Error then
  1250.         Exit;
  1251.     end;
  1252.     for P.Row := StartRow to Pred(StartRow + Rows) do
  1253.     begin
  1254.       S := RowStartString(P.Row);
  1255.       for P.Col := StartCol to Pred(StartCol + Cols) do
  1256.         S := S + CellToFString(P, Color);
  1257.       WString(S);
  1258.       if Error then
  1259.         Exit;
  1260.     end;
  1261.     Inc(StartCol, Cols);
  1262.     if (StartCol > LastPos.Col) or (StartCol = 0) then
  1263.     begin
  1264.       Inc(StartRow, Rows);
  1265.       if (StartRow > LastPos.Row) or (StartRow = 0) then
  1266.         Finished := True
  1267.       else begin
  1268.         Inc(PageV);
  1269.         PageH := 1;
  1270.         StartCol := 1;
  1271.       end;
  1272.     end
  1273.     else
  1274.       Inc(PageH);
  1275.     Write(F, Chr(FF));
  1276.   end; { PrintPage }
  1277.  
  1278. begin { Spreadsheet.Print }
  1279.   repeat
  1280.     GoodFile := True;
  1281.     S := ReadString(PromptFilePrint, Pred(SizeOf(PathStr)), ESCPressed);
  1282.     if ESCPressed then
  1283.       Exit;
  1284.     if S = '' then
  1285.       S := PrinterName
  1286.     else begin
  1287.       if FileExists(S) then
  1288.       begin
  1289.         GoodFile := GetYesNo(PromptOverwriteFile, ESCPressed);
  1290.         if ESCPressed then
  1291.           Exit;
  1292.       end;
  1293.     end;
  1294.   until GoodFile;
  1295.   Compress := GetYesNo(PromptCompressPrint, ESCPressed);
  1296.   if ESCPressed then
  1297.     Exit;
  1298.   Border := GetYesNo(PromptBorderPrint, ESCPressed);
  1299.   if ESCPressed then
  1300.     Exit;
  1301.   Error := False;
  1302. {$I-}
  1303.   Assign(F, S);
  1304.   Rewrite(F);
  1305.   if IOResult <> 0 then
  1306.   begin
  1307.     Scr.PrintError(ErrNoOpen);
  1308.     Exit;
  1309.   end;
  1310.   if Compress then
  1311.   begin
  1312.     PageCols := PrintCompressedCols;
  1313.     Write(F, PrinterCompressChar);
  1314.   end
  1315.   else
  1316.     PageCols := PrintNormalCols;
  1317.   PageV := 1;
  1318.   PageH := 1;
  1319.   StartCol := 1;
  1320.   StartRow := 1;
  1321.   Finished := False;
  1322.   repeat
  1323.     PrintPage;
  1324.   until Finished;
  1325.   Close(F);
  1326. {$I+}
  1327. end; { Spreadsheet.Print }
  1328.  
  1329. procedure Spreadsheet.DeleteColumn;
  1330. { Deletes a column from the spreadsheet }
  1331. var
  1332.   C : Word;
  1333.   Start, Stop, P, OldPos, OldSPos : CellPos;
  1334.   Deleted : Boolean;
  1335.   OldName : PathStr;
  1336.   CP : CellPtr;
  1337.   H : HashItemPtr;
  1338.   B : Block;
  1339.   F : File;
  1340.   Good : Boolean;
  1341. begin
  1342.   C := GetColumn(PromptColumnDelete, MaxCols, ColSpace);
  1343.   if C = 0 then
  1344.     Exit;
  1345.   OldPos := CurrPos;
  1346.   OldSPos := ScreenBlock.Start;
  1347.   P.Col := C;
  1348.   Deleted := False;
  1349.   if P.Col <= LastPos.Col then
  1350.   begin
  1351.     with B do
  1352.     begin
  1353.       Start.Col := P.Col;
  1354.       Start.Row := 1;
  1355.       Stop.Col := P.Col;
  1356.       Stop.Row := LastPos.Row;
  1357.       Good := FormatHash.Delete(Start, Stop);
  1358.     end; { with }
  1359.     DeleteBlock(B, Deleted);
  1360.   end;
  1361.   Dec(LastPos.Col);
  1362.   WidthHash.Delete(C);
  1363.   with CellHash do
  1364.   begin
  1365.     CP := FirstItem;
  1366.     while CP <> nil do
  1367.     begin
  1368.       with CP^ do
  1369.       begin
  1370.         if Loc.Col > C then
  1371.           Dec(Loc.Col);
  1372.         if (CP^.ShouldUpdate) and (Loc.Col > C) then
  1373.           FixFormulaCol(CP, -1, MaxCols, MaxRows);
  1374.       end; { with }
  1375.       CP := NextItem;
  1376.     end;
  1377.   end; { with }
  1378.   with WidthHash do
  1379.   begin
  1380.     H := FirstItem;
  1381.     while H <> nil do
  1382.     begin
  1383.       if WordPtr(@H^.Data)^ > C then
  1384.         Dec(WordPtr(@H^.Data)^);
  1385.       H := NextItem;
  1386.     end;
  1387.   end; { with }
  1388.   with FormatHash do
  1389.   begin
  1390.     H := FirstItem;
  1391.     while H <> nil do
  1392.     begin
  1393.       Move(H^.Data, Start, SizeOf(Start));
  1394.       Move(H^.Data[SizeOf(CellPos)], Stop, SizeOf(Stop));
  1395.       if (Start.Col = C) and (Stop.Col = C) then
  1396.         Good := Delete(Start, Stop)
  1397.       else begin
  1398.         if Start.Col > C then
  1399.         begin
  1400.           Dec(Start.Col);
  1401.           Move(Start, H^.Data, SizeOf(Start));
  1402.         end;
  1403.         if Stop.Col > C then
  1404.         begin
  1405.           Dec(Stop.Col);
  1406.           Move(Stop, H^.Data[SizeOf(CellPos)], SizeOf(Stop));
  1407.         end;
  1408.       end;
  1409.       H := NextItem;
  1410.     end;
  1411.   end; { with }
  1412.   OldName := FileName;
  1413.   ToFile(TempFileName);
  1414.   Done;
  1415.   Good := FromFile(TempFileName);
  1416.   Assign(F, TempFileName);
  1417.   Erase(F);
  1418.   FileName := OldName;
  1419.   if Deleted then
  1420.     P.Row := LastPos.Row
  1421.   else
  1422.     P.Row := 1;
  1423.   Dec(P.Col);
  1424.   SetLastPos(P);
  1425.   MakeCurrent;
  1426.   SetChanged(WasChanged);
  1427.   CurrPos := OldPos;
  1428.   SetScreenColStart(OldSPos.Col);
  1429.   SetScreenRowStart(OldSPos.Row);
  1430.   Display;
  1431. end; { Spreadsheet.DeleteColumn }
  1432.  
  1433. procedure Spreadsheet.InsertColumn;
  1434. { Inserts a column into the spreadsheet }
  1435. var
  1436.   C : Word;
  1437.   Start, Stop, P, OldPos, OldSPos : CellPos;
  1438.   Deleted : Boolean;
  1439.   H : HashItemPtr;
  1440.   OldName : PathStr;
  1441.   CP : CellPtr;
  1442.   B : Block;
  1443.   F : File;
  1444.   Good : Boolean;
  1445. begin
  1446.   C := GetColumn(PromptColumnInsert, MaxCols, ColSpace);
  1447.   if C = 0 then
  1448.     Exit;
  1449.   OldPos := CurrPos;
  1450.   OldSPos := ScreenBlock.Start;
  1451.   Deleted := False;
  1452.   if LastPos.Col = MaxCols then
  1453.   begin
  1454.     with B do
  1455.     begin
  1456.       Start.Col := MaxCols;
  1457.       Start.Row := 1;
  1458.       Stop.Col := MaxCols;
  1459.       Stop.Row := LastPos.Row;
  1460.       Good := FormatHash.Delete(Start, Stop);
  1461.     end; { with }
  1462.     DeleteBlock(B, Deleted);
  1463.   end
  1464.   else
  1465.     Inc(LastPos.Col);
  1466.   P.Col := C;
  1467.   WidthHash.Delete(MaxCols);
  1468.   with CellHash do
  1469.   begin
  1470.     CP := FirstItem;
  1471.     while CP <> nil do
  1472.     begin
  1473.       with CP^ do
  1474.       begin
  1475.         if Loc.Col >= C then
  1476.           Inc(Loc.Col);
  1477.         if (CP^.ShouldUpdate) and (Loc.Col >= C) then
  1478.           FixFormulaCol(CP, 1, MaxCols, MaxRows);
  1479.       end; { with }
  1480.       CP := NextItem;
  1481.     end;
  1482.   end; { with }
  1483.   with WidthHash do
  1484.   begin
  1485.     H := FirstItem;
  1486.     while H <> nil do
  1487.     begin
  1488.       if WordPtr(@H^.Data)^ >= C then
  1489.         Inc(WordPtr(@H^.Data)^);
  1490.       H := NextItem;
  1491.     end;
  1492.   end; { with }
  1493.   with FormatHash do
  1494.   begin
  1495.     H := FirstItem;
  1496.     while H <> nil do
  1497.     begin
  1498.       Move(H^.Data, Start, SizeOf(Start));
  1499.       Move(H^.Data[SizeOf(CellPos)], Stop, SizeOf(Stop));
  1500.       if Start.Col >= C then
  1501.       begin
  1502.         Inc(Start.Col);
  1503.         Move(Start, H^.Data, SizeOf(Start));
  1504.       end;
  1505.       if Stop.Col >= C then
  1506.       begin
  1507.         Inc(Stop.Col);
  1508.         Move(Stop, H^.Data[SizeOf(CellPos)], SizeOf(Stop));
  1509.       end;
  1510.       H := NextItem;
  1511.     end;
  1512.   end; { with }
  1513.   OldName := FileName;
  1514.   ToFile(TempFileName);
  1515.   Done;
  1516.   Good := FromFile(TempFileName);
  1517.   Assign(F, TempFileName);
  1518.   Erase(F);
  1519.   FileName := OldName;
  1520.   if Deleted then
  1521.     P.Row := LastPos.Row
  1522.   else
  1523.     P.Row := 1;
  1524.   if LastPos.Col = MaxCols then
  1525.     P.Col := MaxCols
  1526.   else
  1527.     Inc(P.Col);
  1528.   SetLastPos(P);
  1529.   MakeCurrent;
  1530.   SetChanged(WasChanged);
  1531.   CurrPos := OldPos;
  1532.   SetScreenColStart(OldSPos.Col);
  1533.   SetScreenRowStart(OldSPos.Row);
  1534.   Display;
  1535. end; { Spreadsheet.InsertColumn }
  1536.  
  1537. procedure Spreadsheet.DeleteRow;
  1538. { Deletes a row from the spreadsheet }
  1539. var
  1540.   R : Word;
  1541.   Start, Stop, P, OldPos, OldSPos : CellPos;
  1542.   Deleted : Boolean;
  1543.   OldName : PathStr;
  1544.   CP : CellPtr;
  1545.   B : Block;
  1546.   F : File;
  1547.   Good : Boolean;
  1548.   H : HashItemPtr;
  1549. begin
  1550.   R := GetRow(PromptRowDelete, MaxRows);
  1551.   if (R = 0) or (R > LastPos.Row) then
  1552.     Exit;
  1553.   OldPos := CurrPos;
  1554.   OldSPos := ScreenBlock.Start;
  1555.   P.Row := R;
  1556.   if P.Row <= LastPos.Row then
  1557.   begin
  1558.     with B do
  1559.     begin
  1560.       Start.Col := 1;
  1561.       Start.Row := P.Row;
  1562.       Stop.Col := LastPos.Col;
  1563.       Stop.Row := P.Row;
  1564.       Good := FormatHash.Delete(Start, Stop);
  1565.     end; { with }
  1566.     DeleteBlock(B, Deleted);
  1567.   end;
  1568.   Dec(LastPos.Row);
  1569.   with CellHash do
  1570.   begin
  1571.     CP := FirstItem;
  1572.     while CP <> nil do
  1573.     begin
  1574.       with CP^ do
  1575.       begin
  1576.         if Loc.Row > R then
  1577.           Dec(Loc.Row);
  1578.         if (CP^.ShouldUpdate) and (Loc.Row > R) then
  1579.           FixFormulaRow(CP, -1, MaxCols, MaxRows);
  1580.       end; { with }
  1581.       CP := NextItem;
  1582.     end;
  1583.   end; { with }
  1584.   with FormatHash do
  1585.   begin
  1586.     H := FirstItem;
  1587.     while H <> nil do
  1588.     begin
  1589.       Move(H^.Data, Start, SizeOf(Start));
  1590.       Move(H^.Data[SizeOf(CellPos)], Stop, SizeOf(Stop));
  1591.       if (Start.Row = R) and (Stop.Row = R) then
  1592.         Good := Delete(Start, Stop)
  1593.       else begin
  1594.         if Start.Row > R then
  1595.         begin
  1596.           Dec(Start.Row);
  1597.           Move(Start, H^.Data, SizeOf(Start));
  1598.         end;
  1599.         if Stop.Row > R then
  1600.         begin
  1601.           Dec(Stop.Row);
  1602.           Move(Stop, H^.Data[SizeOf(CellPos)], SizeOf(Stop));
  1603.         end;
  1604.       end;
  1605.       H := NextItem;
  1606.     end;
  1607.   end; { with }
  1608.   OldName := FileName;
  1609.   ToFile(TempFileName);
  1610.   Done;
  1611.   Good := FromFile(TempFileName);
  1612.   Assign(F, TempFileName);
  1613.   Erase(F);
  1614.   FileName := OldName;
  1615.   if Deleted then
  1616.     P.Col := LastPos.Col
  1617.   else
  1618.     P.Col := 1;
  1619.   Dec(P.Row);
  1620.   SetLastPos(P);
  1621.   MakeCurrent;
  1622.   SetChanged(WasChanged);
  1623.   CurrPos := OldPos;
  1624.   SetScreenColStart(OldSPos.Col);
  1625.   SetScreenRowStart(OldSPos.Row);
  1626.   Display;
  1627. end; { Spreadsheet.DeleteRow }
  1628.  
  1629. procedure Spreadsheet.InsertRow;
  1630. { Inserts a row into the spreadsheet }
  1631. var
  1632.   R : Word;
  1633.   Start, Stop, P, OldPos, OldSPos : CellPos;
  1634.   Deleted : Boolean;
  1635.   OldName : PathStr;
  1636.   CP : CellPtr;
  1637.   B : Block;
  1638.   F : File;
  1639.   Good : Boolean;
  1640.   H : HashItemPtr;
  1641. begin
  1642.   R := GetRow(PromptRowInsert, MaxRows);
  1643.   if (R = 0) or (R > LastPos.Row) then
  1644.     Exit;
  1645.   OldPos := CurrPos;
  1646.   OldSPos := ScreenBlock.Start;
  1647.   if LastPos.Row = MaxRows then
  1648.   begin
  1649.     with B do
  1650.     begin
  1651.       Start.Col := 1;
  1652.       Start.Row := MaxRows;
  1653.       Stop.Col := LastPos.Col;
  1654.       Stop.Row := MaxRows;
  1655.       Good := FormatHash.Delete(Start, Stop);
  1656.     end; { with }
  1657.     DeleteBlock(B, Deleted);
  1658.   end
  1659.   else
  1660.     Inc(LastPos.Row);
  1661.   P.Row := R;
  1662.   with CellHash do
  1663.   begin
  1664.     CP := FirstItem;
  1665.     while CP <> nil do
  1666.     begin
  1667.       with CP^ do
  1668.       begin
  1669.         if Loc.Row >= R then
  1670.           Inc(Loc.Row);
  1671.         if (CP^.ShouldUpdate) and (Loc.Row >= R) then
  1672.           FixFormulaRow(CP, 1, MaxCols, MaxRows);
  1673.       end; { with }
  1674.       CP := NextItem;
  1675.     end;
  1676.   end; { with }
  1677.   with FormatHash do
  1678.   begin
  1679.     H := FirstItem;
  1680.     while H <> nil do
  1681.     begin
  1682.       Move(H^.Data, Start, SizeOf(Start));
  1683.       Move(H^.Data[SizeOf(CellPos)], Stop, SizeOf(Stop));
  1684.       if Start.Row >= R then
  1685.       begin
  1686.         Inc(Start.Row);
  1687.         Move(Start, H^.Data, SizeOf(Start));
  1688.       end;
  1689.       if Stop.Row >= R then
  1690.       begin
  1691.         Inc(Stop.Row);
  1692.         Move(Stop, H^.Data[SizeOf(CellPos)], SizeOf(Stop));
  1693.       end;
  1694.       H := NextItem;
  1695.     end;
  1696.   end; { with }
  1697.   OldName := FileName;
  1698.   ToFile(TempFileName);
  1699.   Done;
  1700.   Good := FromFile(TempFileName);
  1701.   Assign(F, TempFileName);
  1702.   Erase(F);
  1703.   FileName := OldName;
  1704.   if Deleted then
  1705.     P.Col := LastPos.Col
  1706.   else
  1707.     P.Col := 1;
  1708.   if LastPos.Row = MaxRows then
  1709.     P.Row := MaxRows
  1710.   else
  1711.     Inc(P.Row);
  1712.   SetLastPos(P);
  1713.   MakeCurrent;
  1714.   SetChanged(WasChanged);
  1715.   CurrPos := OldPos;
  1716.   SetScreenColStart(OldSPos.Col);
  1717.   SetScreenRowStart(OldSPos.Row);
  1718.   Display;
  1719. end; { Spreadsheet.InsertRow }
  1720.  
  1721. end.
  1722.