PROGRAM TVLife;

{                        Turbo Vision Life  v1.0                         }
{                            by Ben Ziegler                              }
{                          February 16, 1992                             }
{                                                                        }
{  TVLife is a simple program that illustrates a few of Turbo Visions's  }
{  features:  1) how to use the Idle event to execute background tasks,  }
{  and 2) how to incorporate menus inside of Twindows.  It is merely     }
{  meant to be a demonstration program for Turbo Pascal v6.0             }
{                                                                        }
{  Send any questions or comments to:                                    }
{                                                                        }
{     Ben Ziegler                  Internet Email Address:               }
{     4010 Terrace Dr              bpz4r@virginia.edu                    }
{     Annandale, VA  22003         (email valid until May 1992)          }


{$R-,S-}                          { This will speed up program execution }

USES Objects, Drivers, Views, Menus, App;

CONST
  cmLife        = 101;            { Opens a Life window           }
  cmIdle        = 102;            { issued when TV is Idle        }
  cmStart       = 103;            { Starts a Life window running  }
  cmStop        = 104;            { Stops a Life window           }
  cmClearBoard  = 105;            { Clears the Life Board         }
  cmRandom      = 106;            { Randomly fills the Life Board }
  cmHighRes     = 107;            { Set Screen to VGA 43/50 Lines }
  cmLowRes      = 108;            { Set Screen to 25 Lines        }

  Xm            = 80;             { Max X Size of Life Window     }
  Ym            = 48;             { Max Y Size of Life Window     }

TYPE
  Board         = array[1..Xm, 1..Ym] of byte;

  TMyApp        = object(TApplication)
    constructor Init;
    procedure   HandleEvent(var Event: TEvent); virtual;
    procedure   InitMenuBar; virtual;
    procedure   InitStatusLine; virtual;
    procedure   idle; virtual;
    procedure   DoLife;
    procedure   HighRes;
    procedure   LowRes;
  end;

  PMyMenuBar    = ^TMyMenuBar;
  TMyMenuBar    = object(TMenuBar)
     function   GetPalette: PPalette; virtual;
  end;

  PLifeInterior = ^TLifeInterior;
  TLifeInterior = object(Tview)
    OldB        : ^Board;
    mx,my       : integer;
    running     : boolean;
    constructor Init(var Bounds: TRect);
    procedure   HandleEvent(var Event:TEvent); virtual;
    procedure   Iterate(var o : Board);
    procedure   InitBoard(var b : Board);
    procedure   ClearBoard(var b : Board);
    procedure   Update; virtual;
    procedure   Draw; virtual;
  end;

  PLifeView     = ^TLifeView;
  TLifeView     = object(TWindow)
    MyInterior  : PLifeInterior;
    MB          : PMyMenuBar;
    constructor Init(Bounds:Trect; s : string; num:integer);
    procedure   handleevent(var event : Tevent); virtual;
    procedure   SizeLimits(var Min, Max: TPoint); virtual;
    end;

{ ************* }
{ TLifeInterior }
{ ************* }

CONSTRUCTOR TLifeInterior.Init(var Bounds: TRect);
BEGIN
   TView.Init(Bounds);
   GrowMode  := gfGrowHiX + gfGrowHiY;
   Options   := Options OR ofFramed;
   EventMask := $FFFF;                   { Listen for all types of events }
   mx        := 0;
   my        := 0;

   NEW(OldB);
   InitBoard(OldB^);
end;

PROCEDURE TLifeInterior.InitBoard(var b : Board);
VAR
   x,y,i : integer;
BEGIN
   FOR x := 1 TO Xm DO
      FOR y := 1 TO Ym DO
         b[x,y] := 0;
   Randomize;
   FOR i := 1 TO 999 DO BEGIN
      x := Random(Xm-2)+2;
      y := Random(Ym-2)+2;
      b[x,y] := 1;
      END;
END;

PROCEDURE TLifeInterior.ClearBoard(var b : Board);
VAR
   x,y   : integer;
BEGIN
   FOR x := 1 TO Xm DO
      FOR y := 1 TO Ym DO
         b[x,y] := 0;
END;

PROCEDURE TLifeInterior.Draw;
VAR
   x,y   : integer;
   R     : TRect;
   ex,ey : integer;
   B     : array[0..2047] of word;    { Buffer used to speed up Draw }
BEGIN
   GetExtent(R);
   ex := R.B.X+1;
   ey := R.B.Y+1;

   FOR y := 2 TO ey DO BEGIN
      FOR x := 2 TO ex DO BEGIN
         IF OldB^[x,y]=0 THEN BEGIN
            MoveChar(B[x-2], #32, GetColor(2), 1);
            END
         ELSE BEGIN
            MoveChar(B[x-2], #9, GetColor(2), 1);
            END;
         END;
      WriteLine(0, y-2, Size.X, 1, B);
      END;
END;

PROCEDURE TLifeInterior.Iterate(var o : Board);
VAR
   x,y,num : integer;
   n       : Board;
BEGIN
   n := o;

   FOR x := 2 TO Xm-1 DO
      FOR y := 2 TO Ym-1 DO BEGIN
         { Find number of neighbors }
         num := o[x-1,y-1] + o[x,y-1] + o[x+1,y-1]
              + o[x-1,y]              + o[x+1,y]
              + o[x-1,y+1] + o[x,y+1] + o[x+1,y+1];
         IF o[x,y]=1 THEN
            IF ((num=2) OR (num=3)) THEN n[x,y] := 1
               ELSE n[x,y] := 0;
         IF o[x,y]=0 THEN
            IF num=3 THEN n[x,y] := 1   { Birth = 3! }
               ELSE n[x,y] := 0;
         END;

   o := n;
END;

PROCEDURE TLifeInterior.Update;
BEGIN
   Iterate(OldB^);
   Draw;
END;

PROCEDURE TLifeInterior.HandleEvent(var event : Tevent);
VAR
   p,o : Tpoint;
BEGIN
   tview.handleevent(event);
   IF event.what = evCommand THEN
      CASE event.command OF
         cmStart : running := TRUE;
         cmStop  : running := FALSE;
         end;
   IF event.what = evBroadCast THEN
      IF event.command = cmIdle THEN BEGIN
         IF running THEN Update;
         END;
   IF event.what = evCommand THEN
      IF event.command = cmClearBoard THEN BEGIN
         ClearBoard(OldB^);
         Draw;
         ClearEvent(event);
         END;
   IF event.what = evCommand THEN
      IF event.command = cmRandom THEN BEGIN
         InitBoard(OldB^);
         Draw;
         ClearEvent(event);
         END;

   IF (event.what AND (evMouseDown OR evMouseAuto)) <> 0 THEN BEGIN
         o := event.where;
         MakeLocal(o, p);
         p.x := p.x+2;
         p.y := p.y+2;
         IF (mx<>p.x) OR (my<>p.y) THEN BEGIN
            OldB^[p.x, p.y] := 1-OldB^[p.x, p.y];
            Draw;
            mx := p.x;
            my := p.y;
            END;
         END;
END;


{ ********* }
{ TLifeView }
{ ********* }

CONSTRUCTOR TLifeView.Init(Bounds:Trect; s : string; num:integer);
VAR
   R : TRect;
BEGIN
   Twindow.init(Bounds, s, num);

   GetExtent(R);
   R.Grow(-1,-1);
   R.B.Y := R.A.Y + 1;
   MB := New(PMyMenuBar, Init(R, NewMenu(
     NewSubMenu('~A~ction', hcNoContext, NewMenu(
       NewItem('~S~tart', 'Alt-S', kbAltS, cmStart, hcNoContext,
       NewItem('Sto~p~', 'Alt-P', kbAltP, cmStop, hcNoContext,
       NewItem('~C~lear Board', 'Alt-C', kbAltC, cmClearBoard, hcNoContext,
       NewItem('~R~andomize', 'Alt-R', kbAltR, cmRandom, hcNoContext,
       NewLine(
       NewItem('Close ~W~indow', 'Alt-F3', kbAltF3, cmClose, hcNoContext,
       nil))))))),
     nil)
   )));
   Insert(MB);

   GetClipRect(Bounds);
   Bounds.Grow(-1,-2);
   Bounds.B.y := Bounds.b.y + 1;
   MyInterior := New(PLifeInterior, Init(Bounds));
   Insert(MyInterior);

   Options := Options OR (ofFirstClick OR ofTileable);
   dragmode := $F0;                     { Can't move window off screen }
END;

PROCEDURE TLifeView.HandleEvent(var event : Tevent);
VAR
   HelloThere : pointer;
BEGIN
   { NOTE:  HelloThere must come before twindow.he or CRASH! }
   HelloThere := Message(MyInterior, event.what, event.command, nil);
   Twindow.HandleEvent(event);
END;

PROCEDURE TLifeView.SizeLimits(var Min, Max: TPoint);
CONST
   MyMin : TPoint = (X: 28; Y: 11);
VAR
   R     : TRect;
BEGIN
   Desktop^.GetExtent(R);
   Min := MyMin;
   Max := R.B;
END;


{ ********** }
{ TMyMenuBar }
{ ********** }

FUNCTION TMyMenuBar.GetPalette: PPalette;
CONST
   CMyStuff = #4#3#6#5#6#7;
   PMyStuff : string[Length(CMyStuff)] = CMyStuff;
BEGIN
   GetPalette := @PMyStuff;
END;

{ ****** }
{ TMyApp }
{ ****** }

PROCEDURE Tile;
VAR
   R: TRect;
BEGIN
   Desktop^.GetExtent(R);
   Desktop^.Tile(R);
END;

PROCEDURE Cascade;
VAR
   R: TRect;
BEGIN
   Desktop^.GetExtent(R);
   Desktop^.Cascade(R);
END;

PROCEDURE TMyApp.HandleEvent(var Event: TEvent);
BEGIN
  TApplication.HandleEvent(Event);
  IF Event.What = evCommand THEN BEGIN
    CASE Event.Command OF
      cmLife    : DoLife;
      cmTile    : Tile;
      cmCascade : Cascade;
      cmHighRes : HighRes;
      cmLowRes  : LowRes;
    ELSE
      Exit;
    END;
    ClearEvent(Event);
  END;
END;

PROCEDURE TMyApp.InitMenuBar;
VAR
   R: TRect;
BEGIN
  GetExtent(R);
  R.B.Y := R.A.Y + 1;
  MenuBar := New(PMenuBar, Init(R, NewMenu(
    NewSubMenu('~F~ile', hcNoContext, NewMenu(
      NewItem('~L~ife Window', 'F9', kbF9, cmLife, hcNoContext,
      NewLine(
      NewItem('E~x~it', 'Alt-X', kbAltX, cmQuit, hcNoContext,
      nil)))),
    NewSubMenu('~W~indow', hcNoContext, NewMenu(
      NewItem('~N~ext', 'F6', kbF6, cmNext, hcNoContext,
      NewItem('~Z~oom', 'F5', kbF5, cmZoom, hcNoContext,
      NewItem('~T~ile', '', 0, cmTile, 0,
      NewItem('~C~ascade', '', 0, cmCascade, 0,
      NewItem('~H~igh Res', 'Alt-H', kbAltH, cmHighRes, 0,
      NewItem('~L~ow Res', 'Alt-L', kbAltL, cmLowRes, 0,
      nil))))))),
    nil)
  ))));
END;

PROCEDURE TMyApp.InitStatusLine;
VAR
   R: TRect;
BEGIN
  GetExtent(R);
  R.A.Y := R.B.Y - 1;
  StatusLine := New(PStatusLine, Init(R,
    NewStatusDef(0, $FFFF,
      NewStatusKey('', kbF10, cmMenu,
      NewStatusKey('~Alt-X~ Exit', kbAltX, cmQuit,
      NewStatusKey('~F9~ Life Window', kbF9, cmLife,
      NewStatusKey('~Alt-F3~ Close', kbAltF3, cmClose,
      nil)))),
    nil)
  ));
END;

PROCEDURE TMyApp.DoLife;
VAR
   R    : TRect;
   R2   : TRect;
   Life : PLifeView;
BEGIN
   GetExtent(R2);
   R.Assign(0, 0, 28, 11);
   R.Move(Random(R2.B.X-29), Random(R2.B.Y-12));
   Life := New(PLifeView, Init(R, 'Life', 0));
   Desktop^.Insert(Life);
END;

PROCEDURE TMyApp.Idle;
VAR
   HelloThere : pointer;

FUNCTION IsTileable(P: PView): Boolean; far;
BEGIN
   IsTileable := P^.Options and ofTileable <> 0;
END;

BEGIN
   TApplication.Idle;
   IF Desktop^.FirstThat(@IsTileable) <> NIL THEN
      EnableCommands([cmTile, cmCascade])
   ELSE
      DisableCommands([cmTile, cmCascade]);

   HelloThere := Message(DeskTop, evBroadcast, cmIdle, nil);
end;

PROCEDURE TMyApp.HighRes;
BEGIN
   SetScreenMode(ScreenMode OR smFont8x8);
   DisableCommands([cmHighRes]);
   EnableCommands([cmLowRes]);
END;

PROCEDURE TMyApp.LowRes;
BEGIN
   SetScreenMode(ScreenMode AND NOT smFont8x8);
   DisableCommands([cmLowRes]);
   EnableCommands([cmHighRes]);
END;

CONSTRUCTOR TMyApp.Init;
BEGIN
   Tapplication.init;
   DisableCommands([cmLowRes]);
END;


VAR
   MyApp: TMyApp;

BEGIN
  MyApp.Init;
  MyApp.Run;
  MyApp.Done;
END.