{ Copyright (c) 1989 by Borland International, Inc. } unit TCSheet; { Turbo Pascal 5.5 object-oriented example spreadsheet routines. This unit is used by TCALC.PAS. See TCALC.DOC for an more information about this example. } {$N+,S-} interface uses Crt, Dos, Objects, TCUtil, TCInput, TCScreen, TCLStr, TCHash, TCCell, TCCellSp, TCParser; const DefaultMaxCols = 65535; DefaultMaxRows = 65535; DefaultMaxDecimalPlaces = 8; DefaultDefaultDecimalPlaces = 4; DefaultDefaultColWidth = 10; EmptyRowsAtTop = 1; EmptyRowsAtBottom = 2; MinColWidth = 3; CurrentChar = #4; ChangedChar = '*'; PrintNormalCols = 80; PrintCompressedCols = 132; PrintRows = 66; PrintTopMargin = 1; PrintBottomMargin = 1; PrinterCompressChar = #15; EditYes = True; EditNo = False; DisplayYes = True; DisplayNo = False; WasChanged = True; NotChanged = False; AutoCalcLetter = 'A'; FormulaDisplayLetter = 'F'; MemoryString = 'Memory: '; FileHeader = 'TurboCalc Spreadsheet'^Z; ErrorString = 'ERROR'; TempFileName = 'TEMP.TMP'; { Temporary file used for rehashing } PrinterName = 'PRN'; PromptFileSave = 'File to save'; PromptFilePrint = 'File to print to (ENTER = Printer)'; PromptOverwriteFile = 'The file exists. Overwrite it'; PromptCompressPrint = 'Compress the printing'; PromptBorderPrint = 'Print the borders'; PromptColumnWidth = 'Column to change'; PromptNewWidth = 'New width'; PromptColumnDelete = 'Column to delete'; PromptColumnInsert = 'Insert new column before column'; PromptRowDelete = 'Row to delete'; PromptRowInsert = 'Insert new row before row'; PromptSaveYN = 'Save spreadsheet'; ErrNoOpen = 'Cannot open file'; ErrDiskFull = 'Disk full'; ErrPrinterError = 'Printer error'; ErrNotSpreadsheet = 'Not a TurboCalc spreadsheet file'; MsgRecalc = 'Recalculating cell values'; MsgSave = 'Saving spreadsheet'; MsgLoad = 'Loading spreadsheet'; MsgBlockDelete = 'Deleting block'; type ColStartArray = array[0..ScreenCols] of Byte; ColStartPtr = ^ColStartArray; SpreadsheetPtr = ^Spreadsheet; Spreadsheet = object Number : Byte; MaxRows : Word; MaxCols : Word; MaxDecimalPlaces : Byte; MaxColWidth : Byte; MaxScreenCols : Byte; DefaultColWidth : Byte; DefaultDecimalPlaces : Byte; RowNumberSpace : Byte; ColSpace : Byte; Current : Boolean; Changed : Boolean; CurrPos : CellPos; LastPos : CellPos; ScreenBlock : Block; CurrBlock : Block; BlockOn : Boolean; FileName : PathStr; TotalRows : ScreenRowRange; DisplayArea : ScreenArea; ColArea : ScreenArea; RowArea : ScreenArea; InfoArea : ScreenArea; DataArea : ScreenArea; ContentsArea : ScreenArea; BlankArea : ScreenArea; NoBlankArea : Boolean; ColStart : ColStartPtr; DisplayFormulas : Boolean; AutoCalc : Boolean; CellHash : CellHashTable; OverwriteHash : OverwriteHashTable; WidthHash : WidthHashTable; FormatHash : FormatHashTable; Next : SpreadsheetPtr; constructor Init(InitCells : Longint; InitMaxCols, InitMaxRows : Word; InitMaxDecimalPlaces, InitDefaultDecimalPlaces : Byte; InitDefaultColWidth : Byte); destructor Done; function GetColStart(Col : Word) : Byte; procedure SetAreas(NewNumber : Word; X1 : ScreenColRange; Y1 : ScreenRowRange; X2 : ScreenColRange; Y2 : ScreenRowRange); procedure DisplayCols; procedure DisplayRows; procedure DisplayInfo; procedure DisplayAllCells; procedure Display; procedure DisplayCell(P : CellPos); procedure DisplayCellData; procedure DisplayCellBlock(C1 : Word; R1 : Word; C2 : Word; R2 : Word); procedure DisplayBlock(B : Block); procedure DisplayBlockDiff(B1, B2 : Block); procedure DisplayCol(Col : Word); procedure DisplayRow(Row : Word); procedure DisplayMemory; procedure DisplayFileName; procedure SetChanged(IsChanged : Boolean); procedure MakeCurrent; procedure MakeNotCurrent; procedure Update(UDisplay : Boolean); procedure ToggleFormulaDisplay; procedure SetScreenColStart(NewCol : Word); procedure SetScreenColStop(NewCol: Word); procedure SetScreenRowStart(NewRow : Word); procedure SetScreenRowStop(NewRow : Word); procedure FindScreenColStart; procedure FindScreenColStop; procedure FindScreenRowStart; procedure FindScreenRowStop; procedure SetBlankArea; function AddCell(CellType : CellTypes; P : CellPos; E : Boolean; V : Extended; I : LStringPtr) : Boolean; procedure DeleteCell(P : CellPos; var Deleted : Boolean); procedure DeleteBlock(B : Block; var Deleted : Boolean); function CellToFString(P : CellPos; var Color : Byte) : String; procedure SetLastPos(DPos : CellPos); function GetCurrCol : Word; function GetCurrRow : Word; function ColToX(Col : Word) : Byte; function RowToY(Row : Word) : Byte; function ColWidth(Col : Word) : Byte; function SameCellPos(P1, P2 : CellPos) : Boolean; procedure FixOverwrite; function FromFile(Name : PathStr) : Boolean; procedure ToFile(Name : PathStr); procedure CheckForSave; procedure ChangeWidth; function CellHashStart(TotalCells : Longint) : BucketRange; function WidthHashStart(TotalCells : Longint) : BucketRange; function OverwriteHashStart(TotalCells : Longint) : BucketRange; procedure Print; procedure DeleteColumn; procedure InsertColumn; procedure DeleteRow; procedure InsertRow; end; function GetColWidth(var WHash : WidthHashTable; C : Word) : Byte; implementation function GetColWidth(var WHash : WidthHashTable; C : Word) : Byte; { Returns the width of a column } var W : Word; begin W := WHash.Search(C); if W = 0 then GetColWidth := WHash.GetDefaultColWidth else GetColWidth := W; end; { GetColWidth } constructor Spreadsheet.Init(InitCells : Longint; InitMaxCols, InitMaxRows : Word; InitMaxDecimalPlaces, InitDefaultDecimalPlaces : Byte; InitDefaultColWidth : Byte); { Sets up a new spreadsheet } begin if not CellHash.Init(CellHashStart(InitCells)) then Fail; if not WidthHash.Init(WidthHashStart(InitCells), InitDefaultColWidth) then begin CellHash.Done; Fail; end; if not OverwriteHash.Init(OverwriteHashStart(InitCells)) then begin CellHash.Done; WidthHash.Done; Fail; end; if not FormatHash.Init then begin CellHash.Done; WidthHash.Done; OverwriteHash.Done; Fail; end; MaxCols := InitMaxCols; MaxRows := InitMaxRows; RowNumberSpace := Ord(MaxRows >= 10000) + Ord(MaxRows >= 1000) + Ord(MaxRows >= 100) + Ord(MaxRows >= 10) + 2; MaxColWidth := ScreenCols - RowNumberSpace; MaxScreenCols := MaxColWidth div MinColWidth; GetMem(ColStart, MaxScreenCols); if ColStart = nil then begin CellHash.Done; WidthHash.Done; OverwriteHash.Done; FormatHash.Done; Fail; end; CurrPos.Col := 1; CurrPos.Row := 1; LastPos := CurrPos; BlockOn := False; FileName := ''; DisplayFormulas := False; AutoCalc := False; Current := False; Changed := False; ScreenBlock.Start.Col := 1; ScreenBlock.Start.Row := 1; ColSpace := Succ(Ord(MaxCols >= 18279) + Ord(MaxCols >= 703) + Ord(MaxCols >= 27)); MaxDecimalPlaces := InitMaxDecimalPlaces; DefaultColWidth := InitDefaultColWidth; DefaultDecimalPlaces := InitDefaultDecimalPlaces; end; { Spreadsheet.Init } destructor Spreadsheet.Done; { Removes a spreadsheet from memory } begin CellHash.Done; WidthHash.Done; OverwriteHash.Done; FormatHash.Done; FreeMem(ColStart, MaxScreenCols); end; { Spreadsheet.Done } function Spreadsheet.GetColStart(Col : Word) : Byte; begin GetColStart := ColStart^[Col]; end; { Spreadsheet.GetColStart } procedure Spreadsheet.SetAreas(NewNumber : Word; X1 : ScreenColRange; Y1 : ScreenRowRange; X2 : ScreenColRange; Y2 : ScreenRowRange); { Sets up a spreadsheet's display areas } begin Number := NewNumber; TotalRows := Y2 - Y1 - 2; ColArea.Init(X1 + RowNumberSpace, Y1, X2, Y1, Colors.ColColor); RowArea.Init(X1, Succ(Y1), Pred(X1 + RowNumberSpace), Y2 - 2, Colors.RowColor); InfoArea.Init(X1, Y1, Pred(X1 + RowNumberSpace), Y1, Colors.InfoColor); DisplayArea.Init(X1 + RowNumberSpace, Succ(Y1), X2, Y2 - 2, Colors.BlankColor); DataArea.Init(X1, Pred(Y2), X2, Pred(Y2), Colors.BlankColor); ContentsArea.Init(X1, Y2, X2, Y2, Colors.ContentsColor); SetScreenColStart(ScreenBlock.Start.Col); SetScreenRowStart(ScreenBlock.Start.Row); SetBlankArea; end; { Spreadsheet.SetAreas } procedure Spreadsheet.DisplayCols; { Shows the column headings } var C : Word; begin ColArea.Clear; with ScreenBlock do begin for C := Start.Col to Stop.Col do WriteXY(CenterStr(ColToString(C), ColWidth(C)), ColStart^[C - Start.Col], ColArea.UpperLeft.Row, Colors.ColColor); end; { with } end; { Spreadsheet.DisplayCols } procedure Spreadsheet.DisplayRows; { Shows the row headings } var R : Word; begin RowArea.Clear; with ScreenBlock do begin for R := Start.Row to Stop.Row do with RowArea do WriteXY(LeftJustStr(RowToString(R), RowNumberSpace), UpperLeft.Col, R - Start.Row + UpperLeft.Row, Colors.RowColor); end; { with } end; { Spreadsheet.DisplayRows } procedure Spreadsheet.DisplayInfo; { Shows the spreadsheet number, current dot, and state of AutoCalc and formula display } begin InfoArea.Clear; with InfoArea do WriteXY(NumToString(Number), UpperLeft.Col, UpperLeft.Row, Colors.InfoColor); if Current then Write(CurrentChar) else Write(' '); if AutoCalc then Write(AutoCalcLetter) else Write(' '); if DisplayFormulas then Write(FormulaDisplayLetter) else Write(' '); end; { Spreadsheet.DisplayRows } procedure Spreadsheet.DisplayAllCells; { Displays all of the cells on the screen } begin DisplayArea.Clear; DisplayBlock(ScreenBlock); end; { Spreadsheet.DisplayAllCells } procedure Spreadsheet.DisplayCell(P : CellPos); { Displays a single cell } var S : String[ScreenCols]; Color : Byte; begin S := CellToFString(P, Color); WriteXY(S, ColToX(P.Col), RowToY(P.Row), Color); end; { Spreadsheet.DisplayCell } procedure Spreadsheet.DisplayCellData; { Displays information about a cell - its type and its contents } var CP : CellPtr; begin CP := CellHash.Search(CurrPos); with DataArea do WriteXY(LeftJustStr(ColToString(CurrPos.Col) + RowToString(CurrPos.Row) + ' ' + CP^.Name, 19), UpperLeft.Col, UpperLeft.Row, Colors.CellDataColor); with ContentsArea do begin Clear; WriteXY(LeftJustStr(CP^.DisplayString(DisplayFormulas, MaxDecimalPlaces), Scr.CurrCols), UpperLeft.Col, UpperLeft.Row, Colors.ContentsColor); end; { with } end; { Spreadsheet.DisplayCellData } procedure Spreadsheet.DisplayCellBlock(C1 : Word; R1 : Word; C2 : Word; R2 : Word); { Displays all cells within a range of rows and columns } var P : CellPos; begin with ScreenBlock do begin for P.Row := Max(R1, Start.Row) to Min(R2, Stop.Row) do begin for P.Col := Max(C1, Start.Col) to Min(C2, Stop.Col) do DisplayCell(P); end; end; { with } end; { Spreadsheet.DisplayCellBlock } procedure Spreadsheet.DisplayBlock(B : Block); { Displays all cells within a certain block } begin with B do DisplayCellBlock(Start.Col, Start.Row, Stop.Col, Stop.Row); end; { Spreadsheet.DisplayBlock } procedure Spreadsheet.DisplayBlockDiff(B1, B2 : Block); { When a block is extended, this will update the screen to show the new block } var B : Block; DisplayMiddle : Boolean; begin if Compare(B1, B2, SizeOf(Block)) then Exit; with B do begin DisplayMiddle := False; if B1.Stop.Col <> B2.Stop.Col then begin B.Start.Row := B1.Start.Row; B.Start.Col := Min(Succ(B1.Stop.Col), Succ(B2.Stop.Col)); B.Stop.Row := Min(B1.Stop.Row, B2.Stop.Row); B.Stop.Col := Max(B1.Stop.Col, B2.Stop.Col); DisplayBlock(B); DisplayMiddle := True; end; if B1.Stop.Row <> B2.Stop.Row then begin B.Start.Row := Min(Succ(B1.Stop.Row), Succ(B2.Stop.Row)); B.Start.Col := B1.Start.Col; B.Stop.Row := Max(B1.Stop.Row, B2.Stop.Row); B.Stop.Col := Min(B1.Stop.Col, B2.Stop.Col); DisplayBlock(B); DisplayMiddle := True; end; if DisplayMiddle then begin B.Start.Row := Min(Succ(B1.Stop.Row), Succ(B2.Stop.Row)); B.Start.Col := Min(Succ(B1.Stop.Col), Succ(B2.Stop.Col)); B.Stop.Row := Max(B1.Stop.Row, B2.Stop.Row); B.Stop.Col := Max(B1.Stop.Col, B2.Stop.Col); DisplayBlock(B); end; end; { with } end; { Spreadsheet.DisplayBlockDiff } procedure Spreadsheet.DisplayCol(Col : Word); { Display a column of cells } begin with ScreenBlock do DisplayCellBlock(Col, Start.Row, Col, Stop.Row); end; { Spreadsheet.DisplayCol } procedure Spreadsheet.DisplayRow(Row : Word); { Display a row of cells } begin with ScreenBlock do DisplayCellBlock(Start.Col, Row, Stop.Col, Row); end; { Spreadsheet.DisplayRow } procedure Spreadsheet.DisplayMemory; { Display the amount of free memory } begin WriteXY(RightJustStr(NumToString(MemAvail), 6), Scr.CurrCols - 5, 1, Colors.MemoryColor); end; { Spreadsheet.DisplayMemory } procedure Spreadsheet.DisplayFileName; { Display the spreadsheet's file name, and whether or not it has been updated } var S : PathStr; begin with DataArea do begin if FileName = '' then S := 'No file' else S := FExpand(FileName); WriteXY(LeftJustStr(S, LowerRight.Col - UpperLeft.Col - 20), UpperLeft.Col + 21, UpperLeft.Row, Colors.FileNameColor); end; { with } end; { Spreadsheet.DisplayFileName } procedure Spreadsheet.Display; { Display the entire spreadsheet } begin DisplayCols; DisplayRows; DisplayInfo; DisplayAllCells; DisplayMemory; DisplayCellData; DisplayFileName; SetChanged(Changed); end; { Spreadsheet.Display } procedure Spreadsheet.SetChanged(IsChanged : Boolean); { Sets a spreadsheet as being changed or not changed } var C : Char; begin Changed := IsChanged; if Changed then C := ChangedChar else C := ' '; with DataArea.UpperLeft do WriteXY(C, Col + 19, Row, Colors.ChangedColor); end; { Spreadsheet.SetChanged } procedure Spreadsheet.MakeCurrent; { Make a spreadsheet the current one } begin Current := True; DisplayInfo; end; { Spreadsheet.MakeCurrent } procedure Spreadsheet.MakeNotCurrent; { Make a spreadsheet not the current one } begin Current := False; DisplayInfo; end; { Spreadsheet.MakeNotCurrent } procedure Spreadsheet.Update(UDisplay : Boolean); { Update any cells in the spreadsheet that need updating } var P, U : CellPos; CP : CellPtr; O : Word; begin Scr.PrintMessage(MsgRecalc); with CellHash do begin for P.Row := 1 to LastPos.Row do begin for P.Col := 1 to LastPos.Col do begin CP := Search(P); if CP^.ShouldUpdate then begin with FormulaCellPtr(CP)^ do begin Parser.Init(@CellHash, Formula, MaxCols, MaxRows); Parser.Parse; Value := Parser.ParseValue; Error := Parser.ParseError; O := CP^.Overwritten(CellHash, FormatHash, WidthHash, LastPos, MaxCols, GetColWidth, DisplayFormulas); if (OverwriteHash.Change(CP, O)) and UDisplay and (CP^.Loc.Col + O >= ScreenBlock.Start.Col) then begin U := CP^.Loc; for U.Col := CP^.Loc.Col to ScreenBlock.Stop.Col do begin if ScreenBlock.CellInBlock(U) then DisplayCell(U); end; end; end; { with } end; end; end; end; { with } if UDisplay then DisplayMemory; Scr.ClearMessage; end; { Spreadsheet.Update } procedure Spreadsheet.ToggleFormulaDisplay; { Change from showing formulas to showing values and vice versa } var CP : CellPtr; OChanged : Boolean; begin DisplayFormulas := not DisplayFormulas; DisplayInfo; OChanged := True; with CellHash do begin CP := FirstItem; while (CP <> nil) and OChanged do begin if CP^.ShouldUpdate then OChanged := OverwriteHash.Change(CP, CP^.Overwritten(CellHash, FormatHash, WidthHash, LastPos, MaxCols, GetColWidth, DisplayFormulas)); CP := NextItem; end; end; { with } DisplayAllCells; DisplayMemory; end; { Spreadsheet.ToggleFormulaDisplay } procedure Spreadsheet.SetScreenColStart(NewCol : Word); { Find the starting screen column } begin ScreenBlock.Start.Col := NewCol; FindScreenColStop; FindScreenColStart; end; { Spreadsheet.SetScreenColStart } procedure Spreadsheet.SetScreenColStop(NewCol : Word); { Find the ending screen column } begin ScreenBlock.Stop.Col := NewCol; FindScreenColStart; FindScreenColStop; end; { Spreadsheet.SetScreenColStop } procedure Spreadsheet.SetScreenRowStart(NewRow : Word); { Find the starting screen row } begin ScreenBlock.Start.Row := NewRow; FindScreenRowStop; end; { Spreadsheet.SetScreenRowStart } procedure Spreadsheet.SetScreenRowStop(NewRow : Word); { Find the ending screen row } begin ScreenBlock.Stop.Row := NewRow; FindScreenRowStart; end; { Spreadsheet.SetScreenRowStop } procedure Spreadsheet.FindScreenColStart; { Find the starting screen column when the ending column is known } var Index, Place : Integer; Temp, Width : Byte; begin with ScreenBlock do begin Index := 0; Place := Succ(DisplayArea.LowerRight.Col); Width := ColWidth(Stop.Col); repeat ColStart^[Index] := Place - Width; Dec(Place, Width); Inc(Index); if Stop.Col - Index = 0 then Width := 0 else Width := ColWidth(Stop.Col - Index); until (Width = 0) or (Place - Width < DisplayArea.UpperLeft.Col); Start.Col := Succ(Stop.Col - Index); Dec(Index); if ColStart^[Index] <> DisplayArea.UpperLeft.Col then begin Temp := ColStart^[Index] - DisplayArea.UpperLeft.Col; for Place := 0 to Index do Dec(ColStart^[Place], Temp); end; if Index > 0 then begin for Place := 0 to (Pred(Index) shr 1) do begin Temp := ColStart^[Index - Place]; ColStart^[Index - Place] := ColStart^[Place]; ColStart^[Place] := Temp; end; end; end; { with } end; { Spreadsheet.FindScreenColStart } procedure Spreadsheet.FindScreenColStop; { Find the ending screen column when the starting column is known } var Index, Place : Byte; Width : Byte; begin with ScreenBlock do begin Index := 0; Place := DisplayArea.UpperLeft.Col; Width := ColWidth(Start.Col); repeat ColStart^[Index] := Place; Inc(Place, Width); Inc(Index); if Longint(Index) + Start.Col > MaxCols then Width := 0 else Width := ColWidth(Index + Start.Col); until (Width = 0) or (Place + Width > Succ(DisplayArea.LowerRight.Col)); Stop.Col := Pred(Start.Col + Index); end; { with } end; { Spreadsheet.FindScreenColStop } procedure Spreadsheet.FindScreenRowStart; { Find the starting screen row when the ending row is known } begin with ScreenBlock do begin if Longint(Stop.Row) - TotalRows < 0 then begin Start.Row := 1; FindScreenRowStop; end else Start.Row := Succ(Stop.Row - TotalRows); end; { with } end; { Spreadsheet.FindScreenRowStart } procedure Spreadsheet.FindScreenRowStop; { Find the ending screen row when the starting row is known } begin with ScreenBlock do begin if Longint(Start.Row) + TotalRows > Succ(LongInt(MaxRows)) then begin Stop.Row := MaxRows; FindScreenRowStart; end else Stop.Row := Pred(Start.Row + TotalRows); end; { with } end; { Spreadsheet.FindScreenRowStop } procedure Spreadsheet.SetBlankArea; { Find the size of the blank area (the area at the right edge of the spreadsheet that is not used } var C : Word; begin with BlankArea do begin Move(DisplayArea, BlankArea, SizeOf(DisplayArea)); with ScreenBlock do C := ColStart^[Stop.Col - Start.Col] + ColWidth(Stop.Col); if C > DisplayArea.LowerRight.Col then NoBlankArea := True else begin NoBlankArea := False; UpperLeft.Col := C; end; end; { with } end; { Spreadsheet.SetBlankArea } function Spreadsheet.AddCell(CellType : CellTypes; P : CellPos; E : Boolean; V : Extended; I : LStringPtr) : Boolean; { Add a new cell to the spreadsheet } var CP, S : CellPtr; OldLastPos : CellPos; Good : Boolean; begin AddCell := False; case CellType of ClValue : CP := New(ValueCellPtr, Init(P, E, V)); ClFormula : CP := New(FormulaCellPtr, Init(P, E, V, I)); ClText : CP := New(TextCellPtr, Init(P, I)); ClRepeat : CP := New(RepeatCellPtr, Init(P, I^.Data^[2])); end; { case } if CP = nil then Exit; if not CellHash.Add(CP) then begin Dispose(CP, Done); Exit; end; OldLastPos := LastPos; LastPos.Col := Max(P.Col, LastPos.Col); LastPos.Row := Max(P.Row, LastPos.Row); if not OverwriteHash.Add(CP, CP^.Overwritten(CellHash, FormatHash, WidthHash, LastPos, MaxCols, GetColWidth, DisplayFormulas)) then begin LastPos := OldLastPos; CellHash.Delete(CP^.Loc, S); Dispose(CP, Done); Exit; end; S := OverwriteHash.Search(CP^.Loc); if S <> Empty then Good := OverwriteHash.Change(S, S^.Overwritten(CellHash, FormatHash, WidthHash, LastPos, MaxCols, GetColWidth, DisplayFormulas)); AddCell := True; end; { Spreadsheet.AddCell } procedure Spreadsheet.DeleteCell(P : CellPos; var Deleted : Boolean); { Delete a cell from the spreadsheet } var CP : CellPtr; Good : Boolean; begin CellHash.Delete(P, CP); if CP <> nil then begin Dispose(CP, Done); OverwriteHash.Delete(P); if P.Col > 1 then begin Dec(P.Col); CP := OverwriteHash.Search(P); if CP = Empty then CP := CellHash.Search(P); if CP <> Empty then Good := OverwriteHash.Change(CP, CP^.Overwritten(CellHash, FormatHash, WidthHash, LastPos, MaxCols, GetColWidth, DisplayFormulas)); end; Deleted := True; end else Deleted := False; end; { Spreadsheet.DeleteCell } procedure Spreadsheet.DeleteBlock(B : Block; var Deleted : Boolean); { Delete a block of cells from the spreadsheet } var P : CellPos; H, D : HashItemPtr; Counter : Word; CP : CellPtr; begin Scr.PrintMessage(MsgBlockDelete); Deleted := False; with CellHash, B do begin for Counter := 1 to Buckets do begin H := HashData^[Counter]; while H <> nil do begin D := H; H := H^.Next; Move(D^.Data, CP, SizeOf(CP)); with CP^ do begin if CellInBlock(Loc) then DeleteCell(Loc, Deleted); end; { with } end; end; end; { with } Scr.ClearMessage; end; { DeleteBlock } function Spreadsheet.CellToFString(P : CellPos; var Color : Byte) : String; { Create a formatted string from a cell } var CP : CellPtr; S : String; S1 : DollarStr; F : FormatType; ColorFound : Boolean; Colr : Byte; begin ColorFound := True; if Current and (SameCellPos(P, CurrPos)) then Color := Colors.HighlightColor else if BlockOn and (CurrBlock.CellInBlock(P)) then Color := Colors.BlockColor else ColorFound := False; CP := CellHash.Search(P); if (CP^.HasError) then begin S := ErrorString; S1 := ''; if ColorFound then Inc(Color, Blink) else Color := Colors.CellErrorColor; F := Ord(JCenter) shl JustShift; end else begin S := CP^.FormattedString(OverwriteHash, FormatHash, WidthHash, GetColWidth, P, DisplayFormulas, 1, ColWidth(P.Col), S1, Colr); if not ColorFound then Color := Colr; F := CP^.Format(FormatHash, DisplayFormulas); end; case Justification((F shr JustShift) and JustPart) of JLeft : CellToFString := S1 + LeftJustStr(S, ColWidth(P.Col) - Length(S1)); JCenter : CellToFString := S1 + CenterStr(S, ColWidth(P.Col) - Length(S1)); JRight : CellToFString := S1 + RightJustStr(S, ColWidth(P.Col) - Length(S1)); end; { case } end; { Spreadsheet.CellToFString } procedure Spreadsheet.SetLastPos(DPos : CellPos); { Find the last position used in a spreadsheet } var CP : CellPtr; Counter : Word; ColFound, RowFound : Boolean; begin with CellHash do begin ColFound := DPos.Col < LastPos.Col; RowFound := DPos.Row < LastPos.Row; if (not ColFound) or (not RowFound) then begin if not ColFound then LastPos.Col := 1; if not RowFound then LastPos.Row := 1; CP := FirstItem; while CP <> nil do begin if not ColFound then begin if CP^.Loc.Col > LastPos.Col then begin LastPos.Col := CP^.Loc.Col; ColFound := LastPos.Col = DPos.Col; if ColFound and RowFound then Exit; end; end; if not RowFound then begin if CP^.Loc.Row > LastPos.Row then begin LastPos.Row := CP^.Loc.Row; RowFound := LastPos.Row = DPos.Row; if ColFound and RowFound then Exit; end; end; CP := NextItem; end; end; end; { with } end; { Spreadsheet.SetLastPos } function Spreadsheet.GetCurrCol : Word; { Find the current column } begin GetCurrCol := CurrPos.Col; end; { Spreadsheet.GetCurrCol } function Spreadsheet.GetCurrRow : Word; { Find the current row } begin GetCurrRow := CurrPos.Row; end; { Spreadsheet.GetCurrRow } function Spreadsheet.ColToX(Col : Word) : Byte; { Find where on the screen a column starts } begin ColToX := ColStart^[Col - ScreenBlock.Start.Col]; end; { Spreadsheet.ColToX } function Spreadsheet.RowToY(Row : Word) : Byte; { Find where on the screen a row starts } begin RowToY := Row + DisplayArea.UpperLeft.Row - ScreenBlock.Start.Row; end; { Spreadsheet.RowToY } {$F+} function Spreadsheet.ColWidth(Col : Word) : Byte; { Returns the width of a column } var Width : Word; begin Width := WidthHash.Search(Col); if Width = 0 then ColWidth := DefaultColWidth else ColWidth := Width; end; { Spreadsheet.ColWidth } {$F-} function Spreadsheet.SameCellPos(P1, P2 : CellPos) : Boolean; { Returns True if two cells are at the same position } begin SameCellPos := Compare(P1, P2, SizeOf(CellPos)); end; { Spreadsheet.SameCellPos } procedure Spreadsheet.FixOverwrite; { Fixes the overwrite hash table when the formats have been changed } var CP, D : CellPtr; Counter : Word; Good : Boolean; begin with CellHash do begin CP := FirstItem; while CP <> nil do begin if not OverwriteHash.Add(CP, CP^.Overwritten(CellHash, FormatHash, WidthHash, LastPos, MaxCols, GetColWidth, DisplayFormulas)) then begin CellHash.Delete(CP^.Loc, D); Dispose(CP, Done); Exit; end; CP := OverwriteHash.Search(CP^.Loc); if CP <> Empty then Good := OverwriteHash.Change(CP, CP^.Overwritten(CellHash, FormatHash, WidthHash, LastPos, MaxCols, GetColWidth, DisplayFormulas)); CP := NextItem; end; end; { with } end; { Spreadsheet.FixOverwrite } function Spreadsheet.FromFile(Name : PathStr) : Boolean; { Reads a spreadsheet from disk } var Header : String[Length(FileHeader)]; TotalC : Longint; TotalW : Word; TotalF : Longint; S : SSStream; NewLastPos : CellPos; begin FromFile := True; Name := UpperCase(Name); S.Init(Name, SOpen); if S.Status <> 0 then begin Scr.PrintError(ErrNoOpen); Init(0, DefaultMaxCols, DefaultMaxRows, DefaultMaxDecimalPlaces, DefaultDefaultDecimalPlaces, DefaultDefaultColWidth); Exit; end else begin Header[0] := Chr(Length(FileHeader)); S.Read(Header[1], Length(FileHeader)); if (S.Status <> 0) or (Header <> FileHeader) then begin Scr.PrintError(ErrNotSpreadsheet); S.Done; Init(0, DefaultMaxCols, DefaultMaxRows, DefaultMaxDecimalPlaces, DefaultDefaultDecimalPlaces, DefaultDefaultColWidth); Exit; end; FileName := Name; S.Read(NewLastPos, SizeOf(NewLastPos)); S.Read(TotalW, SizeOf(TotalW)); S.Read(TotalF, SizeOf(TotalF)); S.Read(TotalC, SizeOf(TotalC)); if not Init(TotalC, DefaultMaxCols, DefaultMaxRows, DefaultMaxDecimalPlaces, DefaultDefaultDecimalPlaces, DefaultDefaultColWidth) then begin S.Done; FromFile := False; Exit; end; LastPos := NewLastPos; Scr.PrintMessage(MsgLoad); FileName := Name; WidthHash.Load(S, TotalW); FormatHash.Load(S, TotalF); CellHash.Load(S, TotalC); S.Done; FixOverwrite; Update(DisplayNo); Scr.ClearMessage; end; FromFile := True; end; { Spreadsheet.FromFile } procedure Spreadsheet.ToFile(Name : PathStr); { Writes a spreadsheet to disk } var Header : String[Length(FileHeader)]; S : SSStream; begin S.Init(Name, SCreate); if S.Status <> 0 then begin Scr.PrintError(ErrNoOpen); Exit; end; Scr.PrintMessage(MsgSave); FileName := Name; Header := FileHeader; S.Write(Header[1], Length(Header)); S.Write(LastPos, SizeOf(LastPos)); S.Write(WidthHash.Items, 2); S.Write(FormatHash.Items, SizeOf(FormatHash.Items)); S.Write(CellHash.Items, SizeOf(CellHash.Items)); WidthHash.Store(S); FormatHash.Store(S); CellHash.Store(S); Scr.ClearMessage; S.Done; if S.Status <> 0 then Scr.PrintError(ErrDiskFull) else SetChanged(NotChanged); end; { Spreadsheet.ToFile } procedure Spreadsheet.CheckForSave; { Before prompting for a file name, this will check to see if you want to save the spreadsheet } var S : PathStr; GoodFile, ESCPressed : Boolean; begin if Changed and (GetYesNo(PromptSaveYN, ESCPressed)) then begin S := FileName; repeat GoodFile := True; if S = '' then begin S := ReadString(PromptFileSave, Pred(SizeOf(PathStr)), ESCPressed); if S = '' then Exit; end; if FileExists(S) then begin GoodFile := GetYesNo(PromptOverwriteFile, ESCPressed); if ESCPressed then Exit; if not GoodFile then S := ''; end; until GoodFile; ToFile(S); end; end; { Spreadsheet.CheckForSave } procedure Spreadsheet.ChangeWidth; { Changes the width of a column } var W, C : Word; Good : Boolean; P : CellPos; O : Word; CP : CellPtr; begin C := GetColumn(PromptColumnWidth, MaxCols, ColSpace); if C = 0 then Exit; W := GetNumber(PromptNewWidth, MinColWidth, MaxColWidth, Good); if not Good then Exit; with WidthHash do begin Delete(C); if W <> DefaultColWidth then Good := Add(C, W); end; { with } if not Good then Exit; SetScreenColStart(ScreenBlock.Start.Col); SetChanged(WasChanged); with OverwriteHash do begin Done; Init(OverwriteHashStart(CellHash.Items)); end; with CellHash do begin CP := FirstItem; while CP <> nil do begin O := CP^.Overwritten(CellHash, FormatHash, WidthHash, LastPos, MaxCols, GetColWidth, DisplayFormulas); if O <> 0 then Good := OverwriteHash.Add(CP, O); CP := NextItem; end; end; { with } if CurrPos.Col > ScreenBlock.Stop.Col then SetScreenColStart(CurrPos.Col); Display; end; { Spreadsheet.ChangeWidth } function Spreadsheet.CellHashStart(TotalCells : Longint) : BucketRange; { Formula that determines the number of cell hash table buckets } begin CellHashStart := Max(100, Min(MaxBuckets, TotalCells div 10)); end; { Spreadsheet.CellHashStart } function Spreadsheet.WidthHashStart(TotalCells : Longint) : BucketRange; { Formula that determines the number of width hash table buckets } begin WidthHashStart := 10; end; { Spreadsheet.WidthHashStart } function Spreadsheet.OverwriteHashStart(TotalCells : Longint) : BucketRange; { Formula that determines the number of overwrite hash table buckets } begin OverwriteHashStart := 10; end; { Spreadsheet.OverwriteHashStart } procedure Spreadsheet.Print; { Prints a spreadsheet to a file or a printer } var S : PathStr; F : Text; PageCols : Byte; PageV, PageH : Word; Finished, GoodFile, Error, Compress, Border, ESCPressed : Boolean; StartCol : Word; StartRow : Word; procedure WString(S : String); begin Writeln(F, S); if IOResult <> 0 then begin if S = PrinterName then Scr.PrintError(ErrPrinterError) else Scr.PrintError(ErrDiskFull); Error := True; Finished := True; end; end; { WString } function RowStartString(Row : Word) : String; begin if (PageH = 1) and Border then RowStartString := LeftJustStr(RowToString(Row), RowNumberSpace) else RowStartString := ''; end; { RowStartString } procedure PrintPage; var Counter : Word; S : String; Color, Cols, Rows : Byte; P : CellPos; begin for Counter := 1 to PrintTopMargin do begin WString(''); if Error then Exit; end; Rows := Min(PrintRows - PrintTopMargin - PrintBottomMargin, Succ(MaxRows - StartRow)); if Border then Dec(Rows); Cols := 0; Counter := Length(RowStartString(StartRow)); while Counter <= PageCols do begin Inc(Counter, ColWidth(Cols + StartCol)); Inc(Cols); end; Dec(Cols); Cols := Min(Cols, Succ(MaxCols - StartCol)); if Border and (PageV = 1) then begin S := FillString(Length(RowStartString(StartRow)), ' '); for Counter := StartCol to Pred(StartCol + Cols) do S := S + CenterStr(ColToString(Counter), ColWidth(Counter)); WString(S); if Error then Exit; end; for P.Row := StartRow to Pred(StartRow + Rows) do begin S := RowStartString(P.Row); for P.Col := StartCol to Pred(StartCol + Cols) do S := S + CellToFString(P, Color); WString(S); if Error then Exit; end; Inc(StartCol, Cols); if (StartCol > LastPos.Col) or (StartCol = 0) then begin Inc(StartRow, Rows); if (StartRow > LastPos.Row) or (StartRow = 0) then Finished := True else begin Inc(PageV); PageH := 1; StartCol := 1; end; end else Inc(PageH); Write(F, Chr(FF)); end; { PrintPage } begin { Spreadsheet.Print } repeat GoodFile := True; S := ReadString(PromptFilePrint, Pred(SizeOf(PathStr)), ESCPressed); if ESCPressed then Exit; if S = '' then S := PrinterName else begin if FileExists(S) then begin GoodFile := GetYesNo(PromptOverwriteFile, ESCPressed); if ESCPressed then Exit; end; end; until GoodFile; Compress := GetYesNo(PromptCompressPrint, ESCPressed); if ESCPressed then Exit; Border := GetYesNo(PromptBorderPrint, ESCPressed); if ESCPressed then Exit; Error := False; {$I-} Assign(F, S); Rewrite(F); if IOResult <> 0 then begin Scr.PrintError(ErrNoOpen); Exit; end; if Compress then begin PageCols := PrintCompressedCols; Write(F, PrinterCompressChar); end else PageCols := PrintNormalCols; PageV := 1; PageH := 1; StartCol := 1; StartRow := 1; Finished := False; repeat PrintPage; until Finished; Close(F); {$I+} end; { Spreadsheet.Print } procedure Spreadsheet.DeleteColumn; { Deletes a column from the spreadsheet } var C : Word; Start, Stop, P, OldPos, OldSPos : CellPos; Deleted : Boolean; OldName : PathStr; CP : CellPtr; H : HashItemPtr; B : Block; F : File; Good : Boolean; begin C := GetColumn(PromptColumnDelete, MaxCols, ColSpace); if C = 0 then Exit; OldPos := CurrPos; OldSPos := ScreenBlock.Start; P.Col := C; Deleted := False; if P.Col <= LastPos.Col then begin with B do begin Start.Col := P.Col; Start.Row := 1; Stop.Col := P.Col; Stop.Row := LastPos.Row; Good := FormatHash.Delete(Start, Stop); end; { with } DeleteBlock(B, Deleted); end; Dec(LastPos.Col); WidthHash.Delete(C); with CellHash do begin CP := FirstItem; while CP <> nil do begin with CP^ do begin if Loc.Col > C then Dec(Loc.Col); if (CP^.ShouldUpdate) and (Loc.Col > C) then FixFormulaCol(CP, -1, MaxCols, MaxRows); end; { with } CP := NextItem; end; end; { with } with WidthHash do begin H := FirstItem; while H <> nil do begin if WordPtr(@H^.Data)^ > C then Dec(WordPtr(@H^.Data)^); H := NextItem; end; end; { with } with FormatHash do begin H := FirstItem; while H <> nil do begin Move(H^.Data, Start, SizeOf(Start)); Move(H^.Data[SizeOf(CellPos)], Stop, SizeOf(Stop)); if (Start.Col = C) and (Stop.Col = C) then Good := Delete(Start, Stop) else begin if Start.Col > C then begin Dec(Start.Col); Move(Start, H^.Data, SizeOf(Start)); end; if Stop.Col > C then begin Dec(Stop.Col); Move(Stop, H^.Data[SizeOf(CellPos)], SizeOf(Stop)); end; end; H := NextItem; end; end; { with } OldName := FileName; ToFile(TempFileName); Done; Good := FromFile(TempFileName); Assign(F, TempFileName); Erase(F); FileName := OldName; if Deleted then P.Row := LastPos.Row else P.Row := 1; Dec(P.Col); SetLastPos(P); MakeCurrent; SetChanged(WasChanged); CurrPos := OldPos; SetScreenColStart(OldSPos.Col); SetScreenRowStart(OldSPos.Row); Display; end; { Spreadsheet.DeleteColumn } procedure Spreadsheet.InsertColumn; { Inserts a column into the spreadsheet } var C : Word; Start, Stop, P, OldPos, OldSPos : CellPos; Deleted : Boolean; H : HashItemPtr; OldName : PathStr; CP : CellPtr; B : Block; F : File; Good : Boolean; begin C := GetColumn(PromptColumnInsert, MaxCols, ColSpace); if C = 0 then Exit; OldPos := CurrPos; OldSPos := ScreenBlock.Start; Deleted := False; if LastPos.Col = MaxCols then begin with B do begin Start.Col := MaxCols; Start.Row := 1; Stop.Col := MaxCols; Stop.Row := LastPos.Row; Good := FormatHash.Delete(Start, Stop); end; { with } DeleteBlock(B, Deleted); end else Inc(LastPos.Col); P.Col := C; WidthHash.Delete(MaxCols); with CellHash do begin CP := FirstItem; while CP <> nil do begin with CP^ do begin if Loc.Col >= C then Inc(Loc.Col); if (CP^.ShouldUpdate) and (Loc.Col >= C) then FixFormulaCol(CP, 1, MaxCols, MaxRows); end; { with } CP := NextItem; end; end; { with } with WidthHash do begin H := FirstItem; while H <> nil do begin if WordPtr(@H^.Data)^ >= C then Inc(WordPtr(@H^.Data)^); H := NextItem; end; end; { with } with FormatHash do begin H := FirstItem; while H <> nil do begin Move(H^.Data, Start, SizeOf(Start)); Move(H^.Data[SizeOf(CellPos)], Stop, SizeOf(Stop)); if Start.Col >= C then begin Inc(Start.Col); Move(Start, H^.Data, SizeOf(Start)); end; if Stop.Col >= C then begin Inc(Stop.Col); Move(Stop, H^.Data[SizeOf(CellPos)], SizeOf(Stop)); end; H := NextItem; end; end; { with } OldName := FileName; ToFile(TempFileName); Done; Good := FromFile(TempFileName); Assign(F, TempFileName); Erase(F); FileName := OldName; if Deleted then P.Row := LastPos.Row else P.Row := 1; if LastPos.Col = MaxCols then P.Col := MaxCols else Inc(P.Col); SetLastPos(P); MakeCurrent; SetChanged(WasChanged); CurrPos := OldPos; SetScreenColStart(OldSPos.Col); SetScreenRowStart(OldSPos.Row); Display; end; { Spreadsheet.InsertColumn } procedure Spreadsheet.DeleteRow; { Deletes a row from the spreadsheet } var R : Word; Start, Stop, P, OldPos, OldSPos : CellPos; Deleted : Boolean; OldName : PathStr; CP : CellPtr; B : Block; F : File; Good : Boolean; H : HashItemPtr; begin R := GetRow(PromptRowDelete, MaxRows); if (R = 0) or (R > LastPos.Row) then Exit; OldPos := CurrPos; OldSPos := ScreenBlock.Start; P.Row := R; if P.Row <= LastPos.Row then begin with B do begin Start.Col := 1; Start.Row := P.Row; Stop.Col := LastPos.Col; Stop.Row := P.Row; Good := FormatHash.Delete(Start, Stop); end; { with } DeleteBlock(B, Deleted); end; Dec(LastPos.Row); with CellHash do begin CP := FirstItem; while CP <> nil do begin with CP^ do begin if Loc.Row > R then Dec(Loc.Row); if (CP^.ShouldUpdate) and (Loc.Row > R) then FixFormulaRow(CP, -1, MaxCols, MaxRows); end; { with } CP := NextItem; end; end; { with } with FormatHash do begin H := FirstItem; while H <> nil do begin Move(H^.Data, Start, SizeOf(Start)); Move(H^.Data[SizeOf(CellPos)], Stop, SizeOf(Stop)); if (Start.Row = R) and (Stop.Row = R) then Good := Delete(Start, Stop) else begin if Start.Row > R then begin Dec(Start.Row); Move(Start, H^.Data, SizeOf(Start)); end; if Stop.Row > R then begin Dec(Stop.Row); Move(Stop, H^.Data[SizeOf(CellPos)], SizeOf(Stop)); end; end; H := NextItem; end; end; { with } OldName := FileName; ToFile(TempFileName); Done; Good := FromFile(TempFileName); Assign(F, TempFileName); Erase(F); FileName := OldName; if Deleted then P.Col := LastPos.Col else P.Col := 1; Dec(P.Row); SetLastPos(P); MakeCurrent; SetChanged(WasChanged); CurrPos := OldPos; SetScreenColStart(OldSPos.Col); SetScreenRowStart(OldSPos.Row); Display; end; { Spreadsheet.DeleteRow } procedure Spreadsheet.InsertRow; { Inserts a row into the spreadsheet } var R : Word; Start, Stop, P, OldPos, OldSPos : CellPos; Deleted : Boolean; OldName : PathStr; CP : CellPtr; B : Block; F : File; Good : Boolean; H : HashItemPtr; begin R := GetRow(PromptRowInsert, MaxRows); if (R = 0) or (R > LastPos.Row) then Exit; OldPos := CurrPos; OldSPos := ScreenBlock.Start; if LastPos.Row = MaxRows then begin with B do begin Start.Col := 1; Start.Row := MaxRows; Stop.Col := LastPos.Col; Stop.Row := MaxRows; Good := FormatHash.Delete(Start, Stop); end; { with } DeleteBlock(B, Deleted); end else Inc(LastPos.Row); P.Row := R; with CellHash do begin CP := FirstItem; while CP <> nil do begin with CP^ do begin if Loc.Row >= R then Inc(Loc.Row); if (CP^.ShouldUpdate) and (Loc.Row >= R) then FixFormulaRow(CP, 1, MaxCols, MaxRows); end; { with } CP := NextItem; end; end; { with } with FormatHash do begin H := FirstItem; while H <> nil do begin Move(H^.Data, Start, SizeOf(Start)); Move(H^.Data[SizeOf(CellPos)], Stop, SizeOf(Stop)); if Start.Row >= R then begin Inc(Start.Row); Move(Start, H^.Data, SizeOf(Start)); end; if Stop.Row >= R then begin Inc(Stop.Row); Move(Stop, H^.Data[SizeOf(CellPos)], SizeOf(Stop)); end; H := NextItem; end; end; { with } OldName := FileName; ToFile(TempFileName); Done; Good := FromFile(TempFileName); Assign(F, TempFileName); Erase(F); FileName := OldName; if Deleted then P.Col := LastPos.Col else P.Col := 1; if LastPos.Row = MaxRows then P.Row := MaxRows else Inc(P.Row); SetLastPos(P); MakeCurrent; SetChanged(WasChanged); CurrPos := OldPos; SetScreenColStart(OldSPos.Col); SetScreenRowStart(OldSPos.Row); Display; end; { Spreadsheet.InsertRow } end.