18.6 An Extended Example--Implementing GraphPaper

Two kinds of functionality have to be combined into the working package in order to implement GraphPaper. First, one has to be able to get a window open that can be used for the purpose, and second, one has to have available some drawing routines from a variety of system specific modules. Actually implementing the routines in the definition module then turns out to be relatively routine.

Thus, in the implementations that follow, the functionality is divided between two modules--one that opens and prepares a window for drawing, and the other the actual implementation of GraphPaper.

18.6.1 Defining the Module GraphWindow

The only purpose of this module is to isolate the task of preparing the graphics window from the task of implementing the procedures in GraphPaper. The MacOS versions are relatively simple, and in the initial implementation one incorporated into GraphPaper to make a single module. However, the Windows version was so cumbersome that the details obscured those of GraphPaper so the two were separated and the module GraphWindow created. Here is the definition:

DEFINITION MODULE GraphWindow;

(* Design and Macintosh implementattion by R.  Sutcliffe
   Windows implementation by Joel Schwartz
   Last revision: 1998 07 07 *)
   
(* This module obtains and passes to applications that need it a simple graphics window and its dimensions. Some applications will need only to import this module, and possibly get the window dimensions, as graphing takes place in the current grafport anyway, and this module will set that port, so GetWindow may not have to be imported. *)

IMPORT (* MacOS *) Quickdraw; (* Windows: IMPORT WIN32; *) 

(* for convenience we export the type of the reference; this makes it more compatible to the 
Windows version where we import the HDC type and define WindowRef to be an HDC. *)

TYPE 
  WindowRef = Quickdraw.WindowRef;  (* Windows: WindowRef = WIN32.HDC *)

PROCEDURE GetWindow () : WindowRef;
PROCEDURE GetWDimensions (VAR width, height : INTEGER);

END GraphWindow.

The Windows version needs the minor change as noted--another reason to separate this functionality from that of GraphPaper. One does not want OS-specific items like WindowRef turning up in a top level general definition module. If the user of GraphPaper needs this reference, perhaps to do some annotating of her own in the window, it is available, but at this lower level. A full listing of the Windows version with the small revisions is given in section 18.6.5 for reference purposes.

18.6.2 Implementing GraphWindow in MacOS

There are a variety of ways to get a window open for graphing in MacOS. In the author's implementation of the ISO library, the I/O system opens a window that comes complete with menu bar and a quit command so that no loop is needed to wait for a key press. The implementation that follows imports STextIO, thereby forcing the opening of a window for text (which can also be used to draw) and essentially "steals" this window, setting its graphics port as the current one, and allowing GraphPaper to go ahead and draw in it.

IMPLEMENTATION MODULE SGraphWindow;
(* Design and Macintosh implementation by R. Sutcliffe
   Ultra simple version that steals a window from elsewhere
   Last revision: 1998 07 07 *)

FROM Quickdraw IMPORT
  SetPort;

FROM MacWindows IMPORT
  FrontWindow;

FROM Types IMPORT
  Rect;  
  
IMPORT STextIO; (* force a window *)

VAR
(* Graphics Variables *)
  graphRect  : Rect;
  gWindow : WindowRef;
  lwidth, lheight : INTEGER;

 (*------------------------ Window Related Procedures ------------------ *)

PROCEDURE GetWindow () : WindowRef;

BEGIN
  RETURN gWindow;  (* Return the stored window reference *)
END GetWindow;

PROCEDURE GetWDimensions (VAR width, height : INTEGER);

BEGIN
  width := lwidth;
  height := lheight;
END GetWDimensions;

BEGIN (* main *)
  gWindow := FrontWindow (); (* steals the one STextIO puts up *)
  SetPort (gWindow);
  graphRect := gWindow^.visRgn^^.rgnBBox;
  lwidth := graphRect.right - graphRect.left;
  lheight := graphRect.bottom - graphRect.top;
END SGraphWindow.

The details of the record structure pointed to by a WindowRef are not given here. Suffice it to say that once the window has been opened by STextIO, it is easy to get its reference using FrontWindow and then take information concerning the window size from that data structure as shown.

If the same trick is tried without using the author's implementation of the ISO library, the Terminal program that supplies services to the I/O library probably will not have such features as a Quit command on the menu bar, and the window will simply vanish as soon as the program has run. In that event, the same implementation as above may be employed, but with the addition of a termination clause. One would include the lines:

FROM Keyboard IMPORT
  BusyRead;
FROM Events IMPORT
  Button;

FINALLY
  WriteString ("touch any key to exit");
  REPEAT
    BusyRead (ch); (* delay until keypress or mouse button *)
  UNTIL (ch # 0C) OR Button ();

The addition of the use of Button is to ensure that such items as the control, option, and command buttons (which are not keys) will also exit the program if touched.

If the user desires to create graphics windows without using the ones available by stealing from a Terminal module employed by STextIO then a little more work is necessary, as the module would actually have to open the window itself. Rather than immediately give a variation on GraphWindow here that does this, the information necessary is provided in the following module, which opens a graphics window directly and then draws some rectangles and ovals in it. This module does not use GraphPaper, only built in routines. Extracting the necessary routines from it to produce a stand-alone version of GraphWindow for the MacOS is shown later in this section. The only thing this particular module does, besides open the window, is set a pen size to a two-by-two rectangle rather than the usual one pixel each way, and then draw a few figures. Of course, it would be easy to port this module to GraphPaper either by importing the routines for framing rectangles and ovals directly, or by writing them in a client of GraphPaper.

MODULE DrawRectangles;

(* by R. Sutcliffe
  to demonstrate simple graphics on the MacOS using QuickDraw directly
  illustrates simple actions such as opening and preparing a graphics window
  revised 1996 07 14 *)
  
FROM SYSTEM IMPORT
  ADR, CAST;  
FROM Keyboard IMPORT
  BusyRead;  
FROM Quickdraw IMPORT
  qd, PenSize, WindowRef, InsetRect, SetPort, SetRect, FrameRect, FrameOval; 
FROM MacWindows IMPORT
  WindowRecord, NewWindow, documentProc;  
FROM Types IMPORT
  Rect;  
FROM Events IMPORT
  Button;
  
CONST
  inFront = CAST (WindowRef, -1); (* Special constant used when opening window to say it goes on top. *)
  deltaXY = 20;
  penH = 2;  penV = 2;
  
VAR
  curRect, windRect : Rect;
  left, right, top, bot : CARDINAL;
  ch : CHAR;
  wRecord: WindowRecord;
  myWindow: WindowRef;

BEGIN
  windRect := qd.screenBits.bounds;  (* find out screen size *)
  InsetRect (windRect, 50, 50);  (* and make an inset from this *)

  (* now, open a named window using this rect *)
  (* we have to pass the address of a WindowRecord, a rectangle to draw in, a title for the window, TRUE to make the window visible, the name of the procedure that draws a standard document window, the constant inFront to put it on top, FALSE to indicate it has no goAway box and a zero for the refCon.
   *)
  myWindow := NewWindow
   (ADR (wRecord), windRect, 'DrawRectangles', TRUE, documentProc, inFront, FALSE, 0);

  SetPort (myWindow); (* establish graphics port *)

  (* and the relative coordinates of its size *)
  left := 0;
  right := windRect.right - windRect.left;
  top := 0;
  bot := windRect.bottom - windRect.top;
  PenSize (penH, penV);

  (* Now, draw a series of contained rectangles and ovals *)
  WHILE left < right
    DO
      SetRect (curRect, left, top, right, bot);
      FrameRect (curRect); 
      FrameOval (curRect); 
      INC (left, deltaXY);
      INC (top, deltaXY);
      DEC (right, deltaXY);
      DEC (bot, deltaXY);
    END;
  
 FINALLY
   REPEAT
     BusyRead(ch); (* delay until keypress or mouse button *)
   UNTIL (ch # 0C)  OR Button ();
 
END DrawRectangles.

Here is a reduced screen shot of the output from this simple module.

This module too has a dependency on one of the author's own modules, but the concept has been presented before, and the reader should consult section 8.4.1 for the details of implementing Keyboard.

It is not the purpose of this section to give a detailed description of the MacOS toolbox routines, but some careful study of the simple ones employed here should go a long way toward assisting in understanding some of the basics.

Collecting some of these ideas into one module provides a better implementation of GraphWindow,this one not dependent on Keyboard. Note, however, that this is still not a full-blown MacOS application. It has no menu bar, and it is not possible to switch out of applications based on this window and then back in again, for the graphics drawn on the window will not reappear once they have been erased. Once the client program is finished, the termination clause in this module takes over, and waits for a keypress or a mouse click. In this version, other button clicks (CMD, OPT, CNTRL, SHFT) are left alone so that the normal screen shot process can take place.

This implementation also hints at the very different style of programming needed in a graphics user interface such as MacOS or Windows. When the program is idling, it does so in a loop (called a main event loop) that waits for an event to take place. When one does, the program decides whether or not to handle that event. In this case, the MainEventLoop is a very simple one and contains all the code needed to handle the only events of interest. Much more would need to be done in a fully developed application, and the usual method is to have separate procedures for each kind of event and dispatch control to the handling procedures after detecting which event has occurred. The purpose of the gSleep variable is to give the system some time to respond to events as well, keeping the system clock and other such things up-to-date. Very few systems will lack colour; those that do will produce a window anyway, but it will not be possible to import routines from Quickdraw to change the pen colour for drawing.

IMPLEMENTATION MODULE GraphWindow;
(* Implements a very simple graphics window on the Mac. There is no menu, and any keypress or mouse click quits.
  Screenshots using shft-cmd-4 work, so the results from clients can be captured.
 
  Design and Macintosh implementation by R.  Sutcliffe
   Last revision: 1998 07 22 *)

FROM SYSTEM  IMPORT
  ADR, CAST;
FROM Types  IMPORT
  OSErr, Rect;
FROM OSUtils IMPORT
  SysEnvirons, SysEnvRec;
FROM Quickdraw IMPORT
  qd, SetPort;  
FROM MacWindows IMPORT
  WindowRecord, NewCWindow, NewWindow, documentProc;
FROM Events IMPORT
  EventRecord, GetCaretTime, WaitNextEvent, keyDown, mouseDown, everyEvent;

VAR
    (* Graphics Variables to pass out *)
  gWindow : WindowRef;
  lwidth, lheight : INTEGER;
    (* internal variables *)
  windRect, graphRect  : Rect;
  gSleep   : INTEGER;
  wRecord : WindowRecord;

CONST
  inFront = CAST (WindowRef, -1); (* special constant to display window *)

 (*------------Exported Procedures --------------- *)

PROCEDURE GetWindow () : WindowRef;

BEGIN
  RETURN gWindow;  (* Return the stored window reference *)
END GetWindow;

PROCEDURE GetWDimensions (VAR width, height : INTEGER);

BEGIN
  width := lwidth;
  height := lheight;
END GetWDimensions;

(* -------------------------------------------*)
   

(* Initialize everything for the program, make sure we can run. *)
PROCEDURE Initialize;
VAR
  error : OSErr;
  theWorld  : SysEnvRec;
  colour : BOOLEAN;
  
BEGIN
  (* Test the computer to be sure we can do color.  If not we could crash.  Note that a client program should do its own test before assuming that the window has colour. *)
  
  error := SysEnvirons (1, theWorld);
  colour := theWorld.hasColorQD;
 
  (* The run time system initializes all the needed managers. *)

  (* Make a new window for drawing in.  The window is inset from full screen size. *)
  
  windRect := qd.screenBits.bounds; (* get overall dimensions *)
  INC (windRect.top, 40); (* drop down from the top *)
  IF colour (* make it a colour window if we can *)
    THEN
      gWindow := NewCWindow
        (ADR (wRecord), windRect, 'GraphicsWindow', TRUE, documentProc, 
              inFront, FALSE, 0);
    ELSE (* otherwise get one anyway--only on old Macs *)
       gWindow := NewWindow
        (ADR (wRecord), windRect, 'GraphicsWindow', TRUE, documentProc, 
              inFront, FALSE, 0);
    END;
 SetPort (gWindow);      (* set window to be current graf port  *)
  graphRect := gWindow^.visRgn^^.rgnBBox; (* get local copies of length and width *)
  lwidth := graphRect.right - graphRect.left;
 lheight := graphRect.bottom - graphRect.top;
   (* set idle time used by WaitNextEvent *)
 gSleep := GetCaretTime ();

END Initialize;

PROCEDURE MainEventLoop;
VAR 
  theEvent : EventRecord;
BEGIN
  LOOP (* wait for something to happen *)
    IF WaitNextEvent (everyEvent, theEvent, gSleep, NIL)
      THEN
        IF (theEvent.what = keyDown) OR (theEvent.what = mouseDown)
          THEN
            EXIT (* on any keypress or mouse press *)
          END
      END (* if WaitNextEvent *)
  END (* loop *)

END MainEventLoop;

BEGIN    
  Initialize;
FINALLY
  MainEventLoop;  
END GraphWindow.

Once graphics windows are available, one can do some interesting things in them. The following program, often supplied for beginners on the MacOS, draws small coloured balls on the screen at random locations and with random colours. Its only purpose here is to demonstrate GraphWindow and to give the students a few more graphics tools to play with. As the output is dynamic, and the ball drawing stops only when the mouse button is pressed, no output for this module is shown here. The aname Sillyballs, by the way, comes from the original of this program, which was part of tutorial materials for the MacOS.

Notice that all drawing is in the context of a rectangle, including that of each individual ball. Notice also that in this case, the interior of the balls has been painted.

MODULE SillyBalls;
(* This program draws balls in random colours and at random locations on the screen. *)

FROM SYSTEM  IMPORT
  INT16;
FROM Types  IMPORT
  Rect, OSErr, UInt16;
FROM OSUtils IMPORT
  SysEnvirons, SysEnvRec;
FROM DateTimeUtils IMPORT
  GetDateTime;
FROM Sound IMPORT
  SysBeep;
FROM Quickdraw IMPORT
  qd, RGBColor, InsetRect, SetRect, Random, RGBForeColor, PaintOval, MoveTo, InvertColor;
FROM QuickdrawText IMPORT
  DrawString, TextSize;
FROM Events IMPORT
  Button;
IMPORT GraphWindow; (* gets window *)

(*----------------------------------------------------------------
#
#   Adapted to Modula-2
#   by R. Sutcliffe
#   Trinity Western University
#   1996 01 29
#   revised 1998 07 22 
#   to use GraphWindows
#     last revision 1998 09 09 for Mac 3.1 interfaces
#   from an original program bearing the notice:
#
#  Macintosh Developer Technical Support
#  Simple Color QuickDraw Sample Application
#
#   Copyright © 1988 Apple Computer, Inc.
#  All rights reserved.
#
*)

CONST
  ballWidth  = 25;
  ballHeight = 25;
  TWUSize  = 8;    (* Size of text in each ball. *)
VAR
  height, width : INTEGER;

(* Initialize everything for the program, make sure we can run. *)
PROCEDURE Initialize;
VAR
  error : OSErr;
  theWorld  : SysEnvRec;
  
BEGIN
  (* Test the computer to be sure we can do color.  If not we would crash, which would be bad.  If we canąt run, just beep and exit. *)
  
  error := SysEnvirons (1, theWorld);
  IF NOT theWorld.hasColorQD
    THEN 
      SysBeep (50);
      HALT;            (* If no color QD, we must leave. *)
    END;
  
  (* The run time system initializes all the needed managers. *)
  
  (* To make the Random sequences truly random, we need to make the seed start at a different number.  An easy way to do this is to put the current time and date into the seed.  Since it is always incrementing the starting seed will always be different.  Donąt for each call of Random, or the sequence will no longer be random.  Only needed once, here in the init. *)
  
  GetDateTime (qd.randSeed);

  (* Make a new window for drawing in, and it must be a color window.  The window is full screen size, made smaller to make it more visible. *)
  
  TextSize (TWUSize);     (* small font for drawing. *)
  GraphWindow.GetWDimensions (width, height);
  DEC (width, ballWidth); (* don't start any balls too far right *)
  
END Initialize;

(* NewBall: make another ball in the window at a random location and color. *)
PROCEDURE NewBall;
VAR
  ballColor  : RGBColor;
  ballRect  : Rect;
  newLeft, newTop : INTEGER;
  
BEGIN
  (* Make a random new color for the ball. *)
  WITH ballColor
    DO 
      red := VAL (UInt16, ABS (Random()));
      green := VAL (UInt16, ABS (Random()));
      blue := VAL (UInt16, ABS (Random()));
    END;
  (* Set that color as the new color to use in drawing. *) 
  RGBForeColor (ballColor);

  (* Make a Random new location for the ball, that is normalized to the window size.  
  This makes the Integer from Random into a number that is 0..hieght  and 0..width.  They are normalized so that we don't spend most of our time drawing in places outside of the window. *)
  
  newTop := Random();
  newLeft := Random();
  newTop := VAL (INT16, ((VAL (INTEGER, newTop) + 32767) * VAL(INTEGER, height)) DIV 65536);
  newLeft := VAL (INT16, ((VAL(INTEGER, newLeft) + 32767) * VAL(INTEGER, width)) DIV 65536);
  SetRect (ballRect, newLeft, newTop, newLeft + ballWidth, 
          newTop + ballHeight);
  
  (* Move pen to the new location, and paint the colored ball. *)  
  MoveTo(newLeft, newTop);
  PaintOval (ballRect);
  
  (* Move the pen to the middle of the new ball position, for the text *)  
  MoveTo(ballRect.left + ballWidth DIV 2 - TWUSize, 
      ballRect.top + ballHeight DIV 2 + TWUSize DIV 2 -1);
  
  (* Invert the color and draw the text there.  This wonąt look quite right in 1 bit mode, since the foreground and background colors will be the same.  Color QuickDraw special cases this to not invert the color, to avoid invisible drawing. *)
  
  InvertColor (ballColor); 
  RGBForeColor (ballColor);
  DrawString ('TWU');
END NewBall;


BEGIN     (* Main body of program SillyBalls *)
  Initialize;  
  REPEAT
    NewBall;
  UNTIL Button();  
END SillyBalls.

18.6.3 Implementing GraphWindow in Windows NT

As a reminder, the (stripped down and relatively uncommented) definition module is given first, with the appropriate small modifications to move from MacOS to Windows NT. This implementation should also work in Windows 95/98.

DEFINITION MODULE GraphWindow;
IMPORT WIN32;
TYPE
  WindowRef = WIN32.HDC;

PROCEDURE GetWindow () : WindowRef;

PROCEDURE GetWDimensions (VAR width, height : INTEGER);
END GraphWindow.

The implementation is somewhat more work, as the trick of stealing the top available window does not appear to give good results, and more has to be done to get anything to happen at all. Moreover, a lot more information has to be prepared into a data structure before the window is opened. As the purpose here is to supply the necessary code, not to explain every detail, very little other commentary is provided. The reader is invited to compare this code with what was needed in the corresponding MacOS implementation. There are similarities, but several differences as well.

WARNING: This implementation was done for Stonybrook Modula-2 for Win32. As numerous implementation details are certain to vary, the reader cannot expect it to work unmodified on other 32-bit Windows implementations. It is likely that the various imports will come from different places, and it is also likely that the method of handling the translation to C++ classes will also be different. It may be necessary for the reader, as it was for the author, to obtain an example program for the specific implementation, and then modify it to suit, as the available documentation for the Microsoft C++ classes and API is unlikely to shed much light on how to get started.

The reader will note that the style here is a little different in that a window class has to be created and registered before the window itself is created. Then, the main event loop consists of waiting for a message, which is then translated and despatched. Because the system has some built in handlers to which messages can be dispatched, client applications created with this module can be quit in the normal way.

IMPLEMENTATION MODULE GraphWindow;

(* Design by R. Sutcliffe
   Implementation by Joel Schwartz for StonyBrook Modula-2
   last modification : 1998 07 14 by RS *)
   
FROM SYSTEM IMPORT
  FUNC, ADR, CAST;

FROM WIN32 IMPORT
  UINT, WPARAM, LPARAM, LRESULT, BOOL, RECT, HBRUSH, HWND;

FROM WINUSER IMPORT
  GetDC, ReleaseDC, SetRect, InvalidateRect, GetClientRect, RegisterClass,
  ShowWindow, GetMessage, TranslateMessage, DispatchMessage,
  LoadCursor, LoadIcon, IDC_ARROW,
  FillRect, DefWindowProc, UpdateWindow, PostQuitMessage,
  BeginPaint, EndPaint, CreateWindow, GetSysColor,
  LOWORD, HIWORD, COLOR_WINDOW, WS_OVERLAPPEDWINDOW,
  CS_VREDRAW, CS_HREDRAW, CS_BYTEALIGNCLIENT,
  WM_SIZE, WM_DESTROY, WM_PAINT, WM_SYSCOLORCHANGE, WM_ERASEBKGND,
  WNDCLASS, MSG, PAINTSTRUCT;

FROM WINGDI IMPORT
  CreateSolidBrush, GetDeviceCaps, VERTRES, HORZRES;

FROM WINX IMPORT
  DeleteBrush, NULL_HWND, NIL_RECT, NULL_HBRUSH, NULL_HINSTANCE, NULL_HMENU, Instance, PrevInstance, CmdShow;

VAR
  graphRect  : RECT;
(* Window Creation / Management Variables *)
  VRes, HRes : INTEGER;
  WindowWidth, WindowHeight : INTEGER;
  szBuffer : ARRAY [0..30] OF CHAR;
  bFirst : BOOL = TRUE;
  hbrBackgnd  : HBRUSH;    (* background brush -- system window backbround color *)
  Wnd : HWND;
  mess : MSG;
  DC : WindowRef;

(*----------------- Public Procedures --------------*)

PROCEDURE GetWindow () :WindowRef;
BEGIN
  RETURN DC;  (* Return the window reference *)
END GetWindow;

PROCEDURE GetWDimensions (VAR width, height : INTEGER);
BEGIN
  width := WindowWidth;
  height := WindowHeight;
END GetWDimensions;

(*-----------------Private Procedures ---------------*)
<*/PUSH*>
<*/CALLS:WIN32SYSTEM*>

PROCEDURE FrameWndProc (wnd : HWND;
          message : UINT;
          wParam : WPARAM;
          lParam : LPARAM) : LRESULT [EXPORT];

 (* Pre: A window event has occurred
 * Post: The window event is handled manually or by the default manager.
 *)

VAR
  ps : PAINTSTRUCT;
  rc : RECT;

BEGIN
  CASE message
    OF
      WM_SIZE:
        SetRect (graphRect, 0, 0, LOWORD (lParam), HIWORD (lParam));
        UpdateWindow (wnd);
      |
      WM_DESTROY:
  (* Delete Tools *)
        FUNC DeleteBrush (hbrBackgnd);
        PostQuitMessage (0);
      |
      WM_PAINT:
        InvalidateRect (wnd, NIL_RECT, TRUE);
        FUNC BeginPaint (wnd, ps);
        FUNC FillRect (DC, graphRect, hbrBackgnd);
        EndPaint (wnd, ps);
      |
      WM_SYSCOLORCHANGE:
  (* Change tools to coincide with system window colors *)
  (*Delete Tools*)
        FUNC DeleteBrush (hbrBackgnd);
  (* Create Tools *)
        hbrBackgnd  := CreateSolidBrush (GetSysColor (COLOR_WINDOW));
      |
      WM_ERASEBKGND:
        (* Paint over the entire client area *)
        GetClientRect (wnd, rc);
        FUNC FillRect (CAST (WindowRef, wParam), rc, hbrBackgnd);
      |
      ELSE
        (* Perform the default window processing *)
        RETURN DefWindowProc (wnd, message, wParam, lParam);
    END;
  RETURN 0;
END FrameWndProc;
<*/POP*>

PROCEDURE FrameInit () : BOOLEAN;

 (* Pre: The window is to be displayed for the first time.
   Post: The window class is initialized and registered  *)

VAR
  frameClass  : WNDCLASS;

BEGIN
  frameClass.lpszClassName := ADR (szBuffer);
  frameClass.hbrBackground := NULL_HBRUSH;
  frameClass.style         := CS_VREDRAW + CS_HREDRAW + CS_BYTEALIGNCLIENT;
  frameClass.hInstance     := Instance;
  frameClass.lpfnWndProc   := FrameWndProc;
  frameClass.hCursor       := LoadCursor (Instance, IDC_ARROW^);
  frameClass.hIcon         := LoadIcon (Instance, 'Graphics');
  frameClass.cbClsExtra    := 0;
  frameClass.cbWndExtra    := 0;
  frameClass.lpszMenuName  := NIL;

  IF RegisterClass (frameClass) = 0
    THEN
      (* Error registering class -- return *)
      RETURN FALSE;
    END;
  RETURN TRUE;
END FrameInit;

 (*--------------------------- Main Code ---------------------------*)

BEGIN
  (* Initialize variables *)
  szBuffer  := "The Best of Graphics";

  IF PrevInstance = NULL_HINSTANCE
    THEN
      (* First instance -- register window class *)
      IF NOT FrameInit ()
        THEN
          HALT (1);
        END;
    ELSE
        (* Not first instance -- reset bFirst flag *)
        bFirst := FALSE;
    END;

   (* Find the height and width of the screen *)
  DC := GetDC (NULL_HWND);
  VRes := GetDeviceCaps (DC, VERTRES);
  HRes := GetDeviceCaps (DC, HORZRES);
  FUNC ReleaseDC (NULL_HWND, DC);

       (* Create Tools*)
  hbrBackgnd  := CreateSolidBrush (GetSysColor (COLOR_WINDOW));

       (* set window height and width *)
  WindowWidth := HRes;
  WindowHeight := VRes - 30;

  Wnd := CreateWindow ( szBuffer,    (* class name              *)
           szBuffer,    (* The window name         *)
           WS_OVERLAPPEDWINDOW, (* window style            *)
           0,      (* Position window at top right *)
           0,                (* y not used              *)
           WindowWidth,      (* window Width            *)
           WindowHeight,     (* window height           *)
           NULL_HWND,        (* NULL parent handle      *)
           NULL_HMENU,       (* NULL menu/child handle  *)
           Instance,         (* program instance        *)
           NIL               (* NULL data structure ref.*)
           );

  DC := GetDC (Wnd); (* Obtain the window reference*)
  FUNC ShowWindow (Wnd, CmdShow);
  (* Pause until user closes the screen *)
  FINALLY
    WHILE GetMessage (mess, NULL_HWND, 0, 0)
      DO
        FUNC TranslateMessage (mess);
        FUNC DispatchMessage (mess);
      END;
END GraphWindow.

18.6.4 Implementing GraphPaper in MacOS

With the infrastructure now in place, GraphPaper is fairly easy to implement. The next listing is the version for the MacOS. A couple of items to note are that many of the drawing routines use the short integer (16 bit) called INT16. As calls into this module use INTEGER for the most part, users will have to be careful or there will be an overflow. Also, the MacOS uses Pascal strings for most purposes. Thus, the internal string type has to be converted into STR255 type internally.

IMPLEMENTATION MODULE GraphPaper;

(* Original design copyright 1996 by R. Sutcliffe
  Original implementation 1996 using p1 on the Macintosh
  Windows implementation 1998 05 12 by Joel Schwartz
      with use of examples written by Stony Brook
      added scaling, labelling, showing axes
  Changes ported back to the Mac 1998 05 21 by Joel Schwartz
  Removed all widow-related functionality to a separate module 1998 07 06
    to clean thing up a little more; now imports from GraphWindow
    Last revision: by RS 1998 07 11--added angle measure types
*)

FROM GraphWindow IMPORT 
  WindowRef, GetWindow, GetWDimensions;
FROM Strings IMPORT
  Concat;
FROM WholeStr IMPORT
  CardToStr, IntToStr;
FROM SYSTEM IMPORT
  STR255, TOSTR255;
IMPORT Quickdraw; (* use MoveTo and LineTo, and have our own by this name *)
FROM Quickdraw IMPORT
  PenState, SetPenState, GetPenState, SetPort;
FROM QuickdrawText IMPORT
  DrawString;
FROM RealMath IMPORT
  pi, sin, cos;
      
CONST
  convertRad = pi / 180.0;
  AspectV = 40;   (* vertical raster lines per division mark *)
  AspectH = 40;   (* Horizontal pixels per division mark *)

VAR
  activeSystem : CoordSystem;
  angleUnits : AngleType;
  width, height : INTEGER;
  xLabel, yLabel : LabelType;
  xScale, yScale : REAL;
  homeX, homeY, graphX, graphY  : REAL;
  graphArg : REAL; (* kept internally in degrees *)
  divisionValue : INTEGER;
  scaleString : LabelType;
  scaleSet, labelSet, axesDrawn : BOOLEAN;
  penPos : PenState;
  gWindow : WindowRef;

(* note that internally we use the Mac/Win position angle in which East is 0 and we rotate clockwise but in the bearing system users communicate with bearing angles in which North is zero and they rotate clockwise. *)

PROCEDURE Round (x : REAL) : INTEGER;
BEGIN
  RETURN VAL (INTEGER, x + 0.5 );
END Round;

PROCEDURE SetCoordSystem (kind : CoordSystem);
BEGIN
  activeSystem := kind;
  CASE activeSystem (* set up appropriate home *)
    OF
      bearing, standard:
        homeX := FLOAT (width) / 2.0;
        homeY := FLOAT (height) / 2.0; |
      MacWin:
        homeX := 0.0;
        homeY := 0.0
    END;
 Home; (* and go there *)
END SetCoordSystem;

PROCEDURE SetAngleType (kind : AngleType);
BEGIN
  angleUnits := kind;
END SetAngleType;

PROCEDURE ToDeg (arg : REAL) : REAL;
(* used to get angle units into degrees for internal store *)
BEGIN
  CASE angleUnits OF
    deg :
      RETURN arg |
    rad :
      RETURN arg/convertRad |
    grad :
      RETURN 0.9 * arg
    END;
END ToDeg;
      
PROCEDURE FromDeg (arg : REAL) : REAL;
(* convert from internal store to whatever units have been set. *)
BEGIN
  CASE angleUnits OF
    deg :
      RETURN arg |
    rad :
      RETURN arg * convertRad |
    grad :
      RETURN arg / 0.9
    END;
END FromDeg;

PROCEDURE Home; (* moves to 0,0 and sets angle to 0 *)
BEGIN
  TurnTo (0.0);
  graphX := homeX;
  graphY := homeY;
  Quickdraw.MoveTo (Round (graphX), Round (graphY));
END Home;

PROCEDURE ShiftOrigin (deltaX, deltaY : INTEGER);
BEGIN
  homeX := homeX + FLOAT (deltaX);
  CASE activeSystem
    OF
      bearing, standard:
        homeY := homeY - FLOAT (deltaY); |
      MacWin:
        homeY := homeY + FLOAT (deltaY);
    END;
END ShiftOrigin;

PROCEDURE GetDimensions (VAR x, y: INTEGER);
BEGIN
  GetWDimensions (x, y);
END GetDimensions;

PROCEDURE GetLocation (VAR x,y :INTEGER);
BEGIN
  x := Round (graphX - homeX);
  CASE activeSystem
    OF
      bearing, standard:
        y := Round (homeY - graphY);|
      MacWin:
        y := Round (graphY - homeY)
     END;
END GetLocation;

PROCEDURE Radians (angle : REAL) : REAL;
BEGIN
  RETURN angle * convertRad;
END Radians;

PROCEDURE MoveBy (distance : INTEGER);
BEGIN
  graphX := graphX + (FLOAT (distance) * cos (Radians (graphArg))) ;
  graphY := graphY - (FLOAT (distance) * sin (Radians (graphArg)));
  Quickdraw.MoveTo (Round (graphX), Round (graphY));
END MoveBy;

PROCEDURE MoveTo (x, y : INTEGER);
 (* have to revise coordinates to screen system *)
BEGIN
  graphX := homeX + FLOAT (x);
  CASE activeSystem
    OF
      bearing, standard:
        graphY := homeY - FLOAT (y);|
      MacWin:
        graphY := homeY + FLOAT (y);
     END;
 Quickdraw.MoveTo (Round (graphX), Round (graphY));
END MoveTo;

PROCEDURE Move (dx, dy: INTEGER);
BEGIN
  graphX := graphX + FLOAT (dx);
  CASE activeSystem
    OF
      bearing, standard:
        graphY := graphY - FLOAT (dy);|
      MacWin:
        graphY := graphY + FLOAT (dy);
     END;
  Quickdraw.MoveTo (Round (graphX), Round (graphY));
END Move;

PROCEDURE ReviseAngle (angle : REAL) : REAL;
(* this procedure is internal, so works strictly in degrees *)
BEGIN
  CASE activeSystem
    OF
      bearing:
        angle := 450.0 - angle;
        graphArg := angle - FLOAT (VAL (INTEGER, (angle / 360.0)) * 360);
        RETURN graphArg;|
      MacWin:
        angle := 360.0 - angle;
        graphArg := angle - FLOAT (VAL (INTEGER, (angle / 360.0)) * 360);
        RETURN graphArg;|
     standard:
       graphArg :=  angle - FLOAT (VAL (INTEGER, (angle / 360.0)) * 360);
       RETURN graphArg
   END;

END ReviseAngle;

PROCEDURE Turn (angle : REAL);
 (* convert if a bearing change *)
BEGIN
  angle := ToDeg (angle);
  CASE activeSystem
    OF
      bearing, MacWin:
        angle := graphArg - angle;
        graphArg := angle - FLOAT (VAL (INTEGER, (angle / 360.0)) * 360);|
      standard:
        angle := graphArg + angle;
        graphArg := angle - FLOAT (VAL (INTEGER, (angle / 360.0)) * 360)
    END;
END Turn;

PROCEDURE TurnTo (angle : REAL);
BEGIN
  angle := ReviseAngle (ToDeg (angle));
END TurnTo;

PROCEDURE GetCurrentAngle () : REAL;
BEGIN
  RETURN FromDeg (graphArg);
END GetCurrentAngle;

PROCEDURE LineBy (distance : INTEGER);
BEGIN
  graphX := graphX + (FLOAT (distance) * cos (Radians (graphArg)));
  graphY := graphY - (FLOAT (distance) * sin (Radians (graphArg)));
  Quickdraw.LineTo (Round (graphX), Round (graphY));
END LineBy;

PROCEDURE LineTo (x, y : INTEGER);
BEGIN
  graphX := homeX + FLOAT (x);
  CASE activeSystem
    OF
      bearing, standard:
        graphY := homeY - FLOAT (y);|
      MacWin:
        graphY := homeY + FLOAT (y);
   END;
 Quickdraw.LineTo (Round (graphX), Round (graphY));
END LineTo;

PROCEDURE Line (dx, dy: INTEGER);
BEGIN
  graphX := graphX + FLOAT (dx);
  CASE activeSystem
    OF
      bearing, standard:
        graphY := graphY - FLOAT (dy);|
      MacWin:
        graphY := graphY + FLOAT (dy);
  END;
  Quickdraw.LineTo (Round (graphX), Round (graphY));
END Line;

PROCEDURE Dot;
BEGIN
  Line (0, 0);
END Dot;

PROCEDURE DotAt (x, y: INTEGER);
BEGIN
  MoveTo (x,y);
  Dot;
END DotAt;

 (*----------------------- Graphing related functions -------------------*)

PROCEDURE DrawXY;
 (* Draw the axes of the graph *)
BEGIN
  CASE activeSystem 
    OF
      bearing:
        Home;
        MoveTo (0,- (height DIV 2 - 25));
        LineBy (height - 45);
        Home;
        MoveTo (- (width DIV 2 - 30), 0);
        TurnTo (90.0);
        LineBy (width - 60);
      |
      standard:
        Home;
        MoveTo (0,- (height DIV 2 - 25));
        TurnTo (90.0);
        LineBy (height - 45);
        Home;
        MoveTo (- (width DIV 2 - 30), 0);
        LineBy (width - 60);
      |
      MacWin:
        Home;
        MoveTo (0,- (height - 50));
        TurnTo (90.0);
        LineBy (height - 50);
        Home;
        MoveTo (- (width - 30), 0);
        LineBy (width - 30);
    END;
END DrawXY;

PROCEDURE SetLabels (horiz, vert : LabelType);
 (* Set the horizontal and vertical axes labels *)
BEGIN
  xLabel := horiz;
  yLabel := vert;
  labelSet := TRUE;
END SetLabels;

PROCEDURE ShowLabels;
 (* Show the labels but only if the axes have been drawn *)
BEGIN
  IF axesDrawn
    THEN
      IF ~labelSet
        THEN
          xLabel := "x";
          yLabel := "y";
        END;

      CASE activeSystem
        OF
          bearing, standard:
            GetPenState (penPos);
            penPos.pnLoc.h := width - 100;
            penPos.pnLoc.v := height DIV 2 + 20;
            SetPenState (penPos);
            DrawString (TOSTR255(xLabel));
            GetPenState (penPos);
            penPos.pnLoc.h := width DIV 2 + 20;
            penPos.pnLoc.v := 20;
            SetPenState (penPos);
            DrawString (TOSTR255(yLabel));
         |
         MacWin:
            GetPenState (penPos);
            penPos.pnLoc.h := width - 100;
            penPos.pnLoc.v := 50;
            SetPenState (penPos);
            DrawString (TOSTR255(xLabel));
            GetPenState (penPos);
            penPos.pnLoc.h := 40;
            penPos.pnLoc.v := height - 70;
            SetPenState (penPos);
            DrawString (TOSTR255(yLabel));
       END;
   END;
END ShowLabels;

PROCEDURE ShowAxes;
 (* Show the axes and draw the division marks on the axes *)
BEGIN
  DrawXY;
  IF NOT scaleSet
    THEN
      SetScale (1);
    END;
  DrawDivisionMarks;
  axesDrawn := TRUE;
END ShowAxes;

PROCEDURE SetScale (dataPerDivision : CARDINAL);

 (* Set the scale if no scale has been set up to this point.
 * NOTE: this does not support mid-graph scale changing *)
VAR
  temp, temp2 : LabelType;

BEGIN
  IF ~scaleSet
    THEN
      xScale := FLOAT (AspectH * dataPerDivision);
      yScale := FLOAT (AspectV * dataPerDivision);

     (*Set the scale to a string representation *)
      CardToStr (dataPerDivision, temp);
      Concat ('SCALE = 1 unit : ', temp, temp2);
      Concat (temp2,' division', temp);
      divisionValue := dataPerDivision;
      scaleString := temp;
      scaleSet := TRUE;
    END;
END SetScale;

PROCEDURE DrawDivisionMarks;

VAR
  counter : INTEGER;
  multiple : INTEGER;
  xMax, yMax, tempStore, determineDivision : INTEGER;
  xPos, yPos : INTEGER;
  xString, yString : ARRAY [0..5] OF CHAR;
  widthTest, heightTest, xdivisionPos : INTEGER;

BEGIN
  CASE activeSystem
    OF
      bearing, standard:
        widthTest := (width DIV 2 - 30);
        heightTest := ((height - 70) DIV 2);
        xdivisionPos := -10;
     |
     MacWin:
        widthTest := width - 40;
        heightTest := height - 40;
        xdivisionPos := - 10;
   END;

 (* Draw the division marks 1cm apart while determining where the scale marker will go.*)

 CASE activeSystem
   OF
     bearing:  (* for bearing system *)
       counter := AspectH;
       multiple := 2;
       Home;
       WHILE counter < widthTest
          DO
           MoveTo ( - counter, xdivisionPos);
           LineBy (20);
           counter := (AspectH * multiple);
           INC (multiple);
         END;
       counter := AspectH;
       multiple := 2;
       WHILE counter < widthTest
          DO
           MoveTo (counter, xdivisionPos);
           LineBy (20);
           counter := (AspectH * multiple);
           INC (multiple);
         END;
       xMax := multiple - 2;
       xPos := counter - AspectH;

       counter := AspectH;
       multiple := 2;
       WHILE counter < heightTest
          DO
           MoveTo ( -10, -counter);
           TurnTo (90.0);
           LineBy (20);
           counter := (AspectH * multiple);
           INC (multiple);
         END;
       counter := AspectH;
       multiple := 2;
       WHILE counter < heightTest
          DO
           MoveTo ( -10, counter);
           TurnTo (90.0);
           LineBy (20);
           counter := (AspectH * multiple);
           INC (multiple);
         END;

       yMax := multiple - 2;
       yPos := counter - AspectV;
     |
     standard, MacWin: (* For standard coordinate systems *)
       counter := AspectH;
       multiple := 2;
       WHILE counter < widthTest
          DO
           MoveTo ( - counter, xdivisionPos);
           TurnTo (90.0);
           LineBy (20);
           counter := (AspectH * multiple);
           INC (multiple);
         END;
       counter := AspectH;
       multiple := 2;
       WHILE counter < widthTest
          DO
           MoveTo (counter, xdivisionPos);
           TurnTo (90.0);
           LineBy (20);
           counter := (AspectH * multiple);
           INC (multiple);
         END;

       xMax := multiple - 2;
       xPos := counter - AspectH;

       counter := AspectH;
       multiple := 2;
       Home;
       WHILE counter < heightTest
          DO
           MoveTo ( -10, -counter);
           LineBy (20);
           counter := (AspectH * multiple);
           INC (multiple);
         END;
       counter := AspectH;
       multiple := 2;
       WHILE counter < heightTest
          DO
           MoveTo ( -10, counter);
           LineBy (20);
           counter := (AspectH * multiple);
           INC (multiple);
         END;
       yMax := multiple - 2;
       yPos := counter - AspectV;
   END;

  tempStore := xMax / divisionValue;
  determineDivision := xMax MOD divisionValue;
  xPos := xPos - (AspectH * determineDivision);
  IntToStr (tempStore, xString);
  CASE activeSystem
    OF
      bearing, standard:
         GetPenState (penPos);
         penPos.pnLoc.h := width DIV 2 + xPos;
         penPos.pnLoc.v := height DIV 2 + 20;
         SetPenState(penPos);
         DrawString (TOSTR255 (xString));
      |
      MacWin:
         GetPenState(penPos);
         penPos.pnLoc.h := xPos;
         penPos.pnLoc.v := 20;
         SetPenState(penPos);
         DrawString (TOSTR255 (xString));
    END;
  tempStore := yMax / divisionValue;
  determineDivision := yMax MOD divisionValue;
  yPos := yPos - (AspectV * determineDivision);
  IntToStr (tempStore, yString);
  CASE activeSystem
    OF
      bearing, standard:
         GetPenState (penPos);
         penPos.pnLoc.h := width DIV 2 - 20;
         penPos.pnLoc.v := height DIV 2 - yPos;
         SetPenState (penPos);
         DrawString (TOSTR255 (yString));
      |
      MacWin:
         GetPenState(penPos);
         penPos.pnLoc.h := 20;
         penPos.pnLoc.v := yPos;
         SetPenState(penPos);
         DrawString (TOSTR255 (yString));
    END;
  GetPenState(penPos);
  penPos.pnLoc.h := width - 250;
  penPos.pnLoc.v := height - 50;
  SetPenState (penPos);
  DrawString (TOSTR255 (scaleString));
END DrawDivisionMarks;

PROCEDURE PlotPoint (x, y : REAL);

BEGIN
  IF ~scaleSet
    THEN
      SetScale (1);
    END;
  DotAt (Round (x * xScale), Round (y * yScale));
END PlotPoint;

PROCEDURE PolarPlotPoint (radius, angle : REAL);

VAR
  x,y : REAL;

BEGIN
  IF activeSystem = MacWin
    THEN
      angle := - angle;
    END;
  angle := ReviseAngle (ToDeg (angle));

  x := radius * cos (angle * convertRad);
  y := radius * sin (angle * convertRad);

  IF ~scaleSet
    THEN     (* Make sure the graph has a scale *)
      SetScale (1);
    END;
  DotAt (Round (x * xScale), Round (y * yScale));
END PolarPlotPoint;

  (*--------------------------- Main Code ---------------------------*)

BEGIN
  (* Initialize variables *)
  (* the import of GraphWindow sets up the window for us. 
    but we had better make sure the graph port is current *)
  gWindow := GetWindow ();
  SetPort (gWindow);
  GetWDimensions (width, height);
  scaleSet  := FALSE;
  labelSet  := FALSE;
  axesDrawn := FALSE;
  graphArg  := 0.0;
  SetCoordSystem (standard); (* default *)
  Home;
END GraphPaper.

18.6.5 Implementing GraphPaper in Windows NT

Much of the code for implementing the routines is the same in the windows version as in the MacOS version. Some differences to note include:

WARNING: This implementation was done for Stonybrook Modula-2 for Win32. As numerous implementation details are certain to vary, the reader cannot expect it to work unmodified on other 32-bit Windows implementations.

IMPLEMENTATION MODULE GraphPaper;

(* Original design copyright 1996 by R. Sutcliffe
  Original implementation 1996 using p1 on the Macintosh
  Windows implementation 1998 05 12 by Joel Schwartz
    with use of examples written by Stony Brook
  Changes ported back to the Mac 1998 05 21 by Joel Schwartz
  Removed all widow-related functionality to a separate module 1998 07 06
    to clean thing up a little more; now imports from GraphWindow
  Last revision: by RS 1998 07 11
*)
FROM SYSTEM IMPORT
  FUNC;
IMPORT WINGDI;
FROM WINGDI IMPORT
  MoveToEx, TextOut;
FROM WINX IMPORT
  NIL_POINT;
FROM GraphWindow IMPORT
  WindowRef, GetWindow, GetWDimensions;
FROM Strings IMPORT
  Length, Concat;
FROM WholeStr IMPORT
  CardToStr, IntToStr;
FROM RealMath IMPORT
  sin, cos, pi;

CONST
  convertRad = pi / 180.0;
  AspectV = 40;   (* vertical raster lines per division mark *)
  AspectH = 40;   (* Horizontal pixels per division mark *)

VAR

 (* Graphics Variables *)
  window : WindowRef;
  activeSystem : CoordSystem;
  angleUnits : AngleType;
  xLabel, yLabel : LabelType;
  xScale, yScale : REAL;
  homeX, homeY, graphX, graphY  : REAL;
  graphArg : REAL; (* kept internally in degrees *)
  width, height : INTEGER;
  divisionValue : INTEGER;
  scaleString : LabelType;
  scaleSet, labelSet, axesDrawn : BOOLEAN;

 (* note that internally we use the Mac/Win position angle in which East is 0 and we rotate clockwise but in the bearing system users communicate with bearing angles in which North is zero and they rotate clockwise. *)

PROCEDURE Round (x : REAL) : INTEGER;
BEGIN
  RETURN VAL (INTEGER, x + 0.5 );
END Round;

PROCEDURE SetCoordSystem (kind : CoordSystem);
BEGIN
  activeSystem := kind;
  CASE activeSystem (* set up appropriate home *)
    OF
      bearing, standard:
        homeX := FLOAT (width) / 2.0;
        homeY := FLOAT (height) / 2.0; |
      MacWin:
        homeX := 0.0;
        homeY := 0.0
   END;
   Home; (* and go there *)
END SetCoordSystem;

PROCEDURE SetAngleType (kind : AngleType);
BEGIN
  angleUnits := kind;
END SetAngleType;

PROCEDURE ToDeg (arg : REAL) : REAL;
BEGIN
  CASE angleUnits OF
    deg :
      RETURN arg |
    rad :
      RETURN arg/convertRad |
    grad :
      RETURN 0.9 * arg
  END;
END ToDeg;

PROCEDURE FromDeg (arg : REAL) : REAL;
BEGIN
  CASE angleUnits OF
    deg :
      RETURN arg |
    rad :
      RETURN arg * convertRad |
    grad :
      RETURN arg / 0.9
  END;
END FromDeg;

PROCEDURE Home; (* moves to 0,0 and sets angle to 0 *)

BEGIN
  TurnTo (0.0);
  graphX := homeX;
  graphY := homeY;
  FUNC MoveToEx (window, Round (graphX), Round (graphY), NIL_POINT);
END Home;

PROCEDURE ShiftOrigin (deltaX, deltaY : INTEGER);
BEGIN
  homeX := homeX + FLOAT (deltaX);
  CASE activeSystem
    OF
      bearing, standard:
        homeY := homeY - FLOAT (deltaY); |
      MacWin:
        homeY := homeY + FLOAT (deltaY);
    END;
END ShiftOrigin;

PROCEDURE GetDimensions (VAR x, y: INTEGER);
BEGIN
  GetWDimensions (x, y);
END GetDimensions;

PROCEDURE GetLocation (VAR x,y :INTEGER);
BEGIN
  x := Round (graphX - homeX);
  CASE activeSystem
    OF
      bearing, standard:
        y := Round (homeY - graphY);|
      MacWin:
        y := Round (graphY - homeY)
  END;
END GetLocation;

PROCEDURE Radians (angle : REAL) : REAL;
BEGIN
  RETURN angle * convertRad;
END Radians;

PROCEDURE MoveBy (distance : INTEGER);
BEGIN
  graphX := graphX
     + (FLOAT (distance) * cos (Radians (graphArg))) ;
  graphY := graphY
     - (FLOAT (distance) * sin (Radians (graphArg)));
  FUNC MoveToEx (window, Round (graphX), Round (graphY), NIL_POINT);
END MoveBy;

PROCEDURE MoveTo (x, y : INTEGER);
 (* have to revise coordinates to screen system *)
BEGIN
  graphX := homeX + FLOAT (x);
  CASE activeSystem
    OF
      bearing, standard:
        graphY := homeY - FLOAT (y);|
      MacWin:
        graphY := homeY + FLOAT (y);
  END;
  FUNC MoveToEx (window, Round (graphX), Round (graphY), NIL_POINT);
END MoveTo;

PROCEDURE Move (dx, dy: INTEGER);
BEGIN
  graphX := graphX + FLOAT (dx);
  CASE activeSystem
    OF
      bearing, standard:
        graphY := graphY - FLOAT (dy);|
      MacWin:
        graphY := graphY + FLOAT (dy);
  END;
  FUNC MoveToEx (window, Round (graphX), Round (graphY), NIL_POINT);
END Move;

PROCEDURE ReviseAngle (angle : REAL) : REAL;
(* this procedure is internal, so works strictly in degrees *)
BEGIN
  CASE activeSystem
    OF
      bearing:
        angle := 450.0 - angle;
        graphArg := angle - FLOAT (VAL (INTEGER, (angle / 360.0)) * 360);
        RETURN graphArg;|
      MacWin:
        angle := 360.0 - angle;
        graphArg := angle - FLOAT (VAL (INTEGER, (angle / 360.0)) * 360);
        RETURN graphArg;|
      standard:
        graphArg :=  angle - FLOAT (VAL (INTEGER, (angle / 360.0)) * 360);
  RETURN graphArg
  END;

END ReviseAngle;

PROCEDURE Turn (angle : REAL);
 (* convert if a bearing change *)
BEGIN
  angle := ToDeg (angle);
  CASE activeSystem
    OF
      bearing, MacWin:
        angle := graphArg - angle;
        graphArg := angle - FLOAT (VAL (INTEGER, (angle / 360.0)) * 360);|
      standard:
        angle := graphArg + angle;
        graphArg := angle - FLOAT (VAL (INTEGER, (angle / 360.0)) * 360)
  END;
END Turn;

PROCEDURE TurnTo (angle : REAL);
BEGIN
 angle := ReviseAngle (ToDeg (angle));
END TurnTo;

PROCEDURE GetCurrentAngle () : REAL;
BEGIN
  RETURN FromDeg (graphArg);
END GetCurrentAngle;

PROCEDURE LineBy (distance : INTEGER);
BEGIN
  graphX := graphX + (FLOAT (distance) * cos (Radians (graphArg)));
  graphY := graphY - (FLOAT (distance) * sin (Radians (graphArg)));
  WINGDI.LineTo (window, Round (graphX), Round (graphY));
END LineBy;

PROCEDURE LineTo (x, y : INTEGER);
BEGIN
  graphX := homeX + FLOAT (x);
  CASE activeSystem
    OF
      bearing, standard:
        graphY := homeY - FLOAT (y);|
      MacWin:
        graphY := homeY + FLOAT (y);
  END;
  WINGDI.LineTo (window, Round (graphX), Round (graphY));
END LineTo;

PROCEDURE Line (dx, dy: INTEGER);
BEGIN
  graphX := graphX + FLOAT (dx);
  CASE activeSystem
    OF
      bearing, standard:
        graphY := graphY - FLOAT (dy);|
      MacWin:
        graphY := graphY + FLOAT (dy);
  END;
  WINGDI.LineTo (window, Round (graphX), Round (graphY));
END Line;

PROCEDURE Dot;
BEGIN
  Line (1, 1);
END Dot;

PROCEDURE DotAt (x, y: INTEGER);
BEGIN
  MoveTo (x,y);
  Dot;
END DotAt;

 (*----------------------- Graphing related functions -------------------*)

PROCEDURE DrawXY;
 (* Draw the axes of the graph *)
BEGIN
  CASE activeSystem
    OF
      bearing:
        Home;
        MoveTo (0,- (height DIV 2 - 50));
        LineBy (height - 70);
        Home;
        MoveTo (- (width DIV 2 - 30), 0);
        TurnTo (90.0);
        LineBy (width - 60);
      |
      standard:
        Home;
        MoveTo (0,- (height DIV 2 - 50));
        TurnTo (90.0);
        LineBy (height - 70);
        Home;
        MoveTo (- (width DIV 2 - 30), 0);
        LineBy (width - 60);
      |
      MacWin:
        Home;
        MoveTo (0,- (height - 50));
        TurnTo (90.0);
        LineBy (height - 50);
        Home;
        MoveTo (- (width - 30), 0);
        LineBy (width - 30);
    END;
END DrawXY;

PROCEDURE SetLabels (horiz, vert : LabelType);
 (* Set the horizontal and vertical axes labels *)
BEGIN
  xLabel := horiz;
  yLabel := vert;
  labelSet := TRUE;
END SetLabels;

PROCEDURE ShowLabels;
 (* Show the labels but only if the axes have been drawn *)
BEGIN
  IF axesDrawn
    THEN
      IF NOT (labelSet)
        THEN
          xLabel := "x";
          yLabel := "y";
        END;

      CASE activeSystem
        OF
          bearing, standard:
            TextOut (window, (width - 100), (height DIV 2 + 50), xLabel, Length (xLabel));
            TextOut (window, (width DIV 2 + 20), 20, yLabel, Length (yLabel));
          |
          MacWin:
            TextOut (window, (width - 100), 50, xLabel, Length (xLabel));
            TextOut (window, (40), (height - 70), yLabel, Length (yLabel));
        END;
    END;

END ShowLabels;

PROCEDURE ShowAxes;
 (* Show the axes and draw the division marks on the axes *)
BEGIN
  DrawXY;
  IF NOT scaleSet
    THEN
      SetScale (1);
    END;
  DrawDivisionMarks;
  axesDrawn := TRUE;
END ShowAxes;

PROCEDURE SetScale (dataPerDivision : CARDINAL);

 (* Set the scale if no scale has been set up to this point.
 * NOTE: this does not support mid-graph scale changing *)
VAR
  temp, temp2 : LabelType;

BEGIN
  IF ~scaleSet
    THEN
      xScale := FLOAT (AspectH * dataPerDivision);
      yScale := FLOAT (AspectV * dataPerDivision);

     (* Set the scale to a string representation *)
      CardToStr (dataPerDivision, temp);
      Concat ('SCALE = 1 unit : ', temp, temp2);
      Concat (temp2,' division', temp);
      divisionValue := dataPerDivision;
      scaleString := temp;
      scaleSet := TRUE;
    END;
END SetScale;

PROCEDURE DrawDivisionMarks;

VAR
  counter : INTEGER;
  multiple : INTEGER;
  xMax, yMax, tempStore, determineDivision : INTEGER;
  xPos, yPos : INTEGER;
  xString, yString : ARRAY [0..5] OF CHAR;
  widthTest, heightTest, xdivisionPos : INTEGER;

BEGIN
  CASE activeSystem
    OF
      bearing, standard:
        widthTest := (width DIV 2 - 30);
        heightTest := ((height - 70) DIV 2);
        xdivisionPos := -10;
      |
      MacWin:
        widthTest := width - 40;
        heightTest := height - 40;
        xdivisionPos := - 10;
    END;

 (* Draw the division marks 1cm apart while determining where the scale marker will go.*)

  CASE activeSystem
    OF
      bearing:  (* for bearing system *)
        counter := AspectH;
        multiple := 2;
        Home;
        WHILE counter < widthTest
          DO
            MoveTo ( - counter, xdivisionPos);
            LineBy (20);
            counter := (AspectH * multiple);
            INC (multiple);
          END;
        counter := AspectH;
        multiple := 2;
        WHILE counter < widthTest
          DO
            MoveTo (counter, xdivisionPos);
            LineBy (20);
            counter := (AspectH * multiple);
            INC (multiple);
          END;
        xMax := multiple - 2;
        xPos := counter - AspectH;

        counter := AspectH;
        multiple := 2;
        WHILE counter < heightTest
          DO
            MoveTo ( -10, -counter);
            TurnTo (90.0);
            LineBy (20);
            counter := (AspectH * multiple);
            INC (multiple);
          END;
        counter := AspectH;
        multiple := 2;
        WHILE counter < heightTest
          DO
            MoveTo ( -10, counter);
            TurnTo (90.0);
            LineBy (20);
            counter := (AspectH * multiple);
            INC (multiple);
          END;

        yMax := multiple - 2;
        yPos := counter - AspectV;
      |
      standard, MacWin: (* For standard coordinate systems *)
        counter := AspectH;
        multiple := 2;
        WHILE counter < widthTest
          DO
            MoveTo ( - counter, xdivisionPos);
            TurnTo (90.0);
            LineBy (20);
            counter := (AspectH * multiple);
            INC (multiple);
          END;
        counter := AspectH;
        multiple := 2;
        WHILE counter < widthTest
          DO
            MoveTo (counter, xdivisionPos);
            TurnTo (90.0);
            LineBy (20);
            counter := (AspectH * multiple);
            INC (multiple);
          END;

        xMax := multiple - 2;
        xPos := counter - AspectH;

        counter := AspectH;
        multiple := 2;
        Home;
        WHILE counter < heightTest
          DO
            MoveTo ( -10, -counter);
            LineBy (20);
            counter := (AspectH * multiple);
            INC (multiple);
          END;
        counter := AspectH;
        multiple := 2;
        WHILE counter < heightTest
          DO
            MoveTo ( -10, counter);
            LineBy (20);
            counter := (AspectH * multiple);
            INC (multiple);
          END;
        yMax := multiple - 2;
        yPos := counter - AspectV;
    END;

  tempStore := xMax / divisionValue;
  determineDivision := xMax MOD divisionValue;
  xPos := xPos - (AspectH * determineDivision);
  IntToStr (tempStore, xString);
  CASE activeSystem
    OF
      bearing, standard:
    TextOut (window, (width DIV 2 + xPos), (height DIV 2 + 20), xString , Length (xString));
      |
      MacWin:
        TextOut (window, (xPos), (20), xString , Length (xString));
    END;
  tempStore := yMax / divisionValue;
  determineDivision := yMax MOD divisionValue;
  yPos := yPos - (AspectV * determineDivision);
  IntToStr (tempStore, yString);
  CASE activeSystem
    OF
      bearing, standard:
      TextOut (window, (width DIV 2 - 20), (height DIV 2 - yPos), yString, Length (yString));
      |
      MacWin:
        TextOut (window, (20), (yPos), yString, Length (yString));
    END;
  IF axesDrawn
    THEN
      TextOut (window, (width - 250), height - 50, scaleString, Length (scaleString));
    END;
END DrawDivisionMarks;

PROCEDURE PlotPoint ( x, y : REAL);

BEGIN
  IF NOT (scaleSet)
    THEN
      SetScale (1);
    END;
  DotAt (Round (x * xScale), Round (y * yScale));
END PlotPoint;

PROCEDURE PolarPlotPoint (radius, angle : REAL);

VAR
  x,y : REAL;

BEGIN
  IF activeSystem = MacWin
    THEN
      angle := - angle;
    END;
  angle := ReviseAngle (ToDeg (angle));

  x := radius * cos (angle * convertRad);
  y := radius * sin (angle * convertRad);

  IF ~scaleSet
    THEN     (* Make sure the graph has a scale *)
      SetScale (1);
    END;
 DotAt (Round (x * xScale), Round (y * yScale));
END PolarPlotPoint;

(*--------------- Main Code ----------------*)

BEGIN
  (* Initialize variables *)
  (* the import of GraphWindow sets up the window for us. *)
  window := GetWindow ();  (* obtain the variable associated with the graph window *)
  GetWDimensions (width, height);
  scaleSet  := FALSE;
  labelSet  := FALSE;
  axesDrawn := FALSE;
  graphArg  := 0.0;
  SetCoordSystem (standard); (* default *)
  Home;
  (* Pause until user closes the screen *)

END GraphPaper.

Again, the reader is invited to peruse the details of the API, but they are not going to be explained in detail here. However, the implementations of GraphWindow and GraphPaper between them, ought to provide a means of getting started on other programs in Windows NT.


Contents