home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Simtel MSDOS - Coast to Coast
/
simteldosarchivecoasttocoast2.iso
/
ddjmag
/
ddj9209.zip
/
HCALC.ASC
< prev
next >
Wrap
Text File
|
1992-08-10
|
20KB
|
588 lines
_STRUCTURED PROGRAMMING COLUMN_
by Jeff Duntemann
[LISTING ONE]
PROGRAM HCalc; { By Jeff Duntemann; Update of 5/2/92 }
{ Requires Turbo Pascal 6.0! }
USES App,Dialogs,Objects,Views,Menus,StdDlg,Drivers,
FInput, { By Allen Bauer; on CompuServe BPROGA }
Mortgage; { By Jeff Duntemann; from DDJ 10/91 }
CONST
cmNewMortgage = 199;
cmExtraPrin = 198;
cmCloseAll = 197;
cmCloseBC = 196;
cmPrintSummary = 195;
cmLoadMortgage = 194;
cmSaveMortgage = 193;
WindowCount : Integer = 0;
TYPE
MortgageDialogData =
RECORD
PrincipalData : Real;
InterestData : Real;
PeriodsData : Integer;
END;
ExtraPrincipalDialogData =
RECORD
PaymentNumber : Integer;
ExtraDollars : Real;
END;
THouseCalcApp =
OBJECT(TApplication)
InitDialog : PDialog; { Dialog for initializing a mortgage }
ExtraDialog : PDialog; { Dialog for entering extra principal }
CONSTRUCTOR Init;
PROCEDURE InitMenuBar; VIRTUAL;
PROCEDURE CloseAll;
PROCEDURE HandleEvent(VAR Event : TEvent); VIRTUAL;
PROCEDURE NewMortgage;
PROCEDURE LoadMortgage;
PROCEDURE SaveMortgage;
END;
PMortgageTopInterior = ^TMortgageTopInterior;
TMortgageTopInterior =
OBJECT(TView)
Mortgage : PMortgage;
CONSTRUCTOR Init(VAR Bounds : TRect);
CONSTRUCTOR Load(VAR S : TStream);
PROCEDURE Store(VAR S : TStream);
PROCEDURE Draw; VIRTUAL;
END;
PMortgageBottomInterior = ^TMortgageBottomInterior;
TMortgageBottomInterior =
OBJECT(TScroller)
{ Points to Mortgage object owned by TMortgageView }
Mortgage : PMortgage;
CONSTRUCTOR Init(VAR Bounds : TRect;
AHScrollBar, AVScrollbar : PScrollBar);
CONSTRUCTOR Load(VAR S : TStream);
PROCEDURE Store(VAR S : TStream);
PROCEDURE Draw; VIRTUAL;
END;
PMortgageView = ^TMortgageView;
TMortgageView =
OBJECT(TWindow)
Mortgage : TMortgage;
TopInterior : PMortgageTopInterior;
BottomInterior : PMortgageBottomInterior;
CONSTRUCTOR Init(VAR Bounds : TRect;
ATitle : TTitleStr;
ANumber : Integer;
InitMortgageData :
MortgageDialogData);
CONSTRUCTOR Load(VAR S : TStream);
PROCEDURE Store(VAR S : TStream);
PROCEDURE HandleEvent(Var Event : TEvent); VIRTUAL;
PROCEDURE ExtraPrincipal;
PROCEDURE PrintSummary;
DESTRUCTOR Done; VIRTUAL;
END;
VAR
HouseCalc : THouseCalcApp; { This is the application object itself }
TempMtg : PMortgageView; { Temporary pointer for mortgage windows }
CONST
DefaultMortgageData : MortgageDialogData =
(PrincipalData : 100000;
InterestData : 10.0;
PeriodsData : 360);
RMortgageView : TStreamRec =
(ObjType : 1101;
VMTLink : Ofs(TypeOf(TMortgageView)^);
Load : @TMortgageView.Load;
Store : @TMortgageView.Store);
RMortgageTopInterior : TStreamRec =
(ObjType : 1102;
VMTLink : Ofs(TypeOf(TMortgageTopInterior)^);
Load : @TMortgageTopInterior.Load;
Store : @TMortgageTopInterior.Store);
RMortgageBottomInterior: TStreamRec =
(ObjType : 1103;
VMTLink : Ofs(TypeOf(TMortgageBottomInterior)^);
Load : @TMortgageBottomInterior.Load;
Store : @TMortgageBottomInterior.Store);
PROCEDURE RegisterAllTypes;
BEGIN
RegisterType(RScrollBar);
RegisterType(RFrame);
RegisterFInputLine;
RegisterType(RMortgage); { RMortgage defined in unit MORTGAGE.PAS }
RegisterType(RMortgageView);
RegisterType(RMortgageTopInterior);
RegisterType(RMortgageBottomInterior);
END;
FUNCTION ExecDialog(P: PDialog; Data: Pointer): Word;
VAR
Result: Word;
BEGIN
Result := cmCancel;
P := PDialog(Application^.ValidView(P));
IF P <> NIL THEN
BEGIN
IF Data <> NIL THEN P^.SetData(Data^);
Result := DeskTop^.ExecView(P);
IF (Result <> cmCancel) AND (Data <> NIL) THEN P^.GetData(Data^);
Dispose(P, Done);
END;
ExecDialog := Result;
END;
{------------------------------}
{ METHODS: THouseCalcApp }
{------------------------------}
CONSTRUCTOR THouseCalcApp.Init;
VAR
R : TRect;
aView : PView;
BEGIN
TApplication.Init; { Always call the parent's constructor first! }
{ Create the dialog for initializing a mortgage: }
R.Assign(20,5,60,16);
InitDialog := New(PDialog,Init(R,'Define Mortgage Parameters'));
WITH InitDialog^ DO
BEGIN
{ First item in the dialog box is input line for principal: }
R.Assign(3,3,13,4);
aView := New(PFInputLine,Init(R,8,DRealSet,DReal,0));
Insert(aView);
R.Assign(2,2,12,3);
Insert(New(PLabel,Init(R,'Principal',aView)));
{ Next is the input line for interest rate: }
R.Assign(17,3,26,4);
aView := New(PFInputLine,Init(R,6,DRealSet,DReal,3));
Insert(aView);
R.Assign(16,2,25,3);
Insert(New(PLabel,Init(R,'Interest',aView)));
R.Assign(26,3,27,4); { Add a static text "%" sign }
Insert(New(PStaticText,Init(R,'%')));
{ Up next is the input line for number of periods: }
R.Assign(31,3,36,4);
aView := New(PFInputLine,Init(R,3,DUnsignedSet,DInteger,0));
Insert(aView);
R.Assign(29,2,37,3);
Insert(New(PLabel,Init(R,'Periods',aView)));
{ These are standard buttons for the OK and Cancel commands: }
R.Assign(8,8,16,10);
Insert(New(PButton,Init(R,'~O~K',cmOK,bfDefault)));
R.Assign(22,8,32,10);
Insert(New(PButton,Init(R,'Cancel',cmCancel,bfNormal)));
END;
{ Create the dialog for adding additional principal to a payment: }
R.Assign(20,5,60,16);
ExtraDialog := New(PDialog,Init(R,'Apply Extra Principal to Mortgage'));
WITH ExtraDialog^ DO
BEGIN
{ First item in the dialog is the payment number to which }
{ we're going to apply the extra principal: }
R.Assign(9,3,18,4);
aView := New(PFInputLine,Init(R,6,DUnsignedSet,DInteger,0));
Insert(aView);
R.Assign(3,2,12,3);
Insert(New(PLabel,Init(R,'Payment #',aView)));
{ Next item in the dialog box is input line for extra principal: }
R.Assign(23,3,33,4);
aView := New(PFInputLine,Init(R,8,DRealSet,DReal,2));
Insert(aView);
R.Assign(20,2,35,3);
Insert(New(PLabel,Init(R,'Extra Principal',aView)));
{ These are standard buttons for the OK and Cancel commands: }
R.Assign(8,8,16,10);
Insert(New(PButton,Init(R,'~O~K',cmOK,bfDefault)));
R.Assign(22,8,32,10);
Insert(New(PButton,Init(R,'Cancel',cmCancel,bfNormal)));
END;
END;
{ This method sends out a broadcast message to all views. Only the
{ mortgage windows know how to respond to it, so when cmCloseBC is
{ issued, only the mortgage windows react--by closing. }
PROCEDURE THouseCalcApp.CloseAll;
VAR
Who : Pointer;
BEGIN
Who := Message(Desktop,evBroadcast,cmCloseBC,@Self);
END;
PROCEDURE THouseCalcApp.LoadMortgage;
VAR
FileName : FNameStr;
FetchMtg : PBufStream;
BEGIN
FileName := '*.MTG';
IF ExecDialog(New(PFileDialog, Init('*.*', 'Load Mortgage File',
'~N~ame', fdOpenButton, 100)), @FileName) <> cmCancel
THEN
BEGIN
FetchMtg := New(PBufStream,Init(FileName,stOpenRead,1024));
IF FetchMtg^.Status <> 0 THEN Halt(1);
TempMtg := PMortgageView(FetchMtg^.Get);
IF FetchMtg^.Status <> 0 THEN
BEGIN
Writeln('Status code =',FetchMtg^.Status);
Halt(1);
END;
Dispose(FetchMtg,Done);
DisableCommands([cmSaveMortgage]);
IF TempMtg <> NIL THEN
Desktop^.Insert(TempMtg);
EnableCommands([cmSaveMortgage]);
END;
END;
PROCEDURE THouseCalcApp.SaveMortgage;
VAR
FileName : FNameStr;
SaveMtg : PBufStream;
BEGIN
FileName := '*.MTG';
IF ExecDialog(New(PFileDialog, Init('*.*', 'Save Mortgage File',
'~N~ame', fdOpenButton, 100)), @FileName) <> cmCancel
THEN
BEGIN
SaveMtg := New(PBufStream,Init(FileName,stCreate,1024));
IF SaveMtg^.Status <> 0 THEN Halt(1);
IF DeskTop^.Current <> NIL THEN
BEGIN
SaveMtg^.Put(DeskTop^.Current);
IF SaveMtg^.Status <> 0 THEN
BEGIN
Writeln('Status value =',SaveMtg^.Status);
Halt(1);
END;
END;
Dispose(SaveMtg,Done);
END;
END;
PROCEDURE THouseCalcApp.HandleEvent(VAR Event : TEvent);
BEGIN
TApplication.HandleEvent(Event);
IF Event.What = evCommand THEN
BEGIN
CASE Event.Command OF
cmNewMortgage : NewMortgage;
cmLoadMortgage : LoadMortgage;
cmSaveMortgage : SaveMortgage;
cmCloseAll : CloseAll;
ELSE
Exit;
END; { CASE }
ClearEvent(Event);
END;
END;
PROCEDURE THouseCalcApp.NewMortgage;
VAR
Code : Integer;
R : TRect;
Control : Word;
ThisMortgage : PMortgageView;
InitMortgageData : MortgageDialogData;
BEGIN
{ First we need a dialog to get the intial mortgage values from }
{ the user. The dialog appears *before* the mortgage window! }
WITH InitMortgageData DO
BEGIN
PrincipalData := 100000;
InterestData := 10.0;
PeriodsData := 360;
END;
InitDialog^.SetData(InitMortgageData);
Control := Desktop^.ExecView(InitDialog);
IF Control <> cmCancel THEN { Create a new mortgage object: }
BEGIN
R.Assign(5,5,45,20);
Inc(WindowCount);
{ Get data from the initial mortgage dialog: }
InitDialog^.GetData(InitMortgageData);
{ Call the constructor for the mortgage window: }
ThisMortgage :=
New(PMortgageView,Init(R,'Mortgage',WindowCount,InitMortgageData));
{ Insert the mortgage window into the desktop: }
Desktop^.Insert(ThisMortgage);
END;
END;
PROCEDURE THouseCalcApp.InitMenuBar;
VAR
R : TRect;
BEGIN
GetExtent(R);
R.B.Y := R.A.Y + 1; { Define 1-line menu bar }
MenuBar := New(PMenuBar,Init(R,NewMenu(
NewSubMenu('~M~ortgage',hcNoContext,NewMenu(
NewItem('~N~ew','F6',kbF6,cmNewMortgage,hcNoContext,
NewItem('~O~pen','F3',kbF3,cmLoadMortgage,hcNoContext,
NewItem('~S~ave Top','F2',kbF2,cmSaveMortgage,hcNoContext,
NewItem('~E~xtra Principal ','',0,cmExtraPrin,hcNoContext,
NewItem('~C~lose all','F7',kbF7,cmCloseAll,hcNoContext,
NewItem('E~x~it','Alt-X',kbAltX,cmQuit,hcNoContext,
NIL))))))),
NIL)
)));
END;
{---------------------------------}
{ METHODS: TMortgageTopInterior }
{---------------------------------}
CONSTRUCTOR TMortgageTopInterior.Init(VAR Bounds : TRect);
BEGIN
TView.Init(Bounds); { Call ancestor's constructor }
GrowMode := gfGrowHiX; { Permits pane to grow in X but not Y }
END;
CONSTRUCTOR TMortgageTopInterior.Load(VAR S : TStream);
BEGIN
TView.Load(S);
END;
PROCEDURE TMortgageTopInterior.Store(Var S : TStream);
BEGIN
TView.Store(S)
END;
PROCEDURE TMortgageTopInterior.Draw;
VAR
YRun : Integer;
Color : Byte;
B : TDrawBuffer;
STemp : String[20];
BEGIN
Color := GetColor(1);
MoveChar(B,' ',Color,Size.X); { Clear the buffer to spaces }
MoveStr(B,' Principal Int. Periods Payment',Color);
WriteLine(0,0,Size.X,1,B);
MoveChar(B,' ',Color,Size.X); { Clear the buffer to spaces }
{ Here we convert payment data to strings for display: }
Str(Mortgage^.Principal:7:2,STemp);
MoveStr(B[1],STemp,Color); { At beginning of buffer B }
Str(Mortgage^.Interest*100:7:2,STemp);
MoveStr(B[10],STemp,Color); { At position 14 of buffer B }
Str(Mortgage^.Periods:4,STemp);
MoveStr(B[20],STemp,Color); { At position 25 of buffer B }
WriteLine(0,1,Size.X,1,B);
Str(Mortgage^.MonthlyPI:7:2,STemp);
MoveStr(B[27],STemp,Color); { At position 29 of buffer B }
WriteLine(0,1,Size.X,1,B);
MoveChar(B,' ',Color,Size.X); { Clear the buffer to spaces }
MoveStr(B,
' Extra Principal Interest',
Color);
WriteLine(0,2,Size.X,1,B);
MoveChar(B,' ',Color,Size.X); { Clear the buffer to spaces }
MoveStr(B,
'Paymt # Prin. Int. Balance Principal So far So far ',
Color);
WriteLine(0,3,Size.X,1,B);
END;
{------------------------------------}
{ METHODS: TMortgageBottomInterior }
{------------------------------------}
CONSTRUCTOR TMortgageBottomInterior.Init(VAR Bounds : TRect;
AHScrollBar, AVScrollBar : PScrollBar);
BEGIN
{ Call ancestor's constructor: }
TScroller.Init(Bounds,AHScrollBar,AVScrollBar);
GrowMode := gfGrowHiX + gfGrowHiY;
Options := Options OR ofFramed;
END;
CONSTRUCTOR TMortgageBottomInterior.Load(VAR S : TStream);
BEGIN
TScroller.Load(S);
END;
PROCEDURE TMortgageBottomInterior.Store(Var S : TStream);
BEGIN
TScroller.Store(S)
END;
PROCEDURE TMortgageBottomInterior.Draw;
VAR
Color : Byte;
B : TDrawBuffer;
YRun : Integer;
STemp : String[20];
BEGIN
Color := GetColor(1);
FOR YRun := 0 TO Size.Y-1 DO
BEGIN
MoveChar(B,' ',Color,80); { Clear the buffer to spaces }
Str(Delta.Y+YRun+1:4,STemp);
MoveStr(B,STemp+':',Color); { At beginning of buffer B }
{ Here we convert payment data to strings for display: }
Str(Mortgage^.Payments^[Delta.Y+YRun+1].PayPrincipal:7:2,STemp);
MoveStr(B[6],STemp,Color); { At beginning of buffer B }
Str(Mortgage^.Payments^[Delta.Y+YRun+1].PayInterest:7:2,STemp);
MoveStr(B[15],STemp,Color); { At position 15 of buffer B }
Str(Mortgage^.Payments^[Delta.Y+YRun+1].Balance:10:2,STemp);
MoveStr(B[24],STemp,Color); { At position 24 of buffer B }
{ There isn't an extra principal value for every payment, so }
{ display the value only if it is nonzero: }
STemp := '';
IF Mortgage^.Payments^[Delta.Y+YRun+1].ExtraPrincipal > 0
THEN
Str(Mortgage^.Payments^[Delta.Y+YRun+1].ExtraPrincipal:10:2,STemp);
MoveStr(B[37],STemp,Color); { At position 37 of buffer B }
Str(Mortgage^.Payments^[Delta.Y+YRun+1].PrincipalSoFar:10:2,STemp);
MoveStr(B[50],STemp,Color); { At position 50 of buffer B }
Str(Mortgage^.Payments^[Delta.Y+YRun+1].InterestSoFar:10:2,STemp);
MoveStr(B[64],STemp,Color); { At position 64 of buffer B }
{ Here we write the line to the window, taking into account the }
{ state of the X scroll bar: }
WriteLine(0,YRun,Size.X,1,B[Delta.X]);
END;
END;
{------------------------------}
{ METHODS: TMortgageView }
{------------------------------}
CONSTRUCTOR TMortgageView.Init(VAR Bounds : TRect;
ATitle : TTitleStr;
ANumber : Integer;
InitMortgageData :
MortgageDialogData);
VAR
HScrollBar,VScrollBar : PScrollBar;
R,S : TRect;
BEGIN
TWindow.Init(Bounds,ATitle,ANumber); { Call ancestor's constructor }
{ Call the Mortgage object's constructor using dialog data: }
WITH InitMortgageData DO
Mortgage.Init(PrincipalData,
InterestData / 100,
PeriodsData,
12);
{ Here we set up a window with *two* interiors, one scrollable, one }
{ static. It's all in the way that you define the bounds, mostly: }
GetClipRect(Bounds); { Get bounds for interior of view }
Bounds.Grow(-1,-1); { Shrink those bounds by 1 for both X & Y }
{ Define a rectangle to embrace the upper of the two interiors: }
R.Assign(Bounds.A.X,Bounds.A.Y,Bounds.B.X,Bounds.A.Y+4);
TopInterior := New(PMortgageTopInterior,Init(R));
TopInterior^.Mortgage := @Mortgage;
Insert(TopInterior);
{ Define a rectangle to embrace the lower of two interiors: }
R.Assign(Bounds.A.X,Bounds.A.Y+5,Bounds.B.X,Bounds.B.Y);
{ Create scroll bars for both mouse & keyboard input: }
VScrollBar := StandardScrollBar(sbVertical + sbHandleKeyboard);
{ We have to adjust vertical bar to fit bottom interior: }
VScrollBar^.Origin.Y := R.A.Y; { Adjust top Y value }
VScrollBar^.Size.Y := R.B.Y - R.A.Y; { Adjust size }
{ The horizontal scroll bar, on the other hand, is standard: }
HScrollBar := StandardScrollBar(sbHorizontal + sbHandleKeyboard);
{ Create bottom interior object with scroll bars: }
BottomInterior :=
New(PMortgageBottomInterior,Init(R,HScrollBar,VScrollBar));
{ Make copy of pointer to mortgage object: }
BottomInterior^.Mortgage := @Mortgage;
{ Set the limits for the scroll bars: }
BottomInterior^.SetLimit(80,InitMortgageData.PeriodsData);
{ Insert the interior into the window: }
Insert(BottomInterior);
END;
CONSTRUCTOR TMortgageView.Load(VAR S : TStream);
VAR
MortgageTemp : PObject;
BEGIN
TWindow.Load(S); { Load what you've inherited from parent type}
MortgageTemp := S.Get; { Load the contained TMortgage object }
{ Now we have to copy the heap-based copy of TMortgage to the copy }
{ embedded in the TMortgageView object we're in the process of loading: }
Move(MortgageTemp^,Mortgage,Sizeof(TMortgage));
GetSubViewPtr(S,BottomInterior);
GetSubViewPtr(S,TopInterior);
TopInterior^.Mortgage := @Mortgage;
BottomInterior^.Mortgage := @Mortgage;
END;
PROCEDURE TMortgageView.Store(VAR S : TStream);
BEGIN
TWindow.Store(S); { Store what you've inherited from parent type}
S.Put(@Mortgage); { Store the contained TMortgage object }
PutSubViewPtr(S,BottomInterior);
PutSubViewPtr(S,TopInterior);
END;
PROCEDURE TMortgageView.HandleEvent(Var Event : TEvent);
BEGIN
TWindow.HandleEvent(Event);
IF Event.What = evCommand THEN
BEGIN
CASE Event.Command OF
cmExtraPrin : ExtraPrincipal;
cmPrintSummary : PrintSummary;
ELSE
Exit;
END; { CASE }
ClearEvent(Event);
END
ELSE
IF Event.What = evBroadcast THEN
CASE Event.Command OF
cmCloseBC : Done
END; { CASE }
END;
PROCEDURE TMortgageView.ExtraPrincipal;
VAR
Control : Word;
ExtraPrincipalData : ExtraPrincipalDialogData;
BEGIN
{ Execute the "extra principal" dialog box: }
Control := Desktop^.ExecView(HouseCalc.ExtraDialog);
IF Control <> cmCancel THEN { Update the active mortgage window: }
BEGIN
{ Get data from the extra principal dialog: }
HouseCalc.ExtraDialog^.GetData(ExtraPrincipalData);
Mortgage.Payments^[ExtraPrincipalData.PaymentNumber].ExtraPrincipal :=
ExtraPrincipalData.ExtraDollars;
Mortgage.Recalc; { Recalculate the amortization table... }
Redraw; { ...and redraw the mortgage window }
END;
END;
PROCEDURE TMortgageView.PrintSummary;
BEGIN
END;
DESTRUCTOR TMortgageView.Done;
BEGIN
Mortgage.Done; { Dispose of the mortgage object's memory }
TWindow.Done; { Call parent's destructor to dispose of window }
END;
BEGIN
RegisterAllTypes;
HouseCalc.Init;
HouseCalc.Run;
HouseCalc.Done;
END.