Program Gantt;
{purpose: creates and prints GANTT charts
 author: Karen Stallworth
 date: April, 1987
 environment: IBM PC/XT/AT compatible under DOS 3.1+
              Color Graphics Adapter
              written in Borland Turbo Pascal v. 3.02A in text mode
    optional: Sideways printer utility  (c) Funk Software, Inc.
              Mouse: Microsoft or Mouse systems
 external I/0:
         uses: Command.Com
               (optional)Sideways.Com printer program (or SWC.Com)
        makes: filename.PRN
               filename.DTA
 modules used: Initialize_Main
               Enter_Choice
               Initialize_Main
               Load
               Enter_Info
               Time_Spans
               Save
               Print_Chart
               Housekeep
 comments: this program needs access to the following:
               1) Command.Com
  (optional):  2) Sideways.Com  (sideways printing utility)

     For high quality wide charts, Sideways is necessary.
 Sidewriter (SWC.COM), a shareware program, is also available, but
 is for Epson Compatible printers only, and cannot print extended
 ASCII characters.
 A narrow chart is also available. This works for printers with the
   IBM extended ASCII characters.

     This program cannot run unless it is Compiled with the 'mAximum
 free dynamic memory' set to about 600.  If it bombs, try setting it
 to 1000 and recompiling.

     Note that a gantt chart saved under Gantt.Com compiled under one
 version of Turbo cannot be loaded for update into Gantt.Com compiled
 under a different version of Turbo.  It can, however, be printed.

     The resulting .PRN file may be edited using a word processing pro-
 gram in non-document mode, and optionally printed using
 Sideways.

     The external procedure MOUSE.TBP was used for mouse interrupts.
 it was provided by Mouse Systems with the PC Mouse.  Several alter-
 natives are available: Dos interrupts can be used (see Byte Sept.
 1985), or the source code for such a procedure is given in the
 Microsoft Mouse Programmer's manual.   }

label StartOver; {goto on abandoned chart}
const
     L = 53;  {Length of printed page in # of lines--53 gives at
               least one line between each task in printed chart}
     Maxdates = 37; {Maximum number of date increments which can
              possibly be displayed on a chart.
              Maxdates = (W div 3) - 1)       }
     Tmax = 20; {max # of tasks--limited by screen height of 25 rows
                 less 2 rows dates, top & bottom border, and message
                 line }
     default = 0;  {default drive}
     C1 = yellow;  {Menu Colors}
     B1 = blue;
     C2 = blue;    {directions}
     B2 = cyan;
     C3 = blue;    {chart colors}
     B3 = cyan;
     C4 = white;   {done box}
     B4 = red;
     C5 = blue;    {input area}
     B5 = white;
blank='                                                                                                                 ';
type
     Str255 = string[255];
     Str150 = string[150];
     Str66 = string[66];
     Str12 = string[12];
     TitleType = array[1..6] of string[30];{3 titles, 3 footings}
     BEDateType = array[1..2] of record
                        year,month,day:string[2];
                end;
     TaskType = record              {chart info for saving in .DTA}
                D:BEDateType;
                T:TitleType;
                N:array [1..Tmax] of record
                      name:string[30];              {task name}
                      TimeSpan:Str150;       {task duration}
                end; {N}
             end; {TaskType}
     MatrixType = array [1..25] of record {input matrix}
                  x,y:integer;
                  st:Str150;
                end;
     ChartType = array[1..L] of Str150;     {chart}
     DateType = record
                yr,mo,da:integer;
                end;
     Days_per_mo = array[1..12] of integer;
            {for typed constant holding # of days in each month}
     RegType = record                            {register type}
               Ax,Bx,Cx,Dx,Bp,Di,Si,Ds,Es,Flags:integer;
               end;
     MonthType = array[1..12] of string[3];

const   {typed constants}
     Days: Days_per_mo = (31,29,31,30,31,30,31,31,20,31,30,31);
                             {# of days in each month (usually)}
     Months: MonthType = ('Jan','Feb','Mar','Apr','May','Jun',
               'Jul','Aug','Sep','Oct','Nov','Dec');

var
     W:integer;            {width of paper--114 for SideWays,
                                            100 for SideWriter,
                                             79 for narrow chart}
     comspec: str66;       {full path name for Command.Com}
     Task:TaskType;        {task names, duration}
     M1,M2,M3,M4:integer;  {mouse parameters}
     Register:RegType;     {registers for DOS calls}
     Mice: boolean;        {mouse installed}
                {input parameters:}
     F:integer;            {maximum field length}
     M:integer;            {total # of entries, incl DONE}
     Temp:MatrixType;      {temporary input matrix}
     Abandon,           {if <Esc>, the can abandon present chart}
     Narrow: boolean;   {80-column chart}
     ch:char;              {input character}

Procedure Mouse(var M1,M2,M3,M4:integer);
External 'MOUSE.TBP';

function Wide:boolean;
{purpose: Wide=true if 80 column screen
              =false if 40 column screen
 global data: Register
 called by: Set_Mouse
            Move_Mouse
            Move_Cursor }

var ah:byte; {hi byte of Ax register}

begin {Wide}
      ah:=$F;
      with Register do
           begin
                ax:=ah shl 8;
                intr($10,register);
           end;
      if register.ax > 20000 then wide:=true else wide:=false;
end; {Wide}

Procedure Hide_Cursor;

{note: this does not work on all systems}
{global data: register}

var ah,       {hi byte of ax register}
    ch,       {top cursor line; hi byte of cx register}
    cl:byte;  {bottom cursor line; lo byte of cx register}
begin {Hide_Cursor}
      ah:=1;
      ch:=8;
      cl:=0;
      with register do
           begin
                ax:=ah shl 8;
                cx:=ch shl 8 + cl;
                intr($10,register);
           end;
end; {Hide_Cursor}

Procedure Show_Cursor;
{note: this does not work on all computer systems}
{global data: register}

var ah,       {hi byte of ax register}
    ch,       {top cursor line; hi byte of cx register}
    cl:byte;  {bottom cursor line; lo byte of cx register}

begin {Show_Cursor}
      ah:=1;
      ch:=6;
      cl:=7;
      with register do
           begin
                ax:=ah shl 8;
                cx:=ch shl 8 + cl;
                intr($10,register);
           end;
end; {Show_Cursor}
{- - - - - - - - - - - - - - - - - - - - - - - - - - - - -}
{functions to read/write to multiple text pages}
{MUCH adapted and improved from 'Turbo Pascal Tricks and Tips' by
 Sgonia and Warner}

procedure Page(al:byte);
      {al: page #;low byte of ax register}

{purpose: display page #al (0..3)
 external I/O:none
 global data: Register
 Called by: Overlap
            Display
 Modules used: none }

var ah: byte;  {hi byte of Ax register}

begin {Page}
      ah:=5;
      with register do
           begin
             ax:= ah shl 8 + al;
             intr($10,register);
           end;
end; {Page}

function WhichPage:byte;
{purpose: returns the page # visible (0..3 in 80 column
                                   or 0..7 in 40 column)
 external I/O:none
 global data: Register
 Called by: ESC
            OverLap
            Move_Mouse
            Move_Cursor2
            Move_Up2
            Move_Down2
 Modules used: none  }

var ah:byte;  {hi byte of Ax register}

begin {WhichPage}
    ah:=15;
    with register do begin
         ax:=ah shl 8;
         intr($10,register);
         WhichPage:=hi(bx);
    end;
end;  {WhichPage}

procedure GotoSXY(bh,dl,dh:byte);
          {bh: page #; hi byte of bx register
           dl: x; lo byte of dx register
           dh: y; hi byte of dx register}

{purpose: equivalent to GOTOXY(x,y), but with page #
 external I/O:none
 global data: Register
 Called by: Display
            Input_Data2
            ESC
            Move_Up2
            Move_Down2
            Move_Cursor2
 Modules used: none }

var ah:byte; {hi byte of ax register}

begin {GotoSXY}
      ah:=2;
      dl:=dl-1;{this lets x:1..80}
      dh:=dh-1;{this lets y:1..25}
      with register do begin
           ax:=ah shl 8;
           bx:=bh shl 8;
           dx:=dh shl 8 + dl;
           intr($10,register);
      end;
end; {GotoSXY}

function WhereSX(bh:byte):byte;
         {bh: page #; hi byte of bx register}
{purpose: equivalent to WHEREX, with the addition of page #
 external I/O:none
 global data: Register
 Called by: Move_Mouse
            Print
            Input_Data2
 Modules used: none }

var ah: byte;

begin {whereSX}
      ah:=3;
      with register do begin
           ax:=ah shl 8;
           bx:=bh shl 8;
           intr($10,register);
           whereSX:=lo(dx)+1; {so that x:1..80}
      end;
end; {whereSX}

function WhereSY(bh:byte):byte;
         {bh: page #; hi byte of bx register}
{purpose: equivalent to WHEREY, with the addition of page #
 external I/O:none
 global data: Register
 Called by: Input_Data2
            MoveMouse
 Modules used: none }

var ah: byte;

begin {whereSY}
      ah:=3;
      with register do begin
           ax:=ah shl 8;
           bx:=bh shl 8;
           intr($10,register);
           whereSY:=hi(dx)+1; {add 1 so that y:1..25}
      end;
end; {whereSX}

procedure Print(bh:byte; var wt);
          {bh: page #; hi byte of bx register
           wt: address of string to print}

{purpose: similar to WRITE, with the addition of page #
 external I/O:none
 global data: Register
 Called by: Display
            Esc
            Input_Data2
 Modules used: WhereSX
               WhereSY
               GotoSXY }

var ah,        {hi byte of ax register}
    al,        {lo byte of ax register}
    lf,        {lo byte of flag register}
    bl:byte;   {lo byte of bx register--for attribute}
    sg,        {segment}
    os: integer;{offset}
    z:byte;

begin {Print}
   if wide then z:=3 else z:=7;
   if (bh in [0..z]) then begin
                      {ck for valid page #}

      ah:=10;
      sg:=seg(wt);
      os:=ofs(wt);
      with register do begin
           for lf:=1 to mem[sg:os] do begin
               al:= mem[sg:os +lf];
               ax:=ah shl 8 + al;
               bx:=bh shl 8;
               cx:=1;
               intr($10,register);
               if WhereSX(bh)=80 then {if at right edge of screen}
                  gotoSXY(bh,1,WhereSY(bh)+1)
               else gotoSXY(bh,WhereSX(bh)+1,Wheresy(bh));
           end; {for}
      end; {with register}
   end; {if}
end; {Print}
{- - - - - - - - - - - - - - - - - - - - - - - - - - - - - }
procedure Error_Ck(var error:boolean);

{purpose: check for I/O errors
 external I/O: none
 global data: none
 called by: Save
            Load
 modules used: GotoSxy
               Print
               WhichPage  }

var err:integer;
    message1,message2:string[40];
    p:byte;

begin {Error_Ck}
    p:=WhichPage;
    err:=IOResult;
    message1:='';message2:='';

    if err<>0 then begin
      TextMode(C80);
      case err of
         $01: message1:='File does not exist'; {should not occur}

         $90,$99: begin
               message1:='This file is not is not in Gantt format';
               message2:='Select a different file';
             end;
         $F0,$F1:
              message1:='Disk is full:  You have lost your chart.';
         $FF: begin
              message1:='Do not change disks on me while ';
              message1:=message1+'I am working!';
              message2:='1) insert disk  2) press <space> ';
              message2:=message2 + '3) <C>hange directory';
             end;
      end; {case}
      gotoSxy(p,20,20);
      print(p,message1);
      gotoSxy(p,20,21);
      print(p,message2);
      read(kbd,ch);
      error:=true;
   end; {if err}
end; {Error_Ck}
{- - - - - - - - - - - - - - - - - - - - - - - - - - - - - }
{mouse procedures used in capturing mouse input}

Procedure Set_Mouse;
{Initialize mouse for use in an input procedure (text mode):
   Limit mouse cursor range, turn text cursor off,
   show mouse cursor.
 external I/O: none;
 global data: F,M constants defined in calling module.
              M1..M4
              Temp
 called by: Input_Info
            Enter_Choice
 modules used: Wide
               Mouse
               Hide_Cursor             }
var z:integer;

begin {Set_Mouse}
if Wide then z:=8 else z:=16;

  M1:=7;  {set horizontal range}
  M3:=(temp[1].x-1)*z;               {min x}
  if M>=3 then M4:=(temp[3].x+F-2)*z {max x}
  else M4:=(temp[1].x+F-2)*z;
  Mouse(M1,M2,M3,M4);

  M1:=8;  {set vertical range}
  M3:=(temp[1].y-1)*8;          {min y}
  M4:=(temp[M].y-1)*8;          {max y}
  Mouse(M1,M2,M3,M4);

  Hide_Cursor;

  M1:=10; {set mouse cursor}
  M2:=0;
  M3:=$00FF;
  M4:=$1400;
  Mouse(M1,M2,M3,M4);

  M1:=1;  {show mouse cursor}
  Mouse(M1,M2,M3,M4);

end; {Set_Mouse}

Procedure Reset_Mouse;
{resets text cursor on and mouse limits to the screen

 external I/O: none;
 global data: M1..M4
 called by: Input_Info
            Enter_Choice
 modules used: Mouse
               Show_Cursor       }

begin {Reset_Mouse}
M1:=2; {hide mouse}
  Mouse(M1,M2,M3,M4);

M1:=10; {turn on text cursor}
  M2:=1;
  M3:=6;
  M4:=7;
  Mouse(M1,M2,M3,M4);

M1:=7; {set Min/Max Horizontal position}
  M3:=0;
  M4:=639;
  Mouse(M1,M2,M3,M4);

M1:=8; {set Min/Max Vertical position}
  M3:=0;
  M4:=199;
  Mouse(M1,M2,M3,M4);
end; {Reset_Mouse}

Procedure Move_Mouse;
{move mouse cursor to location of hdwe cursor (text mode)
 Wide=true if 80 columns
     =false if 40 columns
 external I/O: none;
 global data: M1..M4
 called by: Input_Info
            Enter_Choice
            Input_Info2
 modules used: Wide
               Mouse
               WhereSx
               WhereSy
               WhichPage }

var z:integer;

begin {Move_Mouse}
  if Wide then z:=8 else z:=16;

  M1:=2; Mouse(M1,M2,M3,M4); {hide mouse}

  M1:=4; {move mouse cursor to new location}
  M3:=(whereSX(WhichPage)-1)*z;
  M4:=(whereSy(WhichPage)-1)*8;
  Mouse(M1,M2,M3,M4);

  M1:=1; Mouse(M1,M2,M3,M4); {show mouse}

end; {Move_Mouse}

Procedure Move_Cursor(var R:integer);
{move text cursor to correspond with mouse cursor (text mode)
 Wide=true if 80 columns
     =false if 40 columns
 external I/O: none;
 global data: M
              M1..M4
              Temp
 called by: Input_Info
            Enter_Choice
 modules used: Wide
               Mouse             }

var i,j,z:integer;
    diff:integer;{cursor location - nearest line}
    Mindiff:integer;

begin {Move_Cursor}
  if Wide then z:=8 else z:=16;

{get mouse position}
  M1:=3;
  Mouse(M1,M2,M3,M4);

{calculate nearest valid input line}
  Mindiff:=200;
  for i:=1 to M do begin
      diff:=abs(M4-(temp[i].y-1)*8);
      if diff<Mindiff then begin
         R:=i;
         Mindiff:=diff;
      end; {if}
  end;

{move cursor there}
  if R=M then gotoxy(temp[M].x,temp[M].y)
  else gotoxy(M3 div z +1,temp[R].y);

end; {Move_Cursor}

Procedure Move_Up(var R:integer);
{when up arrow pressed, moved text and mouse cursor up
 external I/O: none;
 global data: M
              Temp
 called by: Input_Info
            Enter_Choice
 modules used: Move_Mouse             }

begin {Move_Up}
  if R <= 1 then R:=M else R:=R-1;
  gotoxy(temp[R].x,temp[R].y);
  if mice then Move_Mouse; {move Mouse cursor to text cursor}
end; {Move_Up}

Procedure Move_Down(var R:integer);
{when down arrow pressed, moved text and mouse cursor up
 external I/O: none;
 global data: M
              Temp
 called by: Input_Info
            Enter_Choice
 modules used: Wide             }

begin {Move_Down}
  if R >= M then R:=1 else R:=R+1;
  gotoxy(temp[R].x,temp[R].y);
  if mice then Move_Mouse; {move Mouse cursor to text cursor}
end; {Move_Down}

Procedure Move_Cursor2(var Pg:MatrixType;{var for efficiency only}
                       var R:integer);
{move text cursor to correspond with mouse cursor (text mode)
 Wide=true if 80 columns
     =false if 40 columns
 external I/O: none;
 global data: M
              M1..M4
 called by: Input_Info2
 modules used: Wide
               Mouse
               GotoSxy
               WhichPage        }

var i,x:integer;
    diff:integer; {cursor location - nearest line}
    Mindiff:integer;
    p:byte; {current page}

begin {Move_Cursor2}
  p:=WhichPage;
{get mouse position}
  M1:=3;
  Mouse(M1,M2,M3,M4);

{calculate nearest valid input line}
  Mindiff:=200;
  for i:=1 to M do begin
      diff:=abs(M4-(Pg[i].y-1)*8);
      if diff<Mindiff then begin
         R:=i;
         Mindiff:=diff;
      end; {if}
  end;

{calculate horizontal location}
  if R=M then x:=Pg[M].x
  else begin
       x:=M3 div 8 +1;
       if x<Pg[R].x then x:=Pg[R].x {keep cursor on the chart}
       else if x > Pg[R].x + F-1 then x:= Pg[R].x + F-1;
   end; {else}

{move cursor there}
   gotoSxy(p,x,Pg[R].y);

end; {Move_Cursor2}

Procedure Move_Up2(var Pg:MatrixType;{var for efficiency only}
                   var R:integer);
{when up arrow pressed, moved text and mouse cursor up

 external I/O: none;
 global data: M
 called by: Input_Info2
 modules used: Move_Mouse
               gotoSxy    }

var p:byte;

begin {Move_Up2}
  p:=WhichPage;
  if R <= 1 then begin
     R:=M;
     gotoSxy(p,Pg[M].x,Pg[M].y);
  end
  else begin
     R:=R-1;
     if R=M-1 then gotoSxy(p,Pg[R].x,Pg[R].y)
     else gotoSxy(p,WhereSx(p),Pg[R].y);
  end;
  if mice then Move_Mouse; {move Mouse cursor to text cursor}
end; {Move_Up2}

Procedure Move_Down2(var Pg:MatrixType; {var for efficiency only}
                     var R:integer);
{when down arrow pressed, moved text and mouse cursor up

 external I/O: none;
 global data: M
 called by: Input_Info2
 modules used: Wide
               gotoSxy   }

var p:byte;

begin {Move_Down2}
  p:=whichpage;

  if R=M then R:=1
  else R:=R+1;

  if (R=M) or (R=1) then gotoSxy(p,Pg[R].x,Pg[R].y)
  else gotoSxy(p,WhereSx(p),Pg[R].y);

  if mice then Move_Mouse; {move Mouse cursor to text cursor}
end; {Move_Down2}
{- - - - - - - - - - - - - - - - - - - - - - - - - - - - - }
procedure ESC;
{purpose: sets 'ABANDON' flag which returns user to Main Menu
          when <Esc> key is pressed
 external I/O: none
  global data: Abandon
  called by: Input_Data
             Enter_Time_Spans
             Input_Filename

  modules used: WhichPage
                GotoSxy
                 Print    }

var message:String[40];
    p:byte; {current page}

begin  {ESC}
     p:=WhichPage;
     message:='ABANDON CURRENT CHART? (Y/N)';
     gotoSxy(P,20,24);
     print(P,message);
     repeat
           read(kbd,ch);
           ch:=upcase(ch);
           if ch='Y' then abandon:=true;
     until (ch='Y') or (ch='N');
     message:='                            ';
     gotoSxy(P,20,24);print(p,message);
end; {ESC}
{- - - - - - - - - - - - - - - - - - - - - - - - - - - - - }
Function Comspec_String : str66;

  {function written by Mike McCall
              Western Illinois University
              Macomb, IL
              October 1987       }

{purpose: returns the full path name of command.com
 global parameters: none
}


   var
       environment_string_segment : integer absolute cseg:$2C;
       temp : string[255];
       comspec_offset : integer;

   begin {Comspec_String}
       temp[0] := chr(255);
       move (mem[environment_string_segment : 0], temp[1], 255);
       comspec_offset := pos('COMSPEC=', temp);
       while (comspec_offset = 0) and
             (environment_string_segment < cseg) do begin
          environment_string_segment:=environment_string_segment+15;
          move (mem[environment_string_segment : 0], temp[1], 255);
          comspec_offset := pos('COMSPEC=', temp);
       end;  {while comspec_offset...}
       if comspec_offset = 0 then
           comspec_string := ''
       else begin
           move (mem[environment_string_segment:
                          comspec_offset - 1],temp[1], 255);
           delete (temp, pos(chr(0), temp), 255);
           delete (temp, 1,8);
           comspec_string := temp;
       end;
   end; {Comspec_String}

procedure spawn(line: Str255 {name of child process});
{purpose: runs the child process named in 'line', then returns to this
          parent program.
 global parameters: none
}


      const
         cr = #13;
      var
         regs: record
                  ax,bx,cx,dx,
                  bp,si,di,ds,es,
                  flags:          integer;
               end;
         child: str66;
         parms: str255;
         parmblock: record
                       seg_env:    integer;
                       addr_parms: ^integer;
                    end;
         ext: string[4];
         comspec: Str66;

      begin {spawn}

      comspec:=Comspec_String;
      if (pos('.',line) = 0) or (pos(' ',line) < pos('.',line)) then
         line := comspec + ' /c ' + line
      else
         begin
         ext := copy(line,pos('.',line),4);
         if (ext = '.bat') or (ext = '.BAT') then
            line := comspec + ' /c ' + line;
         end;

      {seperate child process and its parameters}
      if pos(' ',line) = 0 then
         begin
         child := line + #0;
         parms := cr;
         end
      else
         begin
         child := copy(line,1,pred(pos(' ',line))) + #0;
         parms := copy(line,pos(' ',line),255) + cr;
         end;
      parms[0] := pred(parms[0]);

      with regs do
         begin
         with parmblock do
            begin
            {fill parmblock}
            seg_env := memw[cseg:$002c];
            addr_parms := addr(parms);
            end;

         {spawn child program}
         ds := seg(child[1]);
         dx := ofs(child[1]);
         es := seg(parmblock);
         bx := ofs(parmblock);
         ax := $4b00;
         msdos(regs);

         {report any error}
         if (flags and 1) <> 0 then
            begin
            gotoxy(1,25);
            write('Operation Failed - Error ',ax);
            delay(1800);
            end;
         end;
      end; {spawn}

{ - - - - - - - - - - - - - - - - - - - - - - - - - - - }

function GetFile: str66;
{ author: Denise Walters
 date: 4/87
 modified by: Karen Stallworth
 date: 4/87
 modifications: searches for .DTA files only
                added 'Hide_Cursor' and 'Show_Cursor' calls
                added local color definitions

 purpose: displays a list of the files ending in '.DTA', from which
          the user may select, using arrow keys.
          User has the option of changing path/directory.
 external I/O: program reads a disk directory
 global data: none
 called by: Print
            Load
            Input_File
 modules used: Hide_Cursor
               Show_Cursor
               (others internal to the procedure)  }

const lastfile: string[8] = '        ';

type
   str8 = string[8];
   CellPointer = ^CellType;
   CellType = record
      name: string[8];
      prev: CellPointer;
      next: CellPointer;
   end;

var
   done,
   found:       boolean;

   cell,
   firstcell,
   lastcell:    CellPointer;

   ch:          char;

   dta:         record
                   junk1: array[1..30] of char;
                   name:  array[1..8] of char;
                   junk2: array[1..5] of char;
                   end;

   tcolor,
   tback,
   i,
   foundcount,
   wincount,
   y:           integer;

   regs:        record
                   ax,bx,cx,dx,bp,si,di,ds,es,flags: integer;
                   end;

   HeapTop:     ^integer;

   filename,
   temp:        str8;
   path,
   search_str:  str66;

procedure reverse;

   begin
   textcolor(tback);
   textbackground(tcolor);
   end;

procedure norm;

   begin
   textcolor(tcolor);
   textbackground(tback);
   end;

   procedure ChangePath;

      var
         OK: boolean;
         ch: char;
         path: string[66];

      begin
      window(1,1,80,25);
      window(1,1,80,1);
      OK := false;
      while not OK do
         begin
         gotoxy(15,1);
         norm;
         ClrEol;
         read(path);
         if length(path)=1 then path:=path+':';
         {$I-} ChDir(path); {$I+}
         OK := (IOResult = 0);
         end;
      GetDir(default,path);
      gotoxy(15,1);
      reverse;
      write(path);
      end; {ChangePath}

   procedure FillWindow;
      var
         y: integer;

      begin
      norm; ClrScr;

      {find lastfile}
      found := false;
      cell := firstcell;
      if cell <> nil then
         temp := cell^.name;
      while (not found) and (cell <> nil) do
         begin
         if cell^.name = lastfile then
            begin
            found := true;
            temp := cell^.name;
            end;
         cell := cell^.next;
         end;

      {put first twenty entries in window}
      cell := firstcell;
      lastcell := firstcell;
      found := false;
      wincount := 0;
      y := 20;
      while (wincount < 20) and (cell <> nil) do
         begin
         if cell^.name = temp then
            begin
            reverse;
            found := true;
            y := wincount + 1;
            lastcell := cell;
            end;
         gotoxy(1,wincount+1); write(cell^.name);
         wincount := wincount + 1;
         norm;
         cell := cell^.next;
         end;

      {scroll window until lastfile is highlighted}
      while (not found) and (cell <> nil) do
         begin
         gotoxy(1,1); DelLine;
         if cell^.name = temp then
            begin
            reverse;
            found := true;
            end;
         gotoxy(1,20); write(cell^.name);
         end;
      norm;
      gotoxy(9,y);
      cell := lastcell;
      end; {FillWindow}

   function fixed(workstring: str8): str8;

      var
         i: integer;

      begin
      i := pos('.',workstring);
      if i > 0 then
         delete(workstring,i,8);
      fixed := workstring + '        ';
      end; {fixed}

   procedure LoadDir;

      begin
      with regs do
         begin
         ds := seg(dta);
         dx := ofs(dta);
         ax := $1a00;
         MsDos(regs);      {set dta address}

         search_str := '*.DTA' + #0;
         ds := seg(search_str);
         dx := ofs(search_str) + 1;  {skip length byte}
         cx := 0;                    {normal search attribute}
         ax := $4e00;
         MsDos(regs);                {find first match}
         firstcell := nil;
         if ax = 0 then     {match found}
            begin
            new(firstcell);
            firstcell^.name := fixed(dta.name);
            firstcell^.prev := nil;
            firstcell^.next := nil;
            lastcell := firstcell;

            ax := $4f00;
            MsDos(regs);   {find next match}
            while ax = 0 do
               begin
               new(cell);
               cell^.name := fixed(dta.name);
               cell^.prev := lastcell;
               cell^.next := nil;
               lastcell^.next := cell;
               lastcell := cell;

               ax := $4f00;
               MsDos(regs);   {find next match}
               end;
            end;
         end;
      end; {LoadDir}

   procedure MoveUp;
      var
         y: integer;

      begin
      y := WhereY;
      if cell^.prev <> nil then
         begin
         if y > 1 then
            begin
            gotoxy(1,y); norm; write(cell^.name);
            cell := cell^.prev;
            gotoxy(1,y-1); reverse; write(cell^.name);
            end
         else
            begin
            gotoxy(1,1); norm; write(cell^.name);
            gotoxy(1,1); InsLine;
            cell := cell^.prev;
            gotoxy(1,1); reverse; write(cell^.name);
            end;
         end;
      end;

   procedure MoveDown;
      var
         y: integer;

      begin
      y := WhereY;
      if cell^.next <> nil then
         begin
         if y < 20 then
            begin
            gotoxy(1,y); norm; write(cell^.name);
            cell := cell^.next;
            gotoxy(1,y+1); reverse; write(cell^.name);
            end
         else
            begin
            gotoxy(1,y); norm; write(cell^.name);
            gotoxy(1,1); DelLine;
            cell := cell^.next;
            gotoxy(1,y); reverse; write(cell^.name);
            end;
         end;
      end;

   procedure SetUpWindow;
      begin
      write('Current Path: ');
      GetDir(default,path);
      reverse;
      write(path);
      norm;
      gotoxy(54,11);  write('C - Change Path');
      gotoxy(54,13);  write(#24,' - Move Up');
      gotoxy(54,14);  write(#25,' - Move Down');
      gotoxy(51,15);  write('<cr> - Choose File');
      gotoxy(52,16);  write('ESC - Return to Menu');
      gotoxy(15,3);   write(chr(201));
      gotoxy(26,3);   write(chr(187));
      gotoxy(15,24);  write(chr(200));
      gotoxy(26,24);  write(chr(188));
      for i := 16 to 25 do
         begin
         gotoxy(i,3);  write(chr(205));
         gotoxy(i,24); write(chr(205));
         end;
      for i := 4 to 23 do
         begin
         gotoxy(15,i);  write(chr(186));
         gotoxy(26,i);  write(chr(186));
         end;
      window(17,4,25,23);
      end; {SetUpWindow}


begin {GetFile}
Tcolor:=C2;
Tback:=B2;

mark(HeapTop);
Hide_Cursor;
norm; ClrScr;
SetUpWindow;
LoadDir;
FillWindow;
done := false;
while not done do
   begin
   read(kbd,ch);
   case ch of
      'C','c': begin
               ChangePath;
               window(17,4,25,23);
               norm; ClrScr;
               LoadDir;
               FillWindow;
               end;
      #13: begin
           if cell <> nil then
              begin
              filename := cell^.name;
              lastfile := filename;
              end
           else
              filename := '';
           done := true;
           end;
      #27: if keypressed then
              begin
              read(kbd,ch);
              case ch of
                 #72: if cell <> nil then
                         MoveUp;
                 #80: if cell <> nil then
                         MoveDown;
                 end;
              end
           else
              begin
              filename := '';
              done := true;
              end;
      end;
   end;
release(HeapTop);
if (filename <> '') then
   begin
   while filename[length(filename)] = ' ' do
      delete(filename,length(filename),1);
   end;
getfile := filename;
window(1,1,80,25);
norm; ClrScr;
Show_Cursor;
end; {GetFile}
{- - - - - - - - - - - - - - - - - - - - - - - - - - - - - }
Procedure Initialize_Main (var filename:str12);

{purpose: display credits & directions; initialize mouse
 external I/O: check for existence of needed files:
                           Gantt.Com,Sideways.Com
 global data: Mice         output: if mouse installed
              M1,M2,M3,M4  internal use; mouse parameters
              Abandon
 called by: Main
 modules used: Mouse
               Error_Ck  }

var i:integer;
    fil:text;
    message: string[20];

begin {Initialize_Main}

{Display credits}
  TextMode(C40);
  TextColor(C1);
  TextBackground(B1);
  clrscr;
  gotoxy(8,8); write('A GANTT CHARTING PROGRAM');
  gotoxy(19,13); write('by');
  gotoxy(13,18); write('Karen Stallworth');
  TextColor(B1);ClrEol; {hide cursor}

{Initialize Mouse}
  M1:=0;   {Mouse reset}
  Mouse(M1,M2,M3,M4);
  if (M1<0) then Mice:=true else Mice:=false;

  if mice then begin
     M1:=15;  {set Mickey ratio}
     M3:=8;   {horizontal normal}
     M4:=50;  {vertical -low respose}
     Mouse(M1,M2,M3,M4);

  end; {if}

{Initialize filename}
  filename:='';


{more credits}
  TextMode(C40);
  TextBackground(B1);
  TextColor(C1);
  ClrScr;
  gotoxy(4,4); write('A program to construct and print');
  gotoxy(14,6); write('GANTT CHARTS');
  TextColor(B1);gotoxy(1,23);clreol; {hide cursor}
  for i:=1 to 10000 do if keypressed then exit;

  TextColor(B2);
  gotoxy(15,10); write('Environment:');
  TextColor(C1);
  gotoxy(10,11); write('IBM PC/AT compatibles');
  gotoxy(17,12); write('DOS 2.0+');
  gotoxy(10,13); write('IBM compatible printer');
  gotoxy(17,14); write('Sideways*');
  gotoxy(1,25); write('* printing utility (c)Funk Software Inc');
  TextColor(B1);clreol; {hide cursor}
  for i:=1 to 10000 do if keypressed then exit;

  TextColor(B2);
  gotoxy(9,17); write('done as a requirement for:');
  TextColor(C1);
  gotoxy(12,18); write('CS 566  Graphics II');
  gotoxy(16,19); write('Spring, 1987');
  gotoxy(8,20); write('Western Illinois University');
  TextColor(B1);clreol; {hide cursor}
  for i:=1 to 10000 do if keypressed then exit;

end;  {Initialize_Main}
{- - - - - - - - - - - - - - - - - - - - - - - - - - - - - }
Procedure Enter_Choice(var Choice:byte);
{purpose: display Main Menu and enter choice
 external I/O: none
 global data: Mice
              M1,M2,M3,M4
              Temp
 called by: Main
 modules used: Mouse
               Set_Mouse
               Move_Mouse
               Move_Cursor
               Move_Up
               Move_Down
               Reset_Mouse}

var
    Message:string[80];
    i:integer;
    R:integer; {current location}

begin {Enter_Choice}

  window(1,1,80,25);
  TextMode(C40);
  TextBackground(B1);
  clrscr;

{bottom line message}
  TextBackground(B2);
  TextColor(C2);
  Message:='Arrow keys';
  if Mice then Message:=Message + '/Mouse';
  Message:=Message + ' to move';
  i:=20 - length(message) div 2; {center message}
  Message:=copy(blank,1,i) + Message;
  gotoxy(1,25);
  insline;
  writeln(message);
  Message:='<cr>';
  if Mice then Message:=Message + '/Left Button ';
  Message:=Message + 'to select';
  InsLine;
  i:=20 - length(message) div 2; {center message}
  Message:=copy(blank,1,i) + Message;
  write(message);

{initialize input matrix}
  F:=1; {max line length}
  M:=4; {number of choices}

  for i:=1 to 4 do begin
      temp[i].x:=13;
      temp[i].y:=11+i*2;
      temp[i].st:='';
  end;
  temp[4].y:=20;

{Main Menu}
  TextBackground(B1);
  TextColor(C1);
  gotoxy(temp[1].x,temp[1].y-3);write('<< MAIN MENU >>');
  gotoxy(temp[1].x,temp[1].y);write('New Chart');
  gotoxy(temp[2].x,temp[2].y);write('Update Old Chart');
  gotoxy(temp[3].x,temp[3].y);write('Print Chart');
  gotoxy(temp[4].x,temp[4].y);write('Quit');

{Enter choice}
  choice:=0;
  R:=1;
  gotoxy(temp[1].x,temp[1].y);
  if mice then begin
     Move_Mouse; {move mouse cursor to text cursor}
     Set_Mouse;
  end;
  repeat
      if keypressed then begin
         read(kbd,ch);
         case ch of
           #27:begin {arrow keys}
               if keypressed then read(kbd,ch);
               if ch=#72 then Move_Up(R);
               if ch=#80 then Move_Down(R);
              end; {#27}
           #13:         {cr}
                choice:=R;
          end; {case}
       end; {if}
       if mice then begin
          Move_Cursor(R);

          M1:=5;  {get button press info}
          M2:=0;  {left button}
          Mouse(M1,M2,M3,M4);
          if M2 > 0 then Choice:=R;
    end; {if mice}

  until choice in [1..4];

if Mice then Reset_Mouse;
Textmode(C80);
end;  {Enter_Choice}
{- - - - - - - - - - - - - - - - - - - - - - - - - - - - - }
Procedure Initialize_New(var Filename:Str12);

{purpose: to initialize the above variables in a new chart
 external I/O: none
 Global Data: Task
 Called by: Main
 Modules used: none }

var i,j:integer; {loop control}

begin {Initialize_New}

  Textmode(C80);Textbackground(B2); {select wide or narrow chart}
  ClrScr; TextColor(C2);
  gotoxy(30,10); write('1. Wide chart');
  gotoxy(30,12);
        write('2. Narrow chart');
        gotoxy(19,23);
        write('IMPORTANT: DATA disk CANNOT have a write protect tab!');
        Hide_Cursor;
        gotoxy(30,17); write('Enter your selection:');
        textcolor(B2);textbackground(C2);write(' ');
  repeat read(kbd,ch) until ((ch='1') or (ch='2'));
  if ch='1' then begin
     w:=114;
     narrow:=false
  end
  else begin
       w:=79;
       narrow:=True
  end;

  for i:=1 to Tmax do begin  {initialize variables}
      Task.N[i].name:='';
      Task.N[i].TimeSpan:='';
  end;

  for i:=1 to 6 do Task.T[i]:='';

  for i:=1 to 2 do begin
      Task.D[i].year:='00';
      Task.D[i].month:='00';
      Task.D[i].day:='00';
  end;

  Filename:='';

end; {Initialize_New}
{- - - - - - - - - - - - - - - - - - - - - - - - - - - - - }
Procedure Input_Data;

{purpose: user inputs data, using cursor control keys or mouse.
 External I/O: none
 Global data: M1..M4
              Mice
              Temp,R,M
              Abandon
 Called by: Enter_Info
 Modules used: Mouse
               Set_Mouse
               Reset_Mouse
               Move_Mouse
               Move_Cursor
               Move_Up
               Move_Down
               ESC       }

var done:boolean;
    i:integer; {loop control}
    j:integer; {character within the line}
    R:integer; {current row}
    st:string[80]; {holds F spaces}

begin {Input_Data}

{initialize}
  done:=false;
  st:='';
  for i:=1 to F do st:=st+#32;

{write current contents of input matrix}
  for i:=1 to M-1 do begin
    gotoxy(temp[i].x,temp[i].y);
    write('':F);           {highlight input area}
    gotoxy(temp[i].x,temp[i].y);
    write(temp[i].st);
  end;

  j:=1;
  R:=1;
  gotoxy(temp[R].x,temp[R].y);
  if mice then begin
     Move_Mouse;
     Set_Mouse;
  end;
  repeat
    if keypressed then begin
      read(kbd,ch);
      case ch of
        #27: if not keypressed then begin {<esc> pressed}
                ESC;
                gotoxy(temp[R].x+j-1,temp[R].y);
             end {if <esc>}
           else begin
             read(kbd,ch);
             case ch of
               #72: begin
                    Move_Up(R);
                    j:=1; {first character}
                  end; {#72}
               #80: begin
                    Move_Down(R);
                    j:=1; {first character}
                  end; {#80}
               #75: {<-- nondestructive backspace}
                   if (j>1) and (R<M) then begin
                       j:= j-1;
                       gotoxy(wherex-1,wherey);
                       if mice then Move_Mouse;
                   end {if}
                   else if temp[R].y=temp[R-1].y {for Date entry}
                        then begin
                        Move_Up(R);
                        j:=1;
                      end;{else #75}
               #77: {--> nondestructive space}
                    if ((j<F) and (j<=length(temp[R].st))) then begin
                       j:=j+1;
                       gotoxy(wherex+1,wherey);
                       if mice then Move_Mouse;
                     end {if}
                     else if temp[R].y=temp[R+1].y {for Date entry}
                        then begin
                        Move_Down(R);
                        j:=1;
                     end; {#77}
             end; {case arrow key pressed}
            end; {#27}
        #13: {<cr>} if R=M then done:=true
           else begin
                Move_Down(R);
                j:=1;
              end; {<cr>}
        #8: {back space or ^H (destructive)} begin
                delete(temp[R].st,j,1);
                if j>1 then j:=j-1;
                M1:=2;Mouse(M1,M2,M3,M4);{Hide mouse}
                gotoxy(temp[R].x,wherey);
                write(st);
                gotoxy(temp[R].x,wherey);
                write(temp[R].st);
                gotoxy(temp[R].x+j-1,wherey);
                Move_Mouse;
                M1:=1;Mouse(M1,M2,M3,M4);{Show mouse}
            end; {#8}
     #32..#126: if R<M then begin {add ch to string if not DONE}
           if j<=length(temp[R].st) then delete(temp[R].st,j,1);
           insert(ch,temp[R].st,j);
           if j<F then j:=j+1;
           M1:=2;Mouse(M1,M2,M3,M4); {Hide mouse}
           gotoxy(temp[R].x,wherey);
           write(temp[R].st);
           gotoxy(temp[R].x+j-1,wherey);
           M1:=1;Mouse(M1,M2,M3,M4); {Show mouse}
           if mice then Move_Mouse;
          end; {add ch to string}
        end; {else case}

    end;{if keypressed}
    if mice then begin
       M1:=11;Mouse(M1,M2,M3,M4); {ck for mouse movement}
       if (M3<>0) or (M4<>0) then {then mouse has been moved}
            Move_Cursor(R); {move text cursor to mouse cursor}
       j:=(wherex - temp[R].x) + 1;
       if j>length(temp[R].st) then j:=length(temp[R].st)+1;

       M1:=5;  {get button press info}
       M2:=0;  {left button}
       Mouse(M1,M2,M3,M4);
       if M2 > 0 then if R=M then done:=true;
    end; {if mice}

  until done or abandon;
  if mice then Reset_Mouse;
  window(1,1,80,23); {retains message line}
end; {Input_Data}
{- - - - - - - - - - - - - - - - - - - - - - - - - - - - - }
{Following are procedures to convert dates to and from real
 representation in which elapsed time is easily calculated --
 this was harder than it looks!!}

procedure Date(var D:DateType;N:Real);
{purpose: converts a number N representing a date into day,
          month year
 in integer form
 external I/O: none
 global Data: none
 called by: Make_Chart }

var f, {year-1 if month<=2 else year}
    g, {month+13 if month <=2 else month+1}
    R1,R2: real; {remainder}
    leap: integer; {correction factor}

{all calculation in integer arithmetic; N is real because of size}

begin {Date}
      if frac((N-122.0)/1461) <0.01 then leap:=488 else leap:=487;
      f:=int((N*4)/1461)-1;
      R1:= N - int((f*1461)/4);
      g:= int((R1*5)/153);
      if round(R1)>leap then begin
         f:=int((N*4)/1461);
         R1:= N - int((f*1461)/4);
         g:= int((R1*5)/153);
      end;
      R2:= R1-int((g*153)/5);
      if round(R2)=0 then begin
         g:=g-1;
         R2:=R1-int((g*153)/5);
      end;

      D.da:=trunc(R2);

      if round(g)>13 then D.mo:=round(g)-13
      else D.mo:= round(g)-1;

      if D.mo>2 then D.yr:=round(f)-1900
      else D.yr:=round(f)+1-1900;

end; {Date}

procedure UnDate(D:DateType;var N:real);
{converts date into a representative number N.  N must be real
 because of its size.
 N:=(1461*f) div 4 + (153*g) div 5 + day
     where f represents the year (see below)
          g represents the month (see below)
 external I/O: none
 global Data: none
 called by: DateCk
            Make_Chart }

var f,g:real;

begin {UnDate}
    if D.mo<=2 then f:=1900 + D.yr-1 else f:=1900 + D.yr;
    if D.mo<=2 then g:=D.mo+13 else g:=D.mo+1;
    N:=int((1461*f)/4) + int((153*g)/5) + D.da;

end; {UnDate}
{- - - - - - - - - - - - - - - - - - - - - - - - - - - - - }
Procedure Date_CK(var OKDate:boolean;
                      MaxIncr:integer;
                  var Sdate,Edate:DateType);

{purpose: check for valid dates
 external I/O:none
 Global Data: Temp,M
 Called by: Enter_Info
 Modules used: UnDate  }

var da,yr,mo:array[1..2] of integer;
    err:integer;
    f,g:integer;{for calculation of elapsed time}
    i,j:integer;
    st:string[40]; {for error message}
    N1,N2:real; {for cking elapsed time between 2 dates}

begin  {Date_Ck}
OkDate:=true;

{create error message window}
    TextColor(B4);
    TextBackground(B1);
    window(40,temp[M].y+2,80,24);
    clrscr;

{delete any spaces from dates}
    for i:=1 to M-1 do begin
        j:=pos(' ',temp[i].st);
        if j>0 then delete(temp[i].st,j,1);
    end;

for i:=1 to 2 do begin
    if i=1 then st:='Start' else st:='Finish';

{convert date to integer form, checking for errors}
    val(temp[(i-1)*3+1].st,mo[i],err);
    if err<>0 then begin
       OkDate:=false;
       writeln('invalid ',st,' month');
       writeln(st,' mo=',mo[i],' da=',da[i],' yr=',yr[i]);
    end;

    val(temp[(i-1)*3+2].st,da[i],err);
    if err<>0 then begin
       OkDate:=false;
       writeln('invalid ',st,' day');
       writeln(st,' mo=',mo[i],' da=',da[i],' yr=',yr[i]);
    end;

    val(temp[(i-1)*3+3].st,yr[i],err);
    if err<>0 then begin
       OkDate:=false;
       writeln('invalid ',st,' year');
       writeln(st,' mo=',mo[i],' da=',da[i],' yr=',yr[i]);
    end;

  if not OKDate then exit;
{Check for valid month: 1..12}

     if (mo[i] <1) or (mo[i]>12) then begin
        OkDate:=false;
        writeln('invalid ',st ,' month');
        writeln(st,' mo=',mo[i],' da=',da[i],' yr=',yr[i]);
        exit;
     end;

{Check for valid day: 0<days<Days_per_Month[month]}
    j:=mo[i];
    if (da[i]<1) or (da[i] > Days[j]) then begin
       OkDate:=false;
       writeln('invalid ',st,' day');
       writeln(st,' mo=',mo[i],' da=',da[i],' yr=',yr[i]);
       exit;
    end;

{if day=29 and month=2 then ck for leap year}
    if (da[i]=29) and (mo[i]=2) then
       if (((yr[i]+1900) mod 4)<>0) then begin
          Okdate:=false;
          writeln('not leap year; Feb. 29 invalid');
          writeln(st,' mo=',mo[i],' da=',da[i],' yr=',yr[i]);
          exit;
       end;
end; {for}

{set Date}
     Sdate.da:=da[1];
     Sdate.mo:=mo[1];
     Sdate.yr:=yr[1];
     Edate.da:=da[2];
     Edate.mo:=mo[2];
     Edate.yr:=yr[2];

{Begin date < End date}
      UnDate(Sdate,N1);
      UnDate(Edate,N2);
      if N1>N2 then begin
              Okdate:=false;
              writeln('Start date is later than Finish date');
              writeln('Start  mo=',mo[1],' da=',da[1],' yr=',yr[1]);
              writeln('Finish mo=',mo[2],' da=',da[2],' yr=',yr[2]);
          exit;
          end;

{ ck for at least on week elapsed time}
        if N2-N1<7 then begin
           Okdate:=false;
           writeln('Allow at least one week elapsed time');
           writeln('      between start and finish dates');
           writeln('Start  mo=',mo[1],' da=',da[1],' yr=',yr[1]);
           writeln('Finish mo=',mo[2],' da=',da[2],' yr=',yr[2]);
           exit;
        end;

{calculate elapsed months; ck if it will fit on the chart}

    i:=(yr[2]-yr[1])*12+(mo[2]-mo[1])+1;
    if i>MaxIncr then begin
       Okdate:=false;
       writeln('Start and Finish dates too far apart');
       writeln('Start  mo=',mo[1],' da=',da[1],' yr=',yr[1]);
       writeln('Finish mo=',mo[2],' da=',da[2],' yr=',yr[2]);
    end;
end;   {Date_Ck}
{- - - - - - - - - - - - - - - - - - - - - - - - - - - - - }
{$I GANTT.INC}
{Due to space limitations of the turbo editor, the remainder
 of the procedures are contained in GANTT.INC}
{- - - - - - - - - - - - - - - - - - - - - - - - - - - - - }
{begin of Main Gantt}

var i,ChartWidth:integer;
    Choice: byte;         {menu choice}
    Chart: ChartType;     {chart as displayed and printed}
    TaskCount: integer;   {number of tasks}
    TaskMax: integer;     {length of longest task}
    MaxIncr: integer;     {maximum allowed date increments}
    SDate,Edate: DateType;          {begin/end dates as integers}
    FileName: Str12;
    Changed:boolean; {if UPDATE and critical data has been changed}



begin {Main Gantt}

    abandon:=false;
    Initialize_Main (filename);
    if not abandon then
      repeat
        StartOver: {label for Abandoned charts}
        Abandon:=false;
        Enter_Choice (choice);
        case Choice of
          1: begin {new chart}
              Initialize_New (Filename);
              Enter_Info (TaskMax,TaskCount,MaxIncr,
                          SDate,Edate,Changed);
              if Abandon then goto StartOver;
              Time_Spans (Chart,Sdate,Edate,TaskCount,
                          TaskMax,MaxIncr,Changed);
              if Abandon then goto StartOver;
              Save (Chart,TaskCount,Filename);

             end;

          2: begin {update chart}
              Load(Filename);
              if Abandon then goto Startover;
              Enter_Info (TaskMax,TaskCount,MaxIncr,
                          SDate,Edate,Changed);
              if Abandon then goto StartOver;
              Time_Spans (Chart,Sdate,Edate,TaskCount,
                          TaskMax,MaxIncr,Changed);
              if Abandon then goto StartOver;
              Save (Chart,TaskCount,Filename);
            end;

          3: Print_Chart (Filename);
        end;   {case}
     until choice=4;
     Housekeep;
end.  {Main Gantt}

