[net.micro.atari16] OSS Demo

fouts@AMES-NAS.ARPA (07/27/86)

This is the last part of my OSS Pascal demo.  It is the file
strval.pas.  I did not write it, but got it from the OSS PASCAL
buletin board.

     Place strval.pas, peekpoke.pas, and pager.pas in the same folder and
compile pager.pas, using the GEM option on the compiler.

----- CUT HERE -----

{***
 *   Floating Point Conversion routines.
 *   From Real to String and String to Real
 *
 *   By Kevin L. McGrath
 ***}

PROCEDURE Str(Value: Real; VAR St: String);

{* Notes:
 *   This routine is only accurate up to 9 digits becuase of the LongTrunc.
 *   It HAD rounding errors, but they are now fixed (with the LongTrunc)
 *
 * O.S.S. Pascals Floating Point Format:
 *   This is just a guess, but here goes...
 *   One byte of exponent biased by 128 to give a +38 to -38 range.
 *   Fourty bits of mantissa to give 11 digits of accuracy, One bit sign.
 *   Most floating points are normalized to the left, with the point between
 *   the most significant bit of the mantissa and the second most, so I think
 *   this is two.  To find out, just plug out a routine that has a pointer
 *   to a real, coerce's it into a pointer to a record structure of byte like
 *   this:
 *     Record
 *       Exponent:      Byte;
 *       MantissaOne:   Long;
 *       MantissaTwo:   Long;
 *       MantissaThree: Long;
 *     End;
 *   then you can extract the exponent and mantissa just by doing a
 *   "Ptr.Exponent" or somethin like that.  Well, I haven't had time to get
 *   that fancy with this, but I have used this routine and am sure it works.
 *   Hope you guys at O.S.S. can vert it to some kind of normal ASM function!
 *      Good Luck...   (Nice Compiler)
 *      Call me if there are any probs, dig?
 *}


Const
  Max_Digits    = 09;
  Max_Exponent  = 38;

Var
  Val:          Real;
  TempInt,
  Sig_Digits,
  Dec_Exp,
  I:            Integer;
  Digits:       String;

Begin
  Val := Abs(Value);
  Dec_Exp := 0;

  {* Get the exponent without Natural Log (Ln doesn't seem to work fer me) *}

  If (Val < 1) And (Val > 0) Then
  Begin
    For I := 0 To (Max_Exponent-1) Do
      If (Val < (1/PwrOfTen(I))) And (Val >= (1/PwrOfTen(I+1))) Then
        Dec_Exp := -(I+1);
    Val := Val * PwrOfTen(Abs(Dec_Exp)-1);
  End
  Else
  Begin
    For I := 0 To (Max_Exponent-1) Do
      If (Val >= PwrOfTen(I)) And (Val < PwrOfTen(I+1)) Then
        Dec_Exp := I;
    Val := Val / PwrOfTen(Dec_Exp+1);
  End;

{ Get decimal digits by stripping }

  Digits := '';
  St := '';

  For I := Max_Digits DownTo 1 Do
  Begin
    { Take care of rounding problems }

    Val := Long_Trunc(Val*PwrOfTen(I)+0.5)/PwrOfTen(I);

    Val := Val*10.0;
    Digits := ConCat(Digits,Chr(48+Trunc(Val)));
    Val := Val-Trunc(Val);

    { Take care of rounding problems }

    Val := Long_Trunc(Val*PwrOfTen(I)+0.5)/PwrOfTen(I);

  End;

{ Format and put result in St }
{ Put sign }

  If Value < 0 Then St := '-';

{ Compute significant digits }

  Sig_Digits := Max_Digits;
  I := Max_Digits - 1;
  While ((Digits[I]='0') And (I>0)) Do
  Begin
    Sig_Digits := Sig_Digits - 1;
    I := I - 1;
  End;
  Sig_Digits := Sig_Digits - 1;

{ Put in exponential or non-exonential }

  If ((Sig_Digits-Max_Digits)<=Dec_Exp) And (Dec_Exp<=Max_Digits) Then
  Begin
    { Non-exponental form }
    { Put decimal point and leading zeros for numbers with negative exponents }

    If Dec_Exp < 0 Then
    Begin
      St := ConCat(St,'.');
      For I := 1 To -Dec_Exp-1 Do
        St := ConCat(St,'0');
    End;

    { Put significant digits }

    St := ConCat(St,Digits[1]);
    For I := 1 To Sig_Digits-1 Do
    Begin
      If Dec_Exp = 0 Then
        St := ConCat(St,'.');
      St := ConCat(St,Digits[I+1]);
      Dec_Exp := Dec_Exp - 1;
    End;

    { Put trailing zeros }

    While Dec_Exp > 0 Do
    Begin
      St := ConCat(St,'0');
      Dec_Exp := Dec_Exp - 1;
    End;
  End
  Else
  Begin
    { Exponental form }
    { Put first digit }

    St := ConCat(St,Digits[1]);

    { Put decimal point }

    If Sig_Digits > 1 Then
      St := ConCat(St,'.');

    { Put remaining significant digits }

    For I := 1 To (Sig_Digits - 1) Do
      St := ConCat(St,Digits[I+1]);

    { Put the 'E' for the exponent }

    St := ConCat(St,'E');

    { Put exponents sign }

    If Dec_Exp >= 0 Then
      St := ConCat(St,'+')
    Else
    Begin
      St := ConCat(St,'-');
      Dec_Exp := Abs(Dec_Exp);
    End;

    { Put the exponent }

    If Dec_Exp >= 10 Then
    Begin
      St := ConCat(St,Chr(48+(Dec_Exp Div 10)));
      St := ConCat(St,Chr(48+Dec_Exp-((Dec_Exp Div 10) * 10)));
    End
    Else
    Begin
      St := ConCat(St,'0');
      St := ConCat(St,Chr(48+Dec_Exp));
    End;
  End;
End;


FUNCTION Val( St: String): Real;

Const
  Max_Digits    = 09;

Var
  Dec_Exp,
  Exp_Value,
  Count,
  Position:     Integer;
  Chr:          Char;
  Result:       Real;
  Dec_Sign,
  Exp_Sign:     Boolean;

  PROCEDURE Add_Digit;

  Begin
    Result := (Result * 10) + (Ord(Chr) & $0F);
  End;

  PROCEDURE Read_Chr;

  Begin
    Position := Position + 1;
    If Position > Length(St) Then
      Chr := 'X'
    Else
      Chr := St[Position];
  End;

Begin
  Position := 0;
  Read_Chr;
  Result := 0.0;

{ Get sign }

  Dec_Sign := False;
  If Chr = '+' Then Read_Chr;
  If Chr = '-' Then
  Begin
    Read_Chr;
    Dec_Sign := True;
  End;

{ Get digits to left of decimal point }

  Dec_Exp := 0;
  Count := Max_Digits;
  While ('0' <= Chr) And (Chr <= '9') Do
  Begin
    If Count > 0 Then
    Begin
      Add_Digit;
      Count := Count - 1;
    End
    Else
      Dec_Exp := Dec_Exp + 1;
    Read_Chr;
  End;

{ Get digits to the right of decimal point }

  If Chr = '.' Then
  Begin
    Read_Chr;
    While ('0' <= Chr) And (Chr <= '9') Do
    Begin
      If Count > 0 Then
      Begin
        Add_Digit;
        Dec_Exp := Dec_Exp - 1;
        Count := Count - 1;
      End;
      Read_Chr;
    End;
  End;

{ Get exponent part }

  If (Chr = 'E') Or (Chr = 'e') Then
  Begin
    Read_Chr;
    Exp_Sign := False;
    If Chr = '+' Then Read_Chr;
    If Chr = '-' Then
    Begin
      Read_Chr;
      Exp_Sign := True;
    End;
    Exp_Value := 0;
    If ('0'<=Chr) And (Chr<='9') Then Exp_Value := (Ord(Chr) & $0F)*10;
    Read_Chr;
    If ('0'<=Chr) And (Chr<='9') Then Exp_Value := Exp_Value+(Ord(Chr) & $0F);
    If (Chr = 'X') And (Exp_Value >= 10) Then Exp_Value := Exp_Value Div 10;
    If Exp_Sign Then
      Dec_Exp := Dec_Exp - Exp_Value
    Else
      Dec_Exp := Dec_Exp + Exp_Value;
  End;

{ Multiply or divide Result by power of 10 specified by Dec_Exp }

  If Dec_Exp > 0 Then
    Result := Result * PwrOfTen(Dec_Exp)
  Else
    Result := Result / PwrOfTen(Abs(Dec_Exp));

  If Dec_Sign Then Result := -Result;

  Val := Result;

End;

fouts@AMES-NAS.ARPA (07/27/86)

This is pager.pas, it's the third part of my OSS Pascal demo. Place this,
    strval.pas, and peekpoke.pas in the same file, and compile pager.pas.

- ----- CUT HERE -----
{
  pager.pas is a demonstration of the GEM interface, as exercised
  by OSS Personal Pascal.  Pager.pas is in the public domain, and
  may be used for any purpose, so long as the author is acknowledged.

  Martin Fouts
}

PROGRAM pager;

CONST
  {$I GEMCONST.PAS}
  SUPER_MODE = $20;  { GEMDOS call number to enter supervisor mode }
  Max_Wind = 10;
  Delay = 10000; { Time between timeouts (in milliseconds) }
  Max_Char = 80;

TYPE
  {$I GEMTYPE.PAS}
  Wind_No = 1..Max_Wind;
  { Ubuffer = packed array [1..Max_Char] of char;}
  UBuffer = string [255];
  UTPtr = ^Utext;
  Utext = Record
    Prev, Next : UTPtr;
    Uline : Ubuffer;
  End;
  UFile = packed File of char;
  Wind_Rec = Record
    Handle : integer;    { GEM Handle from New_Window }
    InUse : boolean;     { True if this record is in use }
    Title : string;      { Contents of title bar }
    Full : boolean;      { True if last click on full made full window }
    Ufp : UFile;         { File associated with window }
    Ufirst : UTPtr;      { Start of data from this file }
    Ulast : UTPtr;       { End of data from this file }
    UCurrent : UTPtr;    { First line of current screen }
    UCharNo : integer;   { First character of current screen (zero based )}
    ULineNo : integer;   { First line of current screen }
    UWide : integer;     { Width of widest line in this file }
    UHigh : integer;     { Number of lines read in }
    LWide : integer;     { Number of characters wide }
    LHigh : integer;     { Number of characters high }
    X_percent : 0..1000; { Position of slider, initially 0 }
    Y_percent : 0..1000; { Position of slider, initially 0 }
    Ended : boolean;     { True if EOF(Ufp) has occured }
    { Current coordinates }
    windx, windy, windw, windh : integer;
    { Last coordinates less than full size }
    smallx, smally, smallw, smallh : integer;
    { Coordinates of working space }
    workx, worky, workw, workh : integer;
  end;
  Wind_Array = array[Wind_No] of Wind_Rec;

VAR
  wind : Wind_Array;    { Track the windows we are using }
  running : boolean;    { Set to false to quit execution }
  pathname : string;    { Default search path for file opens }
  filename : string;    { Filename returned by select file }
  mymenu : Menu_Ptr;    { Pointer to menu for this program }
  mytitle : Integer;    { Pointer to first (only) title bar in menu }
  Item1 : Integer;      { First (open) item in File menu }
  Item2 : Integer;      { Second (close) item in File menu }
  Item3 : Integer;      { Third (quit) item in File menu }
  B_Left : Integer;     { Status of Left Button, 0 = up, 1 = down }
  InWindow : Boolean;   { True if in the front (active) window }
  Timeouts : Integer;   { Count the number of timeouts }
  cw, ch : Integer;     { Width and Height of a character }
  bw, bh : Integer;     { Width and Height of a box around a char }
  ticks : long_integer; { Timer count at start of program }
  mouse_init : Boolean; { True if mouse has been initialized }
  menu_init : Boolean;  { True if menu has been initialized }

{$I GEMSUBS.PAS}
{$I PEEKPOKE.PAS}
{$I STRVAL.PAS}

FUNCTION min (x, y : integer) : integer;
BEGIN
  if (x < y)
  THEN min := x
  ELSE min := y;
END;

FUNCTION max (x, y : integer) : integer;
BEGIN
  if (x > y)
  THEN max := x
  ELSE max := y;
END;

PROCEDURE Update_Slides(VAR wind : Wind_Rec);
VAR
  XSize : Integer;
  YSize : Integer;

  FUNCTION Kof(X,Y:integer) : integer;
  { Returns X div Y, normalized to the range 0-1000,
    excess values are 'clipped' to the endpoints of the range }
  VAR
    Ftemp1 : real;
    Ftemp2 : real;
    Itemp : Integer;
  BEGIN
    { These calculations are done this way to avoid integer overflow
      and preserve decimal places. }
    IF (Y = 0) { Avoid divide by zero errors }
      THEN Kof := 0
      ELSE
        BEGIN
          Ftemp1 := X;
          Ftemp2 := Y;
          ITemp := Trunc((Ftemp1 / Ftemp2) * 1000.0);
          Kof := MAX(MIN(1000,Itemp),1);
        END;
  END;

BEGIN
  WITH wind DO
  BEGIN
    work_rect(handle,WorkX,WorkY,WorkW,WorkH);
    sys_font_size(cw,ch,bw,bh);
    LWide := WorkW div cw; { convert pixel size to character size }
    LHigh := WorkH div ch;
    { Calculate position and size of horizontal elevator }
    X_Percent := Kof(UCharNo+1,UWide);
    XSize := Kof(LWide,UWide);
    { Calculate position and size of vertical elevator }
    IF (Ended)
      THEN
        BEGIN  { Actually know length of file, so use real values }
          Y_Percent := Kof(UlineNo,Uhigh);
          YSize := Kof(LHigh,UHigh);
        END
      ELSE
        BEGIN  { Don't know length, allow one page of end room }
          Y_Percent := Kof(UlineNo,(Uhigh+Lhigh));
          YSize := Kof(LHigh,(UHigh+Lhigh));
        END;
    { Now set the elevator position and size }
    Wind_Set(handle,WF_HSlSize,XSize,0,0,0);
    Wind_Set(handle,WF_VSlSize,YSize,0,0,0);
    Wind_Set(handle,WF_HSlide,X_percent,0,0,0);
    Wind_Set(handle,WF_VSlide,Y_percent,0,0,0);
  END;
END;

FUNCTION super( sp: long_integer) : long_integer;
  GEMDOS($20);

FUNCTION Get_timer : long_integer;
VAR
  ssp : long_integer;
BEGIN
  ssp := super(0);
  Get_timer := 5*lpeek($4ba);
  ssp := super(ssp);
END;

PROCEDURE Get_String(VAR Ufd:Ufile; VAR Uline:Ubuffer;
                     VAR Ended : boolean);
{ Read a carriage return terminated string and return it with
  the carriage return replaced by null }
VAR
  i : integer;
  c : char;
BEGIN
  i := 0;
  c := chr(0);
  ended := false;
  WHILE (i < Max_Char) AND (c <> chr(13)) AND (NOT Ended) DO
  BEGIN
    c := Ufd^;
    i := i + 1;
    Uline[i] := c;
    Ended := Eof(Ufd);
    IF NOT Ended THEN get(Ufd);
  END;
  Ended := Eof(Ufd);
  IF NOT Ended THEN get(Ufd); { Skip the linefeed }
  Uline[0] := chr(i);
  If i = 0 THEN i := 1;
  Uline[i] := chr(0);
END;

PROCEDURE Init_Menu;
{ Set up the Menu.  GEM Requires all titles first, then all items
  IN ORDER within Title. }
BEGIN
  menu_init := true;
  mymenu := New_Menu(10, ' About Pager');
  mytitle := Add_MTitle(mymenu,' FILE ');
  Item1 :=   Add_MItem(mymenu,mytitle,' Open ');
  Item2 :=   Add_MItem(mymenu,mytitle,' Close ');
  Item3 :=   Add_MItem(mymenu,mytitle,' Quit ');
  Draw_Menu(mymenu);
END;

FUNCTION Match_Window(new_handle : integer) : integer;
{ Find the window record for the specified handle. Return 0 if not found }
VAR
 i, n : Integer;
BEGIN
  n := 0;
  FOR i := 1 to Max_Wind DO
    IF (Wind[i].handle = new_handle) THEN n := i;
  Match_Window := n;
END;

PROCEDURE Redraw_Text(handle,x,y,w,h:integer);
VAR
  i : integer;
  lines : integer;
  lineno : integer;
  ptr : UTPtr;
  finished : boolean;
BEGIN
  i := Match_Window(handle);
  Set_Clip(x,y,w,h);
  WITH Wind[i] DO
  BEGIN
    Work_Rect(handle,x,y,w,h);
    lines := h div ch;
    ptr := Ucurrent;
    lineno := 1;
    finished := false;
    WHILE (lineno <= lines) AND (NOT finished) DO
      BEGIN
        IF (Ptr = nil)
        THEN
          BEGIN
            IF (NOT Ended) THEN
              BEGIN
                New(ptr);
                Get_String(Ufp,Ptr^.Uline,Ended);
                Draw_String(cw*(-UCharNo),ch*lineno,Ptr^.Uline);
                Uhigh := Uhigh + 1;
                Uwide := MAX(UWide,Length(Ptr^.Uline));
                ptr^.prev := Ulast;
                Ulast^.next := ptr;
                Ulast := ptr;
                ptr^.next := nil;
                ptr := ptr^.next;
              END;
            finished := Ended;
          END
        ELSE
          BEGIN
            Draw_String(cw*(-UCharNo),ch*lineno,Ptr^.Uline);
            Ptr := Ptr^.next;
          END;
        lineno := lineno + 1;
      END;
  END;
  Update_Slides(wind[i]);
END;

FUNCTION Free_Window : integer;
{ Find an unused window. Returns 0 if none available. }
VAR
  i : Integer;
  found : Boolean;
BEGIN
  found := False;
  i := 1;
  WHILE (i < Max_wind) AND (NOT found) DO
  BEGIN
    found := NOT wind[i].InUse;
    i := i + 1;
  END;
  IF found
    THEN Free_Window := i - 1
    ELSE Free_Window := 0;
END;

PROCEDURE Make_Window(VAR wind : Wind_Rec);
 { Build the data structures for a window }
BEGIN
  WITH wind DO
  BEGIN
    InWindow := false;
    B_Left := 0;
    title := filename;
    handle := New_Window(G_All,title,0,0,0,0);
    full := true;
    InUse := true;
    UWide := 0;
    UHigh := 0;
    X_percent := 0;
    Y_percent := 0;
    ULineNo := 0;
    Ufirst := nil;
    ULast := nil;
    UCurrent := nil;
    UCharNo := 0;
    Ended := False;
  END;
END;

PROCEDURE Draw_Window(VAR wind : Wind_Rec);
 { Draw the window on the screen }
VAR
  x, y, w, h : Integer;
BEGIN
  WITH wind DO
  BEGIN
    Begin_Update;
    Hide_Mouse;
    Open_Window(handle,0,0,0,0);
    Set_Window(handle);
    Bring_To_Front(handle);
    Work_rect(handle,workx,worky,workw,workh);
    Set_Clip(workx,worky,workw,workh);
    smallx := workx;
    smally := worky;
    smallw := workw div 2;
    smallh := workh div 2;
    Update_Slides(wind);
    Show_Mouse;
    End_Update;
  END;
END;

PROCEDURE Update_window(handle : integer);
VAR
  x, y, w, h : Integer;
  x0, y0, w0, h0 : Integer;
BEGIN
  Begin_Update;
  Hide_Mouse;
  Work_Rect(handle,x0,y0,w0,h0);
  First_Rect(handle,x,y,w,h);  { Locate an area in need of update }
  WHILE (w <> 0) OR (h <> 0) DO
    BEGIN               { For each area of the window }
      Paint_Rect(x-x0,y-y0,w,h); { need to convert absolute to }
      Redraw_Text(handle,x,y,w,h);
      Next_Rect(handle,x,y,w,h); { Find another rectangle to test }
  END;
  Show_Mouse;
  End_Update;
END;

PROCEDURE prev_window(VAR wind : Wind_rec; lines : integer);
VAR
  lineno : integer;
  ptr : UTPtr;
BEGIN
  WITH wind DO
  BEGIN
    work_rect(handle,workx,worky,workw,workh);
    Paint_Rect(0,0,workw,workh);
    ptr := UCurrent;
    IF (ptr <> NIL) THEN
      WHILE (lines > 0) AND (ptr^.prev <> nil) DO
        BEGIN
          lines := lines - 1;
          ULineNo := ULineNo - 1;
          ptr := ptr^.prev;
        END;
    UCurrent := ptr;
    Update_window(handle);
  END;
END;

PROCEDURE next_window(VAR wind : Wind_rec; lines : integer);
VAR
  lineno : INTEGER;
  Ptr : UTPtr;
BEGIN
  WITH wind DO
  BEGIN
    work_rect(handle,workx,worky,workw,workh);
    Paint_Rect(0,0,workw,workh);
    lineno := 1;
    ptr := Ucurrent;
    IF (ptr <> NIL) THEN
      WHILE (lineno <= lines) AND (Ptr^.next <> nil) DO
        BEGIN
          ptr := ptr^.next;
          lineno := lineno + 1;
          ULineNo := ULineNo + 1;
        END;
    UCurrent := ptr;
    Update_Window(handle);
  END;
END;

PROCEDURE fill_window(VAR wind : Wind_rec);
VAR
  lines : INTEGER;
  lineno : INTEGER;
  Ptr : UTPtr;
BEGIN
  WITH wind DO
  BEGIN
    lines := LHigh;
    reset(Ufp,filename); { Open the file for reading }
    lineno := 1;
    WHILE (lineno <= lines) AND (NOT ended) DO
      BEGIN
        new(Ptr);
        If (UFirst = nil) THEN UFirst := Ptr;
        Ptr^.Prev := Ucurrent;
        IF (UCurrent <> Nil) THEN Ucurrent^.Next := Ptr;
        Ptr^.Next := Nil;
        UCurrent := Ptr;
        Get_String(Ufp,Ptr^.Uline,Ended);
        lineno := lineno + 1;
        UHigh := UHigh + 1;
        UWide := MAX(UWide,Length(Ptr^.Uline));
      END;
    ULast := UCurrent;
    UCurrent := UFirst;
  END;
  UPdate_Slides(wind);
END;

FUNCTION Init_Window : Boolean;
{ Attempt to create a new window and open a file.  Returns false if aborted by
  the filename dialog, or if there are no windows left }
VAR
  n : Integer;
  temp : Boolean;
  i : integer;
  trying : Boolean;
PROCEDURE IO_CHECK(flag:boolean); EXTERNAL;
FUNCTION IO_RESULT : INTEGER; EXTERNAL;
BEGIN
  n := Free_Window;  { Find a window record for this window }
  temp := n > 0;
  IF NOT temp      { No window available, so fail }
    THEN n := Do_Alert('[3][No More Windows][ OK ]',1)
         { Have a window, so look for a file spec }
    ELSE
      BEGIN
        trying := Get_In_file(pathname,filename);
        WHILE Trying DO
          BEGIN  { Try to open the specified file }
            IO_Check(false);       { We want to handle I/O problems }
            reset(wind[n].Ufp,filename);
            i := IO_Result;
            IO_check(true);
            if (i = 0)
              THEN
                BEGIN
                  temp := true;
                  trying := false;
                END
              ELSE
                BEGIN
                  i := Do_Alert('[3][Open failed!][ OK ]',1);
                  temp := Get_In_file(pathname,filename);
                  trying := temp;
                END;
          END;
      END;
  IF temp THEN  { Set up the window }
    BEGIN
      Make_Window(wind[n]);
      Draw_Window(wind[n]);
      Fill_Window(wind[n]);
    END;
  Init_Window := temp;
END;

PROCEDURE Start_up;
{ Initialize the mouse and the menu and open the first window }
VAR
  i : integer;
  x, y, w, h : Integer;
BEGIN
  { First, give user a chance to bag the program }
  i := Do_Alert(
     '[1][ File Pager | A Program by Martin Fouts ][ Ready | Cancel ]',2);
  running := (i = 1);
  pathname := 'A:*.*';
  mouse_init := false;
  menu_init := false;
  IF running THEN
    BEGIN
      Init_Menu;
      Init_Mouse;
      mouse_init := true;
      Sys_Font_Size(cw,ch,bw,bh);
      Paint_Color(White);
      running := Init_Window;
      timeouts := 0;
      ticks := Get_timer; { What time is it? }
    END;
END;

PROCEDURE Process;
{ Where the work gets done.  Handle a keyboard or message event }
VAR
  i : integer;
  message : Message_Buffer;  { These are all returned by get_event }
  key : Integer;
  bcnt : Integer;
  bstate : Integer;
  mx : Integer;
  my : Integer;
  kbd_state : Integer;
  Cur_X, Cur_Y, Cur_W, Cur_H : Integer;

  PROCEDURE Do_Message;   { Process a Message event }

    PROCEDURE Close_It(n:Integer); { Close a window }
    VAR
      windno, x0, y0, w0, h0 : Integer;
    BEGIN
      Close_Window(n);
      Delete_Window(n);
      Set_Window(Front_Window);
      Work_Rect(Front_Window, x0, y0, w0, h0);
      Set_Clip(x0, y0, w0, h0);
      windno := Match_Window(n);
      WITH Wind[windno] DO
        BEGIN
          InUse := False;
          Close(Ufp);
          IF (UFirst <> Nil) THEN
            WHILE (Ufirst <> Nil) DO
              BEGIN
                UCurrent := Ufirst^.Next;
                Dispose(Ufirst);
                UFirst := UCurrent;
              END;
        END;
    END;

    PROCEDURE Do_Selection; { Process a menu selection event }

    VAR
      temp : integer;

      PROCEDURE Menu_Open; { File Menu Open Item selected }
      VAR
        temp : boolean;
      BEGIN
        temp := Init_Window;   { Open A Window }
      END;

      PROCEDURE Menu_Close; { File Menu Close Item selected }
      BEGIN
        Close_It(Front_Window);
      END;

      PROCEDURE Menu_Quit;  { File Menu Quit Item selected }
      VAR                   { Use an alert to verify the Quit }
        temp : integer;
      BEGIN
        temp :=
          Do_Alert('[3][ Do you really want to Quit? ][ Quit | Continue ]',2);
          running := (temp <> 1);  { Return FALSE to Quit! }
      END;

    BEGIN
      Menu_Normal(mymenu,message[3]); { Turn off menu highlight }
      IF (message[3] = 3) THEN  { Special case, the INFO box }
        temp := Do_Alert('[1][A Sample Program][ OK ]',0)
      ELSE IF (message[4] = item1) THEN Menu_Open
      ELSE IF (message[4] = item2) THEN Menu_Close
      ELSE IF (message[4] = item3) THEN Menu_Quit;
    END; { Procedure Do_Selection }

    PROCEDURE Do_Redraw;  { Handle a redraw message }
    VAR
     temp, x, y, w, h : Integer;
     x0, y0, w0, h0 : Integer;
    BEGIN
      Begin_Update;        { Prevent interference }
      Hide_Mouse;          { Keep the mouse out of the way }
      temp := Get_Window;  { Remember the active window }
      Set_Window(message[3]); { Make the updated window active }
      Work_Rect(message[3],x0,y0,w0,h0); { Find out about it }
      Set_Clip(x0,y0,w0,h0);
      First_Rect(message[3],x,y,w,h);  { Locate an area in need of update }
      WHILE (w <> 0) OR (h <> 0) DO
        BEGIN               { For each area of the window }
          IF Rect_Intersect(message[4],message[5],message[6],message[7],
                            x,y,w,h) THEN
            BEGIN           { Find the area which must be updated and do so }
              Paint_Rect(x-x0,y-y0,w,h); { need to convert absolute to }
              Redraw_Text(message[3],x,y,w,h);
            END;                         { relitive coordinates for Paint }
          Next_Rect(message[3],x,y,w,h); { Find another rectangle to test }
        END;
      Show_Mouse;           { Make the mouse active again }
      End_Update;           { Allow GEM activity again }
      Set_Window(temp);     { Restore the active window }
      Work_Rect(temp,x0,y0,w0,h0);
      Set_Clip(x0,y0,w0,h0); { And set it up as the i/o port }
    END;

    PROCEDURE Do_Newtop;     { Bring a new window to the top }
    BEGIN
      Bring_To_Front(message[3]);
      Set_Window(message[3]);
    END;

    PROCEDURE Do_Close;       { Close a window (and it's file) }
    BEGIN
      Close_It(message[3]);
    END;

    PROCEDURE Do_Fulled;      { Handle a click on the full box }
    var
      n, x, y, w, h : integer;
    BEGIN
      n := Match_Window(message[3]);  { Find the window }
      WITH wind[n] DO
        BEGIN
          IF Wind[n].Full     { If already full then shrink the window }
          THEN
            BEGIN
              Set_WSize(handle, smallx, smally, smallw, smallh);
              windx := smallx;
              windy := smally;
              Windw := smallw;
              windh := smallh;
            END
          ELSE
            BEGIN                  { If small make largest size possible }
              Wind_Get(handle,WF_FullXYWH,windx,windy,windw,windh);
              Set_Wsize(handle,windx,windy,windw,windh);
            END;
          Full := NOT Full;    { Swap the full mode }
          Update_Slides(wind[n]);
        END;
    END;

    PROCEDURE Do_Arrowed;      { Handle an arrow being clicked }
    VAR
      n : integer;

      PROCEDURE Page_up;
      BEGIN
        prev_window(wind[n],wind[n].Lhigh);
      END;

      PROCEDURE Page_down;
      BEGIN
        next_window(wind[n],wind[n].Lhigh);
      END;

      PROCEDURE Row_up;
      BEGIN
        WITH wind[n] DO
          BEGIN
            if (Ucurrent <> nil) THEN
              if (Ucurrent^.prev <> nil) THEN
                BEGIN
                  Ucurrent := Ucurrent^.prev;
                  update_window(handle);
                  ULineno := ULineno - 1;
                END;
          END;
      END;

      PROCEDURE Row_down;
      BEGIN
        WITH wind[n] DO
          BEGIN
            if (Ucurrent <> nil) THEN
              if (Ucurrent^.next <> nil) THEN
                BEGIN
                  Ucurrent := Ucurrent^.next;
                  update_window(handle);
                  ULineno := ULineno + 1;
                END;
          END;
      END;

      PROCEDURE Page_left;
      BEGIN
        WITH wind[n] DO
          BEGIN
            if (UCharNo >= Lwide)
              THEN UCharNo := UCharNo - Lwide
              ELSE UCharNo := 1;
            update_window(handle);
          END;
      END;

      PROCEDURE Page_right;
      BEGIN
        WITH wind[n] DO
          BEGIN
            if (wind[n].UCharNo <= (Uwide - Lwide - 2))
              THEN UCharNo := UCharNo + LWide
              ELSE UcharNo := Uwide - Lwide - 2;
            update_window(handle);
          END;
      END;

      PROCEDURE Column_left;
      BEGIN
        if (wind[n].UCharNo > 0 ) THEN
          BEGIN
            wind[n].UCharNo := wind[n].UCharNo - 1;
            update_window(wind[n].handle);
          END;
      END;

      PROCEDURE Column_right;
      BEGIN
        WITH wind[n] DO
          BEGIN
            if (UCharNo <= (Uwide - Lwide)) THEN
              BEGIN
                UCharNo := UCharNo + 1;
                update_window(handle);
              END;
          END;
      END;

      PROCEDURE No_move;
      BEGIN
      END;

    BEGIN
      n := Match_window(message[3]);
      CASE message[4] OF
        0: Page_up;
        1: Page_down;
        2: Row_up;
        3: Row_down;
        4: Page_left;
        5: Page_right;
        6: Column_left;
        7: Column_right;
        OTHERWISE: No_move;
      END;
    END;

    FUNCTION NofK(X,Y:integer) : integer;
    VAR
      temp1, temp2 : real;
    BEGIN
      temp1 := X;
      temp1 := temp1 / 1000.0;
      temp2 := Y;
      NofK := trunc(temp1*temp2) - 1;
    END;

    PROCEDURE Do_Hor;  { Horizontal slider movement }
    VAR
      n : integer;
    BEGIN
      n := Match_Window(message[3]);
      WITH wind[n] DO
        BEGIN
          UCharno := NofK(message[4],Uwide);
          update_window(handle);
        END;
    END;

    PROCEDURE Do_Ver;  { Vertical slider movement }
    VAR
      n : integer;
      newline : integer;
    BEGIN
      n := Match_Window(message[3]);
      WITH wind[n] DO
        BEGIN
          newline := NofK(message[4],Uhigh);
          IF (newline < Ulineno)
            THEN prev_window(wind[n],Ulineno-newline)
            ELSE
              IF (newline > Ulineno)
                THEN next_window(wind[n],newline-Ulineno);
        END;
    END;

    PROCEDURE Do_Size;
    { Change the size of the current window, and remember the new size }
    VAR
      n : integer;
    BEGIN
      n := Match_Window(message[3]);
      WITH wind[n] DO
        BEGIN
          Set_Wsize(handle,message[4],message[5],message[6],message[7]);
          smallx := message[4];
          smally := message[5];
          smallw := message[6];
          smallh := message[7];
          windx := smallx;
          windy := smally;
          windw := smallw;
          windh := smallh;
          Update_Slides(wind[n]);
        END;
    END;

    PROCEDURE Do_Move;
    { Move the current window to a new place }
    VAR
      n : integer;
    BEGIN
      n := Match_Window(message[3]);
      WITH wind[n] DO
        BEGIN
          Set_Wsize(handle,message[4],message[5],message[6],message[7]);
          smallx := message[4];
          smally := message[5];
          smallw := message[6];
          smallh := message[7];
          windx := smallx;
          windy := smally;
          windw := smallw;
          windh := smallh;
          Update_Slides(wind[n]);
        END;
    END;

    PROCEDURE Do_Nothing;
    BEGIN
    END;

    BEGIN
      CASE message[0] of
        MN_Selected : Do_Selection;
        WM_Redraw   : Do_Redraw;
        WM_Topped   : Do_Newtop;
        WM_Closed   : Do_Close;
        WM_Fulled   : Do_Fulled;
        WM_Arrowed  : Do_Arrowed;
        WM_HSlid    : Do_Hor;
        WM_Vslid    : Do_Ver;
        WM_Sized    : Do_Size;
        WM_Moved    : Do_move;
        Otherwise   : Do_Nothing;
      END;
    END;

    PROCEDURE Do_Keyboard;
    VAR
      temp : integer;
    BEGIN
      IF key = $06200 THEN { HELP Key pushed }
        temp := Do_Alert('[1][ I can''t fix your problems ][ Continue ]',1);
      IF key = $06100 THEN { UNDO Key pushed }
      BEGIN
        temp :=
          Do_Alert('[3][ Do you really want to Quit? ][ Quit | Continue ]',2);
          running := (temp <> 1);  { Return FALSE to Quit! }
      END;
    END;

    PROCEDURE New_Mouse(f:Boolean; n:Integer);
    VAR
      i : Integer;
    BEGIN
      IF f THEN i := 1 ELSE i := 0;
      i := i + (n * 2);
      CASE i OF
        0: Set_Mouse(M_Point_Hand);
        1: Set_Mouse(M_Outln_Cross);
        2: Set_Mouse(M_Arrow);
        3: Set_Mouse(M_Thin_Cross);
        OtherWise: Set_Mouse(M_Bee);
      END;
    END;

    PROCEDURE Do_Button;
    { Mostly for form, change the cursor when the left Button changes }
    BEGIN
      B_Left := 1 - B_Left;
      New_Mouse(InWindow,B_Left);
    END;

    PROCEDURE Do_Rect1;
    { Mostly for form, use the cursor shape to track if the mouse is in
      or out of the active window }
    BEGIN
      InWindow := Not InWindow;
      New_Mouse(InWindow,B_Left);
    END;

    PROCEDURE Do_Timer;
    { This one's just here to fill out the template }
    VAR
      i : integer;
      r : real;
      message : String;
      rval : String;
    BEGIN
      r := (Get_Timer - Ticks) / 1000.0; { Convert to seconds elapsed }
      Str(r,rval);
      message := Concat('[1][ Program run | ', rval,
                        ' | seconds ][ Continue ]');
      i := Do_Alert(message,1);
    END;

BEGIN  { Wait for a GEM message or a keyboard event }
  Work_Rect(Front_Window,Cur_X,Cur_Y,Cur_W,Cur_H);
  i := Get_Event(E_Keyboard|E_Message|E_Button|E_Mrect_1|E_Timer,
                 1, B_Left, 1,  { Wait for left button Change }
                 Delay,         { Wait for timeout }
                 InWindow,Cur_X,Cur_Y,Cur_W,Cur_H, { Front Window border }
                 False,0,0,0,0, { No Rectangle 2 }
                 message,       { Returns message if E_Message }
                 key,           { Returns key pressed if E_Keyboard }
                 bcnt,          { Returns button count if E_Button }
                 bstate,        { Returns button status if E_Button }
                 mx, my,        { Mouse position if E_Button }
                 kbd_state);    { Keyboard state if E_Keyboard }

  IF (i & E_Message) <> 0  THEN Do_Message;
  IF (i & E_Keyboard) <> 0 THEN Do_Keyboard;
  IF (i & E_Timer) <> 0 THEN Do_Timer;
  IF (i & E_MRect_1) <> 0 THEN Do_Rect1;
  IF (i & E_Button) <> 0 THEN Do_Button;

END; { Procedure Process }

PROCEDURE Clean_up;
VAR
  i : integer;
BEGIN
  FOR I := 1 to Max_wind DO
    IF wind[i].InUse THEN
      BEGIN
        Close_Window(wind[i].handle);
        Delete_Window(wind[i].handle);
      END;
  IF mouse_init THEN Set_Mouse(M_Arrow);
  IF menu_init THEN
    BEGIN
      Erase_Menu(mymenu);
      Delete_Menu(mymenu);
    END;
  Exit_Gem;
END;

PROCEDURE Go_For_It; { This is where it happens, Jack }
BEGIN
  running := false;
  Start_up;
  While running do Process;
  Clean_up;
END;

BEGIN { template }
  IF Init_Gem >= 0 THEN Go_For_It;
END. { PROGRAM template }