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.