/* * File: graphics.c * Last modified on Mon Jul 25 11:53:41 1994 by eroberts * ----------------------------------------------------- * This file implements the graphics.h and extgraph.h interfaces * for the Macintosh using THINK's Lightspeed C. */ /* * General implementation notes * ---------------------------- * This implementation is relatively complex for two reasons. * The first source of complexity is that windows on the * Macintosh are dynamically repositioned, obscured, and * reexposed. When these events occur, the drawing in the * window needs to be repainted. Thus, it is not sufficient * to have the drawing package simply issue the appropriate * QuickDraw procedures directly to the screen. This * implementation solves the problem by rendering the image * to an offscreen bitmap and then repainting the image from * that copy whenever update events occur. * * The second problem, which is indeed more severe, is that * that is much more easily understood by experts than by * novices. In a traditional Macintosh application, the * main program is coded as an "event loop," which * continually polls the event manager to see if any activity * is necessary. This strategy runs counter to the more * conventional view of a main program as the highest level * in the problem decomposition. In the context of teaching * programming, we want the main program and its subprograms * to draw the figures -- a strategy which is not easy to * reconcile with the standard Macintosh approach. This * implementation solves this problem by patching in a call * to an update procedure as part of the event loop embedded * in the console input mechanism. */ #include #include #include #include #include #include #include #include "genlib.h" #include "gcalloc.h" #include "strlib.h" #include "extgraph.h" /* * Parameters * ---------- * MinConsoleLines -- Console height never drops below this * MaxConsoleLines -- Console height never drops exceeds this * Console Margin -- Required margin between windows * DefaultFont -- Font used to implement "Default" font * DefaultSize -- Initial point size for text */ #define MinConsoleLines 8 #define MaxConsoleLines 20 #define ConsoleMargin 3 #define DefaultFont "Geneva" #define DefaultSize 10 /* * Other constants * --------------- * LargeInt -- Used to signify out of range values * Epsilon -- Small arithmetic offset to reduce aliasing/banding * Pi -- Mathematical constant pi */ #define LargeInt 16000 #define Epsilon 0.00000000001 #define Pi 3.1415926535 /* * Type: graphicsStateT * -------------------- * This structure holds the variables that make up the graphics state. */ typedef struct graphicsStateT { double cx, cy; string font; int size; bool erase; struct graphicsStateT *link; } *graphicsStateT; /* * Type: regionStateT * ------------------ * The region assembly process has the character of a finite state * machine with the following four states: * * NoRegion Region has not yet been started * RegionStarting Region is started but no line segments yet * RegionActive First line segment appears * PenHasMoved Pen has moved during definition * * The current state determines whether other operations are legal * at that point. */ typedef enum { NoRegion, RegionStarting, RegionActive, PenHasMoved } regionStateT; /* * Global variables * ---------------- * initialized -- TRUE if initialization has been done * windowTitle -- Current window title (initialized statically) * gConsole -- Pointer to console file for graphics window * osPort -- Offscreen memory port for rendering * xResolution -- Horizontal resolution of screen (dots per inch) * yResolution -- Vertical resolution of screen (dots per inch) * windowWidth -- Width of graphics window * windowHeight -- Height of graphics window * regionState -- Current state of the region * regionGray -- Gray scale to apply to region * stateStack -- Stack of graphicStateT blocks * cx, cy -- Current coordinates | These four variable * eraseMode -- Setting of erase flag | consititute the * textFont -- Current font | current graphics * pointSize -- Current point size | state */ static bool initialized = FALSE; static string windowTitle = "Graphics Window"; static FILE *gConsole; static GrafPtr osPort; static short xResolution; static short yResolution; static double windowWidth, windowHeight; static regionStateT regionState; static double regionGray; static graphicsStateT stateStack; static double cx, cy; static bool eraseMode; static string textFont; static int pointSize; /* * Static table: grayList * ---------------------- * This table contains the bitmap patterns for the various gray * scale values. Adding more patterns to this list increases * the precision at which the client can specify gray scales. */ static Pattern grayList[] = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22 }, { 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA }, { 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD }, { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, }; #define NGrays (sizeof grayList / sizeof grayList[0]) /* Private function prototypes */ static void InitCheck(void); static void InitGraphicsState(void); static void EraseWindow(void); static void CreateGraphicsWindow(void); static int ComputeRowPixels(void); static void DisplayLine(double x, double y, double dx, double dy); static void DisplayArc(double x, double y, double rx, double ry, double start, double sweep); static void RenderArc(double x, double y, double rx, double ry, double start, double sweep); static void DisplayText(double x, double y, string text, string font, int size); static void SetArcBB(Rect *rp, double xc, double yc, double rx, double ry, double start, double sweep); static void SetTextBB(Rect *rp, double x, double y, string text, string font, int size); static void UpdateRect(Rect *rp); static StringPtr PascalString(string cstr); static int GetFontNumber(string fontName); static void PatchEventHandler(void); static long FindSystemTrap(short trap); static void MyEventHandler(EventRecord *ep); static void DoUpdateDisplay(void); static int RectWidth(Rect r); static int RectHeight(Rect r); static double Radians(double degrees); static double Degrees(double radians); static double InchesX(int x); static double InchesY(int y); static int PixelsX(double x); static int PixelsY(double y); static int ScaleX(double x); static int ScaleY(double y); static int Round(double x); static int Min(int x, int y); static int Max(int x, int y); static double MinF(double x, double y); static double MaxF(double x, double y); /* Exported entries */ /* Section 1 -- Basic functions from graphics.h */ void InitGraphics(void) { InitGraphicsState(); if (initialized) { EraseWindow(); } else { ProtectVariable(stateStack); ProtectVariable(windowTitle); ProtectVariable(textFont); CreateGraphicsWindow(); PatchEventHandler(); initialized = TRUE; } } void MovePen(double x, double y) { InitCheck(); if (regionState == RegionActive) regionState = PenHasMoved; cx = x; cy = y; } void DrawLine(double dx, double dy) { InitCheck(); switch (regionState) { case NoRegion: DisplayLine(cx, cy, dx, dy); break; case RegionStarting: case RegionActive: DisplayLine(cx, cy, dx, dy); regionState = RegionActive; break; case PenHasMoved: Error("Region segments must be contiguous"); } cx += dx; cy += dy; } void DrawArc(double r, double start, double sweep) { DrawEllipticalArc(r, r, start, sweep); } double GetWindowWidth(void) { InitCheck(); return (windowWidth); } double GetWindowHeight(void) { InitCheck(); return (windowHeight); } double GetCurrentX(void) { InitCheck(); return (cx); } double GetCurrentY(void) { InitCheck(); return (cy); } /* Section 2 -- Elliptical arcs */ void DrawEllipticalArc(double rx, double ry, double start, double sweep) { double x, y; InitCheck(); x = cx + rx * cos(Radians(start + 180)); y = cy + ry * sin(Radians(start + 180)); switch (regionState) { case NoRegion: DisplayArc(x, y, rx, ry, start, sweep); break; case RegionStarting: case RegionActive: RenderArc(x, y, rx, ry, start, sweep); regionState = RegionActive; break; case PenHasMoved: Error("Region segments must be contiguous"); } cx = x + rx * cos(Radians(start + sweep)); cy = y + ry * sin(Radians(start + sweep)); } /* Section 3 -- Graphical structures */ void StartFilledRegion(double grayScale) { GrafPtr saveWindow; InitCheck(); if (regionState != NoRegion) { Error("Region is already in progress"); } if (grayScale < 0 || grayScale > 1) { Error("Gray scale for regions must be between 0 and 1"); } regionState = RegionStarting; regionGray = grayScale; GetPort(&saveWindow); SetPort((WindowPtr) osPort); OpenRgn(); SetPort(saveWindow); } void EndFilledRegion(void) { RgnHandle region; GrafPtr saveWindow; int px; InitCheck(); switch (regionState) { case NoRegion: Error("EndFilledRegion without StartFilledRegion"); case RegionStarting: regionState = NoRegion; return; case RegionActive: case PenHasMoved: regionState = NoRegion; break; } GetPort(&saveWindow); SetPort((WindowPtr) osPort); region = NewRgn(); CloseRgn(region); UpdateRect(&(*region)->rgnBBox); px = regionGray * (NGrays - 1) + 0.5 - Epsilon; FillRgn(region, grayList[(eraseMode) ? 0 : px]); SetPort(saveWindow); DisposeRgn(region); } /* Section 4 -- String functions */ void DrawTextString(string text) { InitCheck(); if (regionState != NoRegion) { Error("Text strings are illegal inside a region"); } DisplayText(cx, cy, text, textFont, pointSize); cx += TextStringWidth(text); } double TextStringWidth(string text) { Rect rect; InitCheck(); SetTextBB(&rect, cx, cy, text, textFont, pointSize); return (InchesX(rect.right - rect.left)); } void SetFont(string font) { InitCheck(); if (GetFontNumber(font) != 0) { textFont = CopyString(font); } } string GetFont(void) { InitCheck(); return (CopyString(textFont)); } void SetPointSize(int size) { InitCheck(); pointSize = size; } int GetPointSize(void) { InitCheck(); return (pointSize); } /* Section 5 -- Miscellaneous functions */ void SetEraseMode(bool mode) { GrafPtr saveWindow; InitCheck(); eraseMode = mode; GetPort(&saveWindow); SetPort((WindowPtr) osPort); PenMode((mode) ? patBic : patCopy); TextMode((mode) ? srcBic : srcOr); SetPort(saveWindow); } bool GetEraseMode(void) { InitCheck(); return (eraseMode); } void SetWindowTitle(string title) { windowTitle = CopyString(title); if (initialized) { SetWTitle(gConsole->window, PascalString(title)); } } string GetWindowTitle(void) { return (CopyString(windowTitle)); } void UpdateDisplay(void) { int cnt; InitCheck(); DoUpdateDisplay(); } void Pause(double seconds) { long dummy; UpdateDisplay(); Delay(seconds * CLOCKS_PER_SEC, &dummy); } void SaveGraphicsState(void) { graphicsStateT sb; InitCheck(); sb = New(graphicsStateT); sb->cx = cx; sb->cy = cy; sb->font = textFont; sb->size = pointSize; sb->erase = eraseMode; sb->link = stateStack; stateStack = sb; } void RestoreGraphicsState(void) { graphicsStateT sb; InitCheck(); if (stateStack == NULL) { Error("RestoreGraphicsState called before SaveGraphicsState"); } sb = stateStack; cx = sb->cx; cy = sb->cy; textFont = sb->font; pointSize = sb->size; eraseMode = sb->erase; stateStack = sb->link; FreeBlock(sb); } /* Private functions */ /* * Function: InitCheck * Usage: InitCheck(); * ------------------- * This function merely ensures that the package has been * initialized before the client functions are called. */ static void InitCheck(void) { if (!initialized) Error("InitGraphics has not been called"); } /* * Function: InitGraphicsState * Usage: InitGraphicsState(); * --------------------------- * This function initializes the graphics state elements to * their default values. */ static void InitGraphicsState(void) { cx = cy = 0; eraseMode = FALSE; textFont = "Default"; pointSize = DefaultSize; stateStack = NULL; regionState = NoRegion; } /* * Function: EraseWindow * Usage: EraseWindow(); * --------------------- * This function erases all of the offscreen memory bits, thereby * clearing the screen image. Note that the osPort->portBits array * is typically larger than a Macintosh segment and therefore cannot * be cleared with array operations or memset. */ static void EraseWindow(void) { GrafPtr saveWindow; WindowPeek window; Rect rect; int width, height; GetPort(&saveWindow); SetPort((WindowPtr) (window = gConsole->window)); width = window->port.portRect.right - window->port.portRect.left; height = window->port.portRect.bottom - window->port.portRect.top; SetRect(&rect, 0, 0, width, height); InvalRect(&rect); SetPort(saveWindow); CopyBits(&osPort->portBits, &osPort->portBits, &osPort->portRect, &osPort->portRect, srcBic, NULL); } /* * Function: CreateGraphicsWindow * Usage: CreateGraphicsWindow(); * ------------------------------ * This function creates the graphics window on the screen and * the offscreen memory to back it up. The graphics window is * created by using the THINK C console package, even though * the window is never used for text; the graphics package * instead uses the window as a drawing space. Most of the * code here is used to compute the dimensions of the windows * based on the size of the screen. */ static void CreateGraphicsWindow(void) { GrafPtr saveWindow, wPort; WindowPeek window; double consoleHeight, screenHeight, graphicsHeight; int width, height, rowPixels, consoleRows, saveTop; ScreenRes(&xResolution, &yResolution); rowPixels = ComputeRowPixels(); GetWMgrPort(&wPort); screenHeight = InchesY(RectHeight(wPort->portRect)); saveTop = console_options.top; graphicsHeight = InchesY(console_options.nrows * rowPixels); consoleHeight = screenHeight - graphicsHeight - InchesY(saveTop + 2 * ConsoleMargin); consoleRows = consoleHeight / InchesY(rowPixels); if (consoleRows > MaxConsoleLines) { consoleRows = MaxConsoleLines; } if (consoleRows < MinConsoleLines) { graphicsHeight -= InchesY((MinConsoleLines - consoleRows) * rowPixels); consoleRows = MinConsoleLines; } console_options.top += PixelsY(graphicsHeight) + ConsoleMargin; console_options.nrows = consoleRows; console_options.title = "\pConsole Window"; printf("\n"); fflush(stdout); console_options.top = saveTop; console_options.nrows = PixelsY(graphicsHeight) / rowPixels; console_options.title = PascalString(windowTitle); gConsole = fopenc(); SetPort((WindowPtr) (window = gConsole->window)); width = RectWidth(window->port.portRect); height = RectHeight(window->port.portRect); windowWidth = InchesX(width); windowHeight = InchesY(height); osPort = malloc(sizeof(GrafPort)); *osPort = window->port; osPort->portBits.baseAddr = calloc((long) height * osPort->portBits.rowBytes, 1); TextFont(console_options.txFont); TextSize(console_options.txSize); TextFace(console_options.txFace); fprintf(gConsole, "\n"); fflush(gConsole); } /* * Function: ComputeRowPixels * Usage: rowPixels = ComputeRowPixels(); * -------------------------------------- * This function estimates the number of pixels in a text row on * the screen. The complication in the implementation is that the * actual font information cannot be obtained using GetFontInfo * until the Toolbox has been initialized. Unfortunately, C * programs that use the console package do not initialize the * Toolbox until the console window is created. This code * therefore estimates the size from the parameters. If * other fonts are used for the console in addition to Geneva * and Monaco, this function can be augmented to include them. */ static int ComputeRowPixels(void) { short monaco; GetFNum("\pMonaco", &monaco); if (console_options.txFont == monaco) { return (Round(console_options.txSize * 1.5)); } else { return (Round(console_options.txSize * 1.2)); } } /* * Function: DisplayLine * Usage: DisplayLine(x, y, dx, dy); * --------------------------------- * This function displays a line segment from the point (x, y) * to the point (x + dx, y + dy). Like all of the other graphical * operations, the line is rendered into offscreen memory. The * actual screen is updated only in response to the update event. */ static void DisplayLine(double x, double y, double dx, double dy) { GrafPtr saveWindow; WindowPeek window; Rect rect; int ix0, iy0, ix1, iy1; ix0 = ScaleX(x); iy0 = ScaleY(y); ix1 = ScaleX(x + dx); iy1 = ScaleY(y + dy); SetRect(&rect, Min(ix0,ix1), Min(iy0,iy1), Max(ix0,ix1), Max(iy0,iy1)); UpdateRect(&rect); GetPort(&saveWindow); SetPort((WindowPtr) osPort); MoveTo(ix0, iy0); LineTo(ix1, iy1); SetPort(saveWindow); } /* * Function: DisplayArc * Usage: DisplayArc(x, y, rx, ry, start, sweep); * --------------------------------------------- * This function displays an elliptical arc segment centered at * the point (x, y). (Note that the argument signature is different * from that of the client function DrawEllipticalArc.) */ static void DisplayArc(double x, double y, double rx, double ry, double start, double sweep) { GrafPtr saveWindow; WindowPeek window; Rect rect; int ix0, iy0, ix1, iy1, istart, isweep; istart = Round(start); isweep = Round(sweep); ix0 = ScaleX(x - rx); iy0 = ScaleY(y + ry); ix1 = ix0 + PixelsX(2 * rx); iy1 = iy0 + PixelsY(2 * ry); SetArcBB(&rect, x, y, rx, ry, start, sweep); UpdateRect(&rect); GetPort(&saveWindow); SetPort((WindowPtr) osPort); SetRect(&rect, ix0, iy0, ix1+1, iy1+1); FrameArc(&rect, 90 - istart, -isweep); SetPort(saveWindow); } /* * Function: RenderArc * Usage: RenderArc(x, y, rx, ry, start, sweep); * --------------------------------------------- * This function is identical to the DisplayArc function except * that the arc is rendered using line segments. This complication * is necessary on the Macintosh for filled shapes because arcs are * not counted as region boundaries. As an optimization, a * complete circle or oval is drawn using the DrawOval tool, which * does add a region boundary. The arc unit is chosen to represent * a pixel width at the specified arc radius. */ static void RenderArc(double x, double y, double rx, double ry, double start, double sweep) { GrafPtr saveWindow; WindowPeek window; double t, mint, maxt, dt; int ix0, iy0, ix1, iy1; Rect rect; GetPort(&saveWindow); SetPort((WindowPtr) osPort); if (sweep < 0) { start += sweep; sweep = -sweep; } if (sweep > 360 - Epsilon) { ix0 = ScaleX(x - rx); iy0 = ScaleY(y + ry); ix1 = ix0 + PixelsX(2 * rx); iy1 = iy0 + PixelsY(2 * ry); SetRect(&rect, ix0, iy0, ix1+1, iy1+1); FrameOval(&rect); } else { dt = atan2(InchesY(1), MaxF(fabs(rx), fabs(ry))); mint = Radians(start); maxt = Radians(start + sweep); for (t = mint; t < maxt; t += dt) { if (t > maxt - dt / 2) t = maxt; ix0 = ScaleX(x + rx * cos(t)); iy0 = ScaleY(y + ry * sin(t)); if (t == mint) { MoveTo(ix0, iy0); } else { LineTo(ix0, iy0); } } } SetArcBB(&rect, x, y, rx, ry, start, sweep); UpdateRect(&rect); SetPort(saveWindow); } /* * Function: DisplayText * Usage: DisplayText(x, y, text, font, size); * ------------------------------------------- * Displays the specified text starting at the point (x, y) * at the given font and size. */ static void DisplayText(double x, double y, string text, string font, int size) { GrafPtr saveWindow; WindowPeek window; Rect rect; int ix, iy; ix = ScaleX(x); iy = ScaleY(y); SetTextBB(&rect, x, y, text, font, size); UpdateRect(&rect); GetPort(&saveWindow); SetPort((WindowPtr) osPort); MoveTo(ix, iy); DrawText(text, 0, strlen(text)); SetPort(saveWindow); } /* * Function: SetArcBB * Usage: SetArcBB(&rect, xc, yc, rx, ry, start, sweep); * ----------------------------------------------------- * This function sets the rectangle dimensions to the bounding * box of the arc segment specified by the remaining arguments. */ static void SetArcBB(Rect *rp, double xc, double yc, double rx, double ry, double start, double sweep) { int xmax, xmin, ymax, ymin; int xl, xr, yt, yb; int ix0, iy0, ix1, iy1; xmin = ScaleX(xc - rx); ymin = ScaleY(yc + ry); xmax = xmin + PixelsX(2 * rx); ymax = ymin + PixelsX(2 * ry); if (sweep < 0) { start += sweep; sweep = -sweep; } if (sweep >= 360) { SetRect(rp, xmin, ymin, xmax, ymax); return; } if (start < 0) { start = 360 - fmod(-start, 360); } else { start = fmod(start, 360); } ix0 = ScaleX(xc + rx * cos(Radians(start))); iy0 = ScaleY(yc + ry * sin(Radians(start))); ix1 = ScaleX(xc + rx * cos(Radians(start + sweep))); iy1 = ScaleY(yc + ry * sin(Radians(start + sweep))); if (start + sweep > 360) { xr = xmax; } else { xr = Max(ix0, ix1); } start = fmod(start + 270, 360); if (start + sweep > 360) { yt = ymin; } else { yt = Min(iy0, iy1); } start = fmod(start + 270, 360); if (start + sweep > 360) { xl = xmin; } else { xl = Min(ix0, ix1); } start = fmod(start + 270, 360); if (start + sweep > 360) { yb = ymax; } else { yb = Max(iy0, iy1); } SetRect(rp, xl, yt, xr + 1, yb + 1); } /* * Function: SetTextBB * Usage: SetTextBB(&rect, x, y, text, font, size); * ------------------------------------------------ * This function sets the rectangle dimensions to the bounding * box of the text string specified by the remaining arguments. */ static void SetTextBB(Rect *rp, double x, double y, string text, string font, int size) { GrafPtr saveWindow; WindowPeek window; FontInfo fInfo; int ix, iy, width, fnum; GetPort(&saveWindow); SetPort((WindowPtr) osPort); ix = ScaleX(x); iy = ScaleY(y); fnum = GetFontNumber(font); if (fnum == 0) Error("Illegal font"); TextFont(fnum); TextSize(size); GetFontInfo(&fInfo); width = TextWidth(text, 0, strlen(text)); SetRect(rp, ix, iy - fInfo.ascent, ix + width, iy + fInfo.descent); SetPort(saveWindow); } /* * Function: UpdateRect * Usage: UpdateRect(&rect); * ------------------------- * This function sets the invalid area of the screen to include * the update rectangle. */ static void UpdateRect(Rect *rp) { GrafPtr saveWindow; WindowPeek window; Rect rect; GetPort(&saveWindow); SetPort((WindowPtr) (window = gConsole->window)); rect = *rp; InsetRect(&rect, -1, -1); InvalRect(&rect); SetPort(saveWindow); } /* * Function: PascalString * Usage: pstr = PascalString(cstr); * -------------------------------- * This function generates a dynamically allocated Pascal string * from the corresponding C string. */ static StringPtr PascalString(string cstr) { int len; StringPtr pstr; len = strlen(cstr); pstr = GetBlock(len + 2); strcpy((char *) &pstr[1], cstr); pstr[0] = len; return (pstr); } /* * Function: GetFontNumber * Usage: fontNumber = GetFontNumber(fontName); * -------------------------------------------- * This function returns the font number corresponding to a * Macintosh font name. */ static int GetFontNumber(string fontName) { StringPtr pstr; short fnum; if (StringEqual(fontName, "Default")) fontName = DefaultFont; GetFNum(pstr = PascalString(fontName), &fnum); FreeBlock(pstr); return (fnum); } /* * Event handler * ------------- * The trickiest part of the graphics library code is the * following patch system, which adds a call to MyEventHandler * prior to the SystemEvent trap. This makes it possible for * the same event loop that handles console events to update * the graphics window instead. */ static char patchArea[0x20]; /* * 00 PEA $FFF0(A6) * 04 JSR xxxx.L * 0A CLR.L -(A7) * 0C PEA $FFF0(A6) * 10 $A9B2 * 12 JMP yyyy.L */ /* * Function: PatchEventHandler * Usage: PatchEventHandler(); * --------------------------- * This call inserts the patch into the event handling code to * call MyEventHandler. The function first finds the address * of the SystemEvent trap and then replaces it with a jump * to the code fragment shown above, after xxxx is replaced * by the address of MyEventHandler and yyyy by the address * after the SystemEvent trap in the existing code. */ static void PatchEventHandler(void) { long trapAddress; char *patch; trapAddress = FindSystemTrap(_SystemEvent); patch = patchArea; if ((int) patch & 1) patch++; memcpy(&patch[0x00], (void *) (trapAddress - 4), 4); *((short *) (&patch[0x04])) = 0x4EB9; *((long *) (&patch[0x06])) = (long) MyEventHandler; *((short *) (&patch[0x0A])) = 0x42A7; memcpy(&patch[0x0C], (void *) (trapAddress - 4), 6); *((short *) (&patch[0x12])) = 0x4EF9; *((long *) (&patch[0x14])) = trapAddress + 2; *((short *) (trapAddress - 4)) = 0x4EF9; *((long *) (trapAddress - 2)) = (long) patch; } /* * Function: FindSystemTrap * Usage: addr = FindSystemTrap(trap); * ---------------------------------- * This function finds the first occurrence of the specified trap * in the code stream beginning at the input function. */ static long FindSystemTrap(short trap) { long ptr, base; int i; base = (long) stdin->proc; if (*((short *) base) == 0x4EF9) { base = *((long *) (base + 2)); } for (ptr = base; *((short *) ptr) != trap; ptr -= 2); return (ptr); } /* * Function: MyEventHandler * Usage: MyEventHandler(); * ------------------------ * This function catches update events for the console window. */ static void MyEventHandler(EventRecord *ep) { if (ep->what != updateEvt) return; if ((WindowPeek) ep->message != gConsole->window) return; DoUpdateDisplay(); } /* * Function: DoUpdateDisplay * Usage: DoUpdateDisplay(); * ------------------------- * This function copies the bits from the offscreen memory to * the invalid area of the display. */ static void DoUpdateDisplay(void) { GrafPtr saveWindow; WindowPeek window; Rect rect; int width, height; window = (WindowPeek) gConsole->window; BeginUpdate((WindowPtr) window); GetPort(&saveWindow); SetPort((WindowPtr) window); CopyBits(&osPort->portBits, &window->port.portBits, &osPort->portRect, &window->port.portRect, srcCopy, NULL); SetPort(saveWindow); EndUpdate((WindowPtr) window); } /* * Utility functions * ----------------- * This section contains several extremely short utility functions * that improve the readability of the code. */ /* * Functions: RectWidth, RectHeight * Usage: w = RectWidth(r); * h = RectHeight(r); * -------------------------------- * These functions return the width and height of a rectangle. */ static int RectWidth(Rect r) { return (r.right - r.left); } static int RectHeight(Rect r) { return (r.bottom - r.top); } /* * Functions: Radians, Degrees * Usage: radians = Radians(degrees); * degrees = Degrees(radians); * ---------------------------------- * These functions convert back and forth between degrees and radians. */ static double Radians(double degrees) { return (degrees * Pi / 180); } static double Degrees(double radians) { return (radians * 180 / Pi); } /* * Functions: InchesX, InchesY * Usage: inches = InchesX(pixels); * inches = InchesY(pixels); * -------------------------------- * These functions convert distances measured in pixels to inches. * Because the resolution may not be uniform in the horizontal and * vertical directions, the coordinates are treated separately. */ static double InchesX(int x) { return ((double) x / xResolution); } static double InchesY(int y) { return ((double) y / yResolution); } /* * Functions: PixelsX, PixelsY * Usage: pixels = PixelsX(inches); * pixels = PixelsY(inches); * -------------------------------- * These functions convert distances measured in inches to pixels. */ static int PixelsX(double x) { return (Round(x * xResolution + Epsilon)); } static int PixelsY(double y) { return (Round(y * yResolution + Epsilon)); } /* * Functions: ScaleX, ScaleY * Usage: pixels = ScaleX(inches); * pixels = ScaleY(inches); * -------------------------------- * These functions are like PixelsX and PixelsY but convert coordinates * rather than lengths. The difference is that y-coordinate values must * be inverted top to bottom to support the cartesian coordinates of * the graphics.h model. */ static int ScaleX(double x) { return (PixelsX(x)); } static int ScaleY(double y) { int height; WindowPeek window; window = gConsole->window; height = window->port.portRect.bottom - window->port.portRect.top; return (Round(height - PixelsY(y))); } static int Round(double x) { return ((int) floor(x + 0.5)); } /* * Functions: Min, Max * Usage: min = Min(x, y); * max = Max(x, y); * ----------------------- * These functions find the minimum and maximum of two integers. */ static int Min(int x, int y) { return ((x < y) ? x : y); } static int Max(int x, int y) { return ((x > y) ? x : y); } /* * Functions: MinF, MaxF * Usage: min = MinF(x, y); * max = MaxF(x, y); * ------------------------ * These functions find the minimum and maximum of two doubles. */ static double MinF(double x, double y) { return ((x < y) ? x : y); } static double MaxF(double x, double y) { return ((x > y) ? x : y); }