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.
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.
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.
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.
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.
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.