home *** CD-ROM | disk | FTP | other *** search
/ Computer Select (Limited Edition) / Computer Select.iso / dobbs / v17n02 / hcalc.asc < prev    next >
Encoding:
Text File  |  1991-11-03  |  14.6 KB  |  483 lines

  1. PROGRAM HCalc;   { By Jeff Duntemann; Update of 10/31/91 }
  2.                  { Requires Turbo Pascal 6.0! }
  3.  
  4. USES App,Dialogs,Objects,Views,Menus,Drivers,
  5.      FInput,    { By Allen Bauer; on CompuServe BPROGA }
  6.      Mortgage;  { By Jeff Duntemann; from DDJ 10/91 }
  7.  
  8. CONST
  9.   cmNewMortgage  = 199;
  10.   cmExtraPrin    = 198;
  11.   cmCloseAll     = 197;
  12.   cmCloseBC      = 196;
  13.   cmPrintSummary = 195;
  14.   WindowCount : Integer = 0;
  15.  
  16. TYPE
  17.   MortgageDialogData =
  18.     RECORD
  19.       PrincipalData : Real;
  20.       InterestData  : Real;
  21.       PeriodsData   : Integer;
  22.     END;
  23.  
  24.   ExtraPrincipalDialogData =
  25.     RECORD
  26.       PaymentNumber : Integer;
  27.       ExtraDollars  : Real;
  28.     END;
  29.  
  30.   THouseCalcApp =
  31.     OBJECT(TApplication)
  32.       InitDialog  : PDialog;  { Dialog for initializing a mortgage }
  33.       ExtraDialog : PDialog;  { Dialog for entering extra principal }
  34.       CONSTRUCTOR Init;
  35.       PROCEDURE   InitMenuBar; VIRTUAL;
  36.       PROCEDURE   CloseAll;
  37.       PROCEDURE   HandleEvent(VAR Event : TEvent); VIRTUAL;
  38.       PROCEDURE   NewMortgage;
  39.     END;
  40.  
  41.   PMortgageTopInterior = ^TMortgageTopInterior;
  42.   TMortgageTopInterior =
  43.     OBJECT(TView)
  44.       Mortgage    : PMortgage;
  45.       CONSTRUCTOR Init(VAR Bounds : TRect);
  46.       PROCEDURE   Draw; VIRTUAL;
  47.     END;
  48.  
  49.  
  50.   PMortgageBottomInterior = ^TMortgageBottomInterior;
  51.   TMortgageBottomInterior =
  52.     OBJECT(TScroller)
  53.       { Points to Mortgage object owned by TMortgageView }
  54.       Mortgage    : PMortgage;
  55.       CONSTRUCTOR Init(VAR Bounds : TRect;
  56.                        AHScrollBar, AVScrollbar : PScrollBar);
  57.       PROCEDURE   Draw; VIRTUAL;
  58.     END;
  59.  
  60.   PMortgageView = ^TMortgageView;
  61.   TMortgageView =
  62.     OBJECT(TWindow)
  63.       Mortgage    : TMortgage;
  64.       CONSTRUCTOR Init(VAR Bounds  : TRect;
  65.                        ATitle  : TTitleStr;
  66.                        ANumber : Integer;
  67.                        InitMortgageData :
  68.                        MortgageDialogData);
  69.       PROCEDURE   HandleEvent(Var Event : TEvent); VIRTUAL;
  70.       PROCEDURE   ExtraPrincipal;
  71.       PROCEDURE   PrintSummary;
  72.       DESTRUCTOR  Done; VIRTUAL;
  73.     END;
  74.  
  75.  
  76. CONST
  77.   DefaultMortgageData : MortgageDialogData =
  78.     (PrincipalData : 100000;
  79.      InterestData  : 10.0;
  80.      PeriodsData   : 360);
  81.  
  82.  
  83. VAR
  84.   HouseCalc : THouseCalcApp;  { This is the application object itself }
  85.  
  86.  
  87.  
  88. {------------------------------}
  89. {   METHODS: THouseCalcApp     }
  90. {------------------------------}
  91.  
  92.  
  93. CONSTRUCTOR THouseCalcApp.Init;
  94.  
  95. VAR
  96.   R : TRect;
  97.   aView      : PView;
  98.  
  99. BEGIN
  100.   TApplication.Init;  { Always call the parent's constructor first! }
  101.  
  102.   { Create the dialog for initializing a mortgage: }
  103.   R.Assign(20,5,60,16);
  104.   InitDialog := New(PDialog,Init(R,'Define Mortgage Parameters'));
  105.   WITH InitDialog^ DO
  106.     BEGIN
  107.       { First item in the dialog box is input line for principal: }
  108.       R.Assign(3,3,13,4);
  109.       aView := New(PFInputLine,Init(R,8,DRealSet,DReal,0));
  110.       Insert(aView);
  111.       R.Assign(2,2,12,3);
  112.       Insert(New(PLabel,Init(R,'Principal',aView)));
  113.  
  114.       { Next is the input line for interest rate: }
  115.       R.Assign(17,3,26,4);
  116.       aView := New(PFInputLine,Init(R,6,DRealSet,DReal,3));
  117.       Insert(aView);
  118.       R.Assign(16,2,25,3);
  119.       Insert(New(PLabel,Init(R,'Interest',aView)));
  120.       R.Assign(26,3,27,4);   { Add a static text "%" sign }
  121.       Insert(New(PStaticText,Init(R,'%')));
  122.  
  123.       { Up next is the input line for number of periods: }
  124.       R.Assign(31,3,36,4);
  125.       aView := New(PFInputLine,Init(R,3,DUnsignedSet,DInteger,0));
  126.       Insert(aView);
  127.       R.Assign(29,2,37,3);
  128.       Insert(New(PLabel,Init(R,'Periods',aView)));
  129.  
  130.       { These are standard buttons for the OK and Cancel commands: }
  131.       R.Assign(8,8,16,10);
  132.       Insert(New(PButton,Init(R,'~O~K',cmOK,bfDefault)));
  133.       R.Assign(22,8,32,10);
  134.       Insert(New(PButton,Init(R,'Cancel',cmCancel,bfNormal)));
  135.     END;
  136.  
  137.   { Create the dialog for adding additional principal to a payment: }
  138.   R.Assign(20,5,60,16);
  139.   ExtraDialog := New(PDialog,Init(R,'Apply Extra Principal to Mortgage'));
  140.   WITH ExtraDialog^ DO
  141.     BEGIN
  142.       { First item in the dialog is the payment number to which }
  143.       { we're going to apply the extra principal:               }
  144.       R.Assign(9,3,18,4);
  145.       aView := New(PFInputLine,Init(R,6,DUnsignedSet,DInteger,0));
  146.       Insert(aView);
  147.       R.Assign(3,2,12,3);
  148.       Insert(New(PLabel,Init(R,'Payment #',aView)));
  149.  
  150.       { Next item in the dialog box is input line for extra principal: }
  151.       R.Assign(23,3,33,4);
  152.       aView := New(PFInputLine,Init(R,8,DRealSet,DReal,2));
  153.       Insert(aView);
  154.       R.Assign(20,2,35,3);
  155.       Insert(New(PLabel,Init(R,'Extra Principal',aView)));
  156.  
  157.       { These are standard buttons for the OK and Cancel commands: }
  158.       R.Assign(8,8,16,10);
  159.       Insert(New(PButton,Init(R,'~O~K',cmOK,bfDefault)));
  160.       R.Assign(22,8,32,10);
  161.       Insert(New(PButton,Init(R,'Cancel',cmCancel,bfNormal)));
  162.     END;
  163.  
  164. END;
  165.  
  166.  
  167. { This method sends out a broadcast message to all views.  Only the
  168. { mortgage windows know how to respond to it, so when cmCloseBC is
  169. { issued, only the mortgage windows react--by closing. }
  170.  
  171. PROCEDURE THouseCalcApp.CloseAll;
  172.  
  173. VAR
  174.   Who : Pointer;
  175.  
  176. BEGIN
  177.   Who := Message(Desktop,evBroadcast,cmCloseBC,@Self);
  178. END;
  179.  
  180.  
  181. PROCEDURE THouseCalcApp.HandleEvent(VAR Event : TEvent);
  182.  
  183. BEGIN
  184.   TApplication.HandleEvent(Event);
  185.   IF Event.What = evCommand THEN
  186.     BEGIN
  187.       CASE Event.Command OF
  188.         cmNewMortgage : NewMortgage;
  189.         cmCloseAll    : CloseAll;
  190.       ELSE
  191.         Exit;
  192.       END; { CASE }
  193.       ClearEvent(Event);
  194.     END;
  195. END;
  196.  
  197.  
  198. PROCEDURE THouseCalcApp.NewMortgage;
  199.  
  200. VAR
  201.   Code       : Integer;
  202.   R          : TRect;
  203.   Control    : Word;
  204.   ThisMortgage     : PMortgageView;
  205.   InitMortgageData : MortgageDialogData;
  206.  
  207. BEGIN
  208.   { First we need a dialog to get the intial mortgage values from }
  209.   { the user.  The dialog appears *before* the mortgage window!   }
  210.   WITH InitMortgageData DO
  211.     BEGIN
  212.       PrincipalData := 100000;
  213.       InterestData  := 10.0;
  214.       PeriodsData   := 360;
  215.     END;
  216.   InitDialog^.SetData(InitMortgageData);
  217.   Control := Desktop^.ExecView(InitDialog);
  218.    IF Control <> cmCancel THEN  { Create a new mortgage object: }
  219.      BEGIN
  220.        R.Assign(5,5,45,20);
  221.        Inc(WindowCount);
  222.        { Get data from the initial mortgage dialog: }
  223.        InitDialog^.GetData(InitMortgageData);
  224.        { Call the constructor for the mortgage window: }
  225.        ThisMortgage :=
  226.          New(PMortgageView,Init(R,'Mortgage',WindowCount,
  227.                                 InitMortgageData));
  228.  
  229.        { Insert the mortgage window into the desktop: }
  230.        Desktop^.Insert(ThisMortgage);
  231.      END;
  232. END;
  233.  
  234.  
  235. PROCEDURE THouseCalcApp.InitMenuBar;
  236.  
  237. VAR
  238.   R : TRect;
  239.  
  240. BEGIN
  241.   GetExtent(R);
  242.   R.B.Y := R.A.Y + 1;  { Define 1-line menu bar }
  243.  
  244.   MenuBar := New(PMenuBar,Init(R,NewMenu(
  245.     NewSubMenu('~M~ortgage',hcNoContext,NewMenu(
  246.       NewItem('~N~ew','F6',kbF6,cmNewMortgage,hcNoContext,
  247.       NewItem('~E~xtra Principal    ','',0,cmExtraPrin,hcNoContext,
  248.       NewItem('~C~lose all','F7',kbF7,cmCloseAll,hcNoContext,
  249.       NewItem('E~x~it','Alt-X',kbAltX,cmQuit,hcNoContext,
  250.       NIL))))),
  251.     NIL)
  252.   )));
  253. END;
  254.  
  255.  
  256. {---------------------------------}
  257. {   METHODS: TMortgageTopInterior }
  258. {---------------------------------}
  259.  
  260. CONSTRUCTOR TMortgageTopInterior.Init(VAR Bounds : TRect);
  261.  
  262. BEGIN
  263.   TView.Init(Bounds);     { Call ancestor's constructor }
  264.   GrowMode := gfGrowHiX;  { Permits pane to grow in X but not Y }
  265. END;
  266.  
  267.  
  268. PROCEDURE TMortgageTopInterior.Draw;
  269.  
  270. VAR
  271.   YRun  : Integer;
  272.   Color : Byte;
  273.   B     : TDrawBuffer;
  274.   STemp : String[20];
  275.  
  276. BEGIN
  277.   Color := GetColor(1);
  278.   MoveChar(B,' ',Color,Size.X);    { Clear the buffer to spaces }
  279.   MoveStr(B,'  Principal    Interest   Periods',Color);
  280.   WriteLine(0,0,Size.X,1,B);
  281.  
  282.   MoveChar(B,' ',Color,Size.X);    { Clear the buffer to spaces }
  283.   { Here we convert payment data to strings for display: }
  284.   Str(Mortgage^.Principal:7:2,STemp);
  285.   MoveStr(B[2],STemp,Color);         { At beginning of buffer B }
  286.   Str(Mortgage^.Interest*100:7:2,STemp);
  287.   MoveStr(B[14],STemp,Color);      { At position 14 of buffer B }
  288.   Str(Mortgage^.Periods:4,STemp);
  289.   MoveStr(B[27],STemp,Color);      { At position 27 of buffer B }
  290.   WriteLine(0,1,Size.X,1,B);
  291.  
  292.   MoveChar(B,' ',Color,Size.X);    { Clear the buffer to spaces }
  293.   MoveStr(B,
  294.   '                                      Extra        Principal      Interest',
  295.   Color);
  296.   WriteLine(0,2,Size.X,1,B);
  297.  
  298.   MoveChar(B,' ',Color,Size.X);    { Clear the buffer to spaces }
  299.   MoveStr(B,
  300.   'Paymt #  Prin.   Int.     Balance     Principal    So far         So far ',
  301.   Color);
  302.   WriteLine(0,3,Size.X,1,B);
  303.  
  304. END;
  305.  
  306.  
  307. {------------------------------------}
  308. {   METHODS: TMortgageBottomInterior }
  309. {------------------------------------}
  310.  
  311. CONSTRUCTOR TMortgageBottomInterior.Init(VAR Bounds : TRect;
  312.                                          AHScrollBar, AVScrollBar :
  313.                                          PScrollBar);
  314.  
  315. BEGIN
  316.   { Call ancestor's constructor: }
  317.   TScroller.Init(Bounds,AHScrollBar,AVScrollBar);
  318.   GrowMode := gfGrowHiX + gfGrowHiY;
  319.   Options := Options OR ofFramed;
  320. END;
  321.  
  322.  
  323. PROCEDURE TMortgageBottomInterior.Draw;
  324.  
  325. VAR
  326.   Color : Byte;
  327.   B     : TDrawBuffer;
  328.   YRun  : Integer;
  329.   STemp : String[20];
  330.  
  331. BEGIN
  332.   Color := GetColor(1);
  333.   FOR YRun := 0 TO Size.Y-1 DO
  334.     BEGIN
  335.       MoveChar(B,' ',Color,80);    { Clear the buffer to spaces }
  336.       Str(Delta.Y+YRun+1:4,STemp);
  337.       MoveStr(B,STemp+':',Color);        { At beginning of buffer B }
  338.       { Here we convert payment data to strings for display: }
  339.       Str(Mortgage^.Payments^[Delta.Y+YRun+1].PayPrincipal:7:2,STemp);
  340.       MoveStr(B[6],STemp,Color);         { At beginning of buffer B }
  341.       Str(Mortgage^.Payments^[Delta.Y+YRun+1].PayInterest:7:2,STemp);
  342.       MoveStr(B[15],STemp,Color);      { At position 15 of buffer B }
  343.       Str(Mortgage^.Payments^[Delta.Y+YRun+1].Balance:10:2,STemp);
  344.       MoveStr(B[24],STemp,Color);      { At position 24 of buffer B }
  345.       { There isn't an extra principal value for every payment, so }
  346.       { display the value only if it is nonzero:                   }
  347.       STemp := '';
  348.       IF  Mortgage^.Payments^[Delta.Y+YRun+1].ExtraPrincipal > 0
  349.       THEN
  350.         Str(Mortgage^.Payments^[Delta.Y+YRun+1].ExtraPrincipal:10:2,STemp);
  351.       MoveStr(B[37],STemp,Color);      { At position 37 of buffer B }
  352.       Str(Mortgage^.Payments^[Delta.Y+YRun+1].PrincipalSoFar:10:2,STemp);
  353.       MoveStr(B[50],STemp,Color);      { At position 50 of buffer B }
  354.       Str(Mortgage^.Payments^[Delta.Y+YRun+1].InterestSoFar:10:2,STemp);
  355.       MoveStr(B[64],STemp,Color);      { At position 64 of buffer B }
  356.       { Here we write the line to the window, taking into account the }
  357.       { state of the X scroll bar: }
  358.       WriteLine(0,YRun,Size.X,1,B[Delta.X]);
  359.     END;
  360. END;
  361.  
  362.  
  363. {------------------------------}
  364. {   METHODS: TMortgageView     }
  365. {------------------------------}
  366.  
  367. CONSTRUCTOR TMortgageView.Init(VAR Bounds  : TRect;
  368.                                    ATitle  : TTitleStr;
  369.                                    ANumber : Integer;
  370.                                    InitMortgageData :
  371.                                    MortgageDialogData);
  372. VAR
  373.   TopInterior    : PMortgageTopInterior;
  374.   BottomInterior : PMortgageBottomInterior;
  375.   HScrollBar,VScrollBar : PScrollBar;
  376.   R,S  : TRect;
  377.  
  378. BEGIN
  379.   TWindow.Init(Bounds,ATitle,ANumber); { Call ancestor's constructor }
  380.   { Call the Mortgage object's constructor using dialog data: }
  381.   WITH InitMortgageData DO
  382.     Mortgage.Init(PrincipalData,
  383.                   InterestData / 100,
  384.                   PeriodsData,
  385.                   12);
  386.  
  387.   { Here we set up a window with *two* interiors, one scrollable, one }
  388.   { static.  It's all in the way that you define the bounds, mostly:  }
  389.   GetClipRect(Bounds);             { Get bounds for interior of view  }
  390.   Bounds.Grow(-1,-1);      { Shrink those bounds by 1 for both X & Y  }
  391.  
  392.   { Define a rectangle to embrace the upper of the two interiors:     }
  393.   R.Assign(Bounds.A.X,Bounds.A.Y,Bounds.B.X,Bounds.A.Y+4);
  394.   TopInterior := New(PMortgageTopInterior,Init(R));
  395.   TopInterior^.Mortgage := @Mortgage;
  396.   Insert(TopInterior);
  397.  
  398.   { Define a rectangle to embrace the lower of two interiors: }
  399.   R.Assign(Bounds.A.X,Bounds.A.Y+5,Bounds.B.X,Bounds.B.Y);
  400.  
  401.   { Create scroll bars for both mouse & keyboard input: }
  402.   VScrollBar := StandardScrollBar(sbVertical + sbHandleKeyboard);
  403.   { We have to adjust vertical bar to fit bottom interior: }
  404.   VScrollBar^.Origin.Y := R.A.Y;       { Adjust top Y value }
  405.   VScrollBar^.Size.Y := R.B.Y - R.A.Y; { Adjust size }
  406.   { The horizontal scroll bar, on the other hand, is standard: }
  407.   HScrollBar := StandardScrollBar(sbHorizontal + sbHandleKeyboard);
  408.  
  409.   { Create bottom interior object with scroll bars: }
  410.   BottomInterior :=
  411.     New(PMortgageBottomInterior,Init(R,HScrollBar,VScrollBar));
  412.   { Make copy of pointer to mortgage object: }
  413.   BottomInterior^.Mortgage := @Mortgage;
  414.   { Set the limits for the scroll bars: }
  415.   BottomInterior^.SetLimit(80,InitMortgageData.PeriodsData);
  416.   { Insert the interior into the window: }
  417.   Insert(BottomInterior);
  418. END;
  419.  
  420.  
  421. PROCEDURE TMortgageView.HandleEvent(Var Event : TEvent);
  422.  
  423. BEGIN
  424.   TWindow.HandleEvent(Event);
  425.   IF Event.What = evCommand THEN
  426.     BEGIN
  427.       CASE Event.Command OF
  428.         cmExtraPrin    : ExtraPrincipal;
  429.         cmPrintSummary : PrintSummary;
  430.       ELSE
  431.         Exit;
  432.       END; { CASE }
  433.       ClearEvent(Event);
  434.     END
  435.   ELSE
  436.     IF Event.What = evBroadcast THEN
  437.       CASE Event.Command OF
  438.         cmCloseBC : Done
  439.       END; { CASE }
  440. END;
  441.  
  442.  
  443. PROCEDURE TMortgageView.ExtraPrincipal;
  444.  
  445. VAR
  446.   Control : Word;
  447.   ExtraPrincipalData : ExtraPrincipalDialogData;
  448.  
  449. BEGIN
  450.   { Execute the "extra principal" dialog box: }
  451.   Control := Desktop^.ExecView(HouseCalc.ExtraDialog);
  452.    IF Control <> cmCancel THEN  { Update the active mortgage window: }
  453.      BEGIN
  454.        { Get data from the extra principal dialog: }
  455.        HouseCalc.ExtraDialog^.GetData(ExtraPrincipalData);
  456.        Mortgage.Payments^[ExtraPrincipalData.PaymentNumber].ExtraPrincipal :=
  457.          ExtraPrincipalData.ExtraDollars;
  458.        Mortgage.Recalc;   { Recalculate the amortization table... }
  459.        Redraw;            { ...and redraw the mortgage window     }
  460.      END;
  461. END;
  462.  
  463.  
  464. PROCEDURE TMortgageView.PrintSummary;
  465.  
  466. BEGIN
  467. END;
  468.  
  469.  
  470. DESTRUCTOR TMortgageView.Done;
  471.  
  472. BEGIN
  473.   Mortgage.Done;  { Dispose of the mortgage object's memory }
  474.   TWindow.Done;   { Call parent's destructor to dispose of window }
  475. END;
  476.  
  477.  
  478.  
  479. BEGIN
  480.   HouseCalc.Init;
  481.   HouseCalc.Run;
  482.   HouseCalc.Done;
  483. END.