Shapes in Quickdraw
Volume Number: | | 2
|
Issue Number: | | 12
|
Column Tag: | | ABC's of C
|
Drawing Shapes with Quickdraw
By Bob Gordon , Contributing Editor
One problem with writing software is that friends often want to know what it does. With most of these columns, frankly, there has not been much to show. This time, though, you can use the mouse to draw some simple shapes on the screen. Of course your friends will point out that MacDraw does everything we're going to do, and much better, too. But this one, we did ourselves, and that makes all the difference!
QuickDraw Shapes and Operations
QuickDraw can draw a number of shapes. This month we are going to cover only the simple shapes. (I define simple shapes as those that can be drawn directly without first defining what the shape is.) The simple shapes are:
Rectangles
Ovals (ellipses and circles)
Round-cornered rectangles
Arcs and wedges
To this list we could also add lines. The functions to draw these shapes are built into the Mac's ROM.
In drawing the shape, QuickDraw can do a number of different operations:
Frame draws a hollow outline
Paint fills the shape with the current grafPort's pen pattern and mode.
Erase fills the shape with the background pattern
Invert inverts all pixels within the shape: black becomes white and white becomes black
Fill fills the shapes with a specified pattern.
In general, these operations work identically for all the shapes. The shape is drawn, and the pen's location is not changed. Except for Fill, the operations require only the information needed to define the shape. Fill also needs to know the pattern to use. Since we have no easy way to select a pattern (we haven't done custom menus yet, so we haven't made a pattern selection menu), this operation will not be used.
The Toolbox supplies a separate function for each shape and operation. There are, for example, five functions to draw rectangles:FrameRect(), PaintRect(), EraseRect(), InvertRect(), and FillRect(). I wanted to select the shape and operation from menus and then draw with the mouse as with MacDraw.
Fig. 1 Our Drawing Program
Alternative Designs
One way to do this (assuming we had the menus built) would be to use a switch (case) statement based on the shape and operation:
switch (shape)
case rect:
switch (operation)
case Frame:
case Paint:
case Erase:
case Invert:
case Fill:
case oval:
switch (operation)
et cetera
This is a sort of brute force method. It would work, but it involves several pages of code to make a couple of simple selections.
Another approach is to take advantage of C's ability to pass and use pointers to functions. To do this we obtain the addresses of the functions and arrange them in a double subscripted array. The problem of selection reduces to a problem of indexing into the array.
Pointers to Functions
Just as we can get the address of a variable and then assign a pointer to it, we can get the address of a function, assign a pointer to it and use the pointer to access the function. Kernighan and Ritchie discuss this on page 114.
There are several step in using a pointer to a function. First, to get the address of a function, just use the name of the function without the parentheses. If fr_rect() is a function, fr_rect will be the address of the of the function. To use a function in this manner we must create a variable that is a pointer to a function. The following line does this:
short (*drfunc)();
This rather strange looking line declares drfunc to be a pointer to a function that returns a short. Note that the parentheses around *afunc are required. Without them we would have:
short *drfunc();
a function that returns a pointer to a short. I could not use this declaration directly in the program but had to use a typedef:
typedef short (*drfunc)();
Once this type is established, we can define some variables. In the program there is a variable, draw.
drfunc draw;
For the moment assume that draw points to a function (it has been assigned a value). To use draw we do the following:
(*draw)(par1,par2);
Again, the parentheses are needed to indicate that draw is a pointer to a function and we want to execute the function it points to. (Without the parentheses draw would be a function that returns a pointer). It is important to pass the proper number of variables. Since the call to the function is made through a pointer, compilers can do very little checking.
The Drawing Functions
I had originally hoped to avoid writing a bunch of drawing functions, but this plan changed. First, Lightspeed C does not allow us to take the address of Toolbox functions. They even have a special error message. This probably has to do with how they access the Toolbox and make the translation from C to Pascal calling conventions. This meant I would need at least a surround function for each Toolbox routine I wished to use.
Secondly, I wanted to draw lines in the same way as the other shapes. Since the Toolbox does not handle lines as shapes, I would have to do it myself. This was complicated by the fact that the initial versions of these functions expected to receive rectangles. Rectangles are used to define all the shapes. With lines, though, it meant taking apart the rectangle, figuring out which corner was the beginning, and keeping track of it. Needless to say, the line functions got very strange looking. To simplify the line functions, all the functions now receive two points. This had the side benefit of simplifying the oval functions, but it required that the surround routines create a rectangle before calling the Toolbox function.
The other requirement for accessing the function via a pointer is that all the functions must receive the same number of parameters. In addition to a rectangle, the round rectangle functions get two parameters describing the oval width and height used in drawing the rounded corner. These are fixed at 20. (Do try varying these. I was able to make some very ugly rounded rectangles). The arcs receive a start angle and the number of degress of angle to move (arcAngle in Toolbox terms). I fixed the arc angle at 90°. The start angle and the sign of the arc angle are adjusted as necessary to make the arc move correctly. These arcs were modeled after the ones in MacDraw.
The result of all this is a single function, drdraw(), that draws all the simple shapes under mouse control!
Speaking of the Mouse
Another addition to the program this month is the code that changes the cursor. While it is not yet complete (it doesn't know about the window borders where the controls would go), it does change the cursor as the mouse moves around. AdjustCursor() receives a pointer to a window record as a parameter and sets the cursor to the crosshairs (cursor number two) if the mouse is in the window and to the arrow if the mouse is not. The arrow cursor itself is not one of the four standard cursors but is defined in Quickdraw.h. AdjustCursor() is called once each time through the event loop.
There are two other points that deserve some mention. One is that several sets of coordinates can be active at once. GetMouse(), for example, returns the mouse's position relative to the current or active window. The window's boundaries, however, are defined in terms of the screen or global coordinates. The functions LocalToGlobal() and GlobalToLocal() will switch a point's coordinate system. Without the switch, the cursor changes magically out in the middle of the screen.
Next Time
We only did the simple shapes this time. I decided to wait on the others pending a discussion of the memory manager. Many of the functions we have used so far make use of the memory manager, and to do some things, we must be able to grab bits of memory. After that (I expect the program to be very brief - just long enough to demonstrate the operation) we'll return to Quickdraw and draw some more shapes.
The Program
/* abc.h
*
* Local definitions to improve readability
*
*/*
#defineTrue 1
#define False 0
#define Nil 0
#define and &&
#define or||
#define not !
#define equals ==
#define notequal !=
/* unsigned char,longs, shorts
* (unsigned longs may not be
* available with all compilers
*/
#define uchar unsigned char
#define ushort unsigned short
#define ulong unsigned long
/* General purpose external routines */
extern char*CtoPstr(); /* String conversion routines */
extern char*PtoCstr(); /* return a pointer to a char */
/* Quickdraw Drawing Program
* Shows off basic quickdraw shapes
* and operations.
* By Bob Gordon for MacTutor.
* Compiled with LightspeedC
*
* Important note for Mac C users:
* Every place you see event->where,
* replace it with &event->where
*/
#include "abc.h"
#include "Quickdraw.h"
#include "EventMgr.h" /* Events.h */
#include "WindowMgr.h" /* Window.h */
#include "MenuMgr.h"/* Menu.h */
/* defines for menu ID's */
#defineMdesk 100
#defineMfile 101
#defineMedit 102
#defineMshape 103
#defineMop 104
/* File */
#defineiNew1
#defineiClose 2
#defineiQuit 3
/* Edit */
#defineiUndo 1
#defineiCut3
#defineiCopy 4
#defineiPaste 5
/* Global variables */
MenuHandle menuDesk;/* menu handles */
MenuHandle menuFile;
MenuHandle menuEdit;
MenuHandle menuShape;
MenuHandle menuOp;
WindowPtrtheWindow;
WindowRecord windowRec;
Rect dragbound;
Rect limitRect;
main()
{
initsys(); /* system initialization */
initapp(); /* application initialization */
eventloop();
}
/* system initialization */
initsys()
{
InitGraf(&thePort); /* these two lines done */
InitFonts(); /* automatically by Mac C */
InitWindows();
InitCursor();
InitMenus();
theWindow = Nil;/*indicates no window */
SetRect(&dragbound,0,0,512,250);
SetRect(&limitRect,60,40,508,244);
}
/* application initialization */
initapp()
{
setupmenu();
drinit();
}
setupmenu()
{
menuDesk = NewMenu(Mdesk,CtoPstr("\24"));
AddResMenu (menuDesk, 'DRVR');
InsertMenu (menuDesk, 0);
menuFile = NewMenu(Mfile, CtoPstr("File"));
AppendMenu (menuFile,CtoPstr("New/N;Close;Quit/Q"));
InsertMenu (menuFile, 0);
menuEdit = NewMenu(Medit, CtoPstr("Edit"));
AppendMenu (menuEdit,CtoPstr("(Undo/Z;(-;(Cut/X;( Copy/C;( Paste/V;(Clear"));
InsertMenu (menuEdit, 0);
menuShape = NewMenu(Mshape, CtoPstr("Shape"));
AppendMenu (menuShape,CtoPstr("Line;Rectangle; Oval;Round Rectangle;Arc"));
InsertMenu (menuShape, 0);
menuOp = NewMenu(Mop, CtoPstr("Operation"));
AppendMenu (menuOp,CtoPstr("Frame;Paint; Erase;Invert;"));
InsertMenu (menuOp, 0);
DrawMenuBar();
}
/* Event Loop */
eventloop()
{
EventRecordtheEvent;
char c;
short windowcode;
WindowPtrww;
while(True)
{
if (theWindow)
{
EnableItem(menuFile,2);
DisableItem(menuFile,1);
AdjustCursor(theWindow);
}
else
{
EnableItem(menuFile,1);
DisableItem(menuFile,2);
}
if (GetNextEvent(everyEvent,&theEvent))
switch(theEvent.what)
{
case keyDown:
c = theEvent.message & charCodeMask;
if (theEvent.modifiers & cmdKey)
domenu(MenuKey(c));
else if (theWindow)
break;
case mouseDown:
domousedown(&theEvent);
break;
default:
break;
}
}
}
/* Arrow or Plus Cursor shape */
AdjustCursor(w)
WindowRecord *w;
{
Point pt;
CursHandle curs;
GetMouse(&pt);
LocalToGlobal(&pt);
if (PtInRgn(pt,w->contRgn))
{
curs = (Cursor **)GetCursor(2);
SetCursor(*curs);
}
else
{
SetCursor(&arrow);
}
}
/* domousedown
* handle mouse down events
*/
domousedown(er)
EventRecord*er;
{
short windowcode;
WindowPtrwhichWindow;
short ingo;
long size;
long newsize;
RgnPtr rp;
Rect box;
Rect *boxp;
windowcode = FindWindow(er->where,
&whichWindow);
switch (windowcode)
{
case inDesk:
if (theWindow notequal 0)
{
HiliteWindow(theWindow, False);
DrawGrowIcon(theWindow);
}
break;
case inMenuBar:
domenu(MenuSelect(er->where));
break;
case inSysWindow:
SysBeep(1);
break;
case inContent:
if (whichWindow equals theWindow)
{
HiliteWindow(whichWindow,True);
DrawGrowIcon(whichWindow);
drdraw(whichWindow);
}
break;
case inDrag:
DragWindow(whichWindow,
er->where, &dragbound);
DrawGrowIcon(whichWindow);
break;
case inGrow:
break;
case inGoAway:
ingo = TrackGoAway(whichWindow,er->where);
if (ingo)
{
CloseWindow(whichWindow);
theWindow = Nil;
}
break;
}
}
/* domenu
* handles menu activity
* simply a dispatcher for each
* menu.
*/
domenu(mc)
long mc; /* menu result */
{
short menuId;
short menuitem;
menuId = HiWord(mc);
menuitem = LoWord(mc);
switch (menuId)
{
case Mdesk : break;
/* not handling DA's */
case Mfile : dofile(menuitem);
break;
case Medit : /* all disabled */
break;
case Mshape: doshape(menuitem);
break;
case Mop : dooper(menuitem);
break;
}
HiliteMenu(0);
}
doshape(item)
short item;
{
static short lastitem;
CheckItem (menuShape,lastitem,False);
CheckItem (menuShape,item,True);
lastitem = item;
drshape(item);
}
dooper(item)
short item;
{
static short lastitem;
CheckItem (menuOp, lastitem,False);
CheckItem (menuOp, item, True);
if (item == 5)
{
item = 0;
SysBeep(1);
}
droper(lastitem = item);
}
dofile(item)
short item;
{
char *title1; /*title for window */
Rect boundsRect;
switch (item)
{
case iNew :/* open the window */
title1 = "ABC Window";
SetRect(&boundsRect,50,50,400,200);
theWindow = NewWindow(&windowRec, &boundsRect,CtoPstr(title1), True,
documentProc,
(WindowPtr) -1, True, 0);
DrawGrowIcon(theWindow);
PtoCstr(title1);
DisableItem(menuFile,1);
EnableItem(menuFile,2);
break;
case iClose : /* close the window */
CloseWindow(theWindow);
theWindow = Nil;
DisableItem(menuFile,2);
EnableItem(menuFile,1);
break;
case iQuit : /* Quit */
ExitToShell();
break;
}
}
/*
* dr.c
* drawing routines
*/
#include "abc.h"
#include "quickdraw.h"
#include "windowMgr.h"
struct shapes
{
short kind;
Rect size;
short oper;
};
struct shapes shapa[20];
short shapdx;
fr_line(startpt,endpt)
Point startpt,endpt;
{
MoveTo(startpt.h,startpt.v);
LineTo(endpt.h,endpt.v);
}
fr_rect(startpt,endpt)
Point startpt,endpt;
{
Rect rt;
Pt2Rect(startpt,endpt,&rt);
FrameRect(&rt);
}
fr_oval(startpt,endpt)
Point startpt,endpt;
{
Rect rt;
Pt2Rect(startpt,endpt,&rt);
FrameOval(&rt);
}
fr_rort(startpt,endpt)
Point startpt,endpt;
{
Rect rt;
Pt2Rect(startpt,endpt,&rt);
FrameRoundRect(&rt,20,20);
}
fr_arc(startpt,endpt)
Point startpt,endpt;
{
Rect rt;
Rect trt;
short sa;
short aa;
Pt2Rect(startpt,endpt,&rt);
cp_arc(&rt,&trt,&sa,&aa);
FrameArc (&trt,sa,aa);
}
cp_arc(irt,ort,startangle,arcangle)
Rect *irt;
Rect *ort;
short *startangle;
short *arcangle;
{
short dh;
short dv;
static Point anchor;
dh = irt->right - irt->left;
dv = irt->bottom - irt->top;
if (not (dh | dv))
{
anchor.v = irt->top;
anchor.h = irt->left;
}
*ort = *irt;
if (irt->left equals anchor.h)
if (irt->top < anchor.v)
{
ort->left -= dh;
ort->top -= dv;
*startangle = 180;
*arcangle = -90;
}
else
{
ort->left -= dh;
ort->bottom += dv;
*startangle = 0;
*arcangle = 90;
}
else
if (irt->top < anchor.v)
{
ort->top -= dv;
ort->right += dh;
*startangle = 180;
*arcangle = 90;
}
else
{
ort->right += dh;
ort->bottom += dv;
*startangle = 0;
*arcangle = - 90;
}
}
er_line(startpt,endpt)
Point startpt,endpt;
{
GrafPtrgp;
Patterntpat;
GetPort(&gp);
BlockMove(gp->pnPat,&tpat,8);
PenPat(gp->bkPat);
MoveTo(startpt.h,startpt.v);
LineTo(endpt.h,endpt.v);
PenPat(&tpat);
}
er_rect(startpt,endpt)
Point startpt,endpt;
{
Rect rt;
Pt2Rect(startpt,endpt,&rt);
EraseRect(&rt);
}
er_oval(startpt,endpt)
Point startpt,endpt;
{
Rect rt;
Pt2Rect(startpt,endpt,&rt);
EraseOval(&rt);
}
er_rort(startpt,endpt)
Point startpt,endpt;
{
Rect rt;
Pt2Rect(startpt,endpt,&rt);
EraseRoundRect(&rt,20,20);
}
er_arc(startpt,endpt)
Point startpt,endpt;
{
Rect rt;
Rect trt;
short sa;
short aa;
Pt2Rect(startpt,endpt,&rt);
cp_arc(&rt,&trt,&sa,&aa);
EraseArc (&trt,sa,aa);
}
pt_line(startpt,endpt)
Point startpt,endpt;
{
GrafPtrgp;
Patterntpat;
MoveTo(startpt.h,startpt.v);
LineTo(endpt.h,endpt.v);
}
pt_rect(startpt,endpt)
Point startpt,endpt;
{
Rect rt;
Pt2Rect(startpt,endpt,&rt);
PaintRect(&rt);
}
pt_oval(startpt,endpt)
Point startpt,endpt;
{
Rect rt;
Pt2Rect(startpt,endpt,&rt);
PaintOval(&rt);
}
pt_rort(startpt,endpt)
Point startpt,endpt;
{
Rect rt;
Pt2Rect(startpt,endpt,&rt);
PaintRoundRect(&rt,20,20);
}
pt_arc(startpt,endpt)
Point startpt,endpt;
{
Rect rt;
Rect trt;
short sa;
short aa;
Pt2Rect(startpt,endpt,&rt);
cp_arc(&rt,&trt,&sa,&aa);
PaintArc (&trt,sa,aa);
}
in_line(startpt,endpt)
Point startpt,endpt;
{
GrafPtrgp;
short tpnMode;
GetPort(&gp);
tpnMode = gp->pnMode;
PenMode(patXor);
MoveTo(startpt.h,startpt.v);
LineTo(endpt.h,endpt.v);
PenMode(tpnMode);
}
in_rect(startpt,endpt)
Point startpt,endpt;
{
Rect rt;
Pt2Rect(startpt,endpt,&rt);
InvertRect(&rt);
}
in_oval(startpt,endpt)
Point startpt,endpt;
{
Rect rt;
Pt2Rect(startpt,endpt,&rt);
InvertOval(&rt);
}
in_rort(startpt,endpt)
Point startpt,endpt;
{
Rect rt;
Pt2Rect(startpt,endpt,&rt);
InvertRoundRect(&rt,20,20);
}
in_arc(startpt,endpt)
Point startpt,endpt;
{
Rect rt;
Rect trt;
short sa;
short aa;
Pt2Rect(startpt,endpt,&rt);
cp_arc(&rt,&trt,&sa,&aa);
InvertArc (&trt,sa,aa);
}
typedef short (*drfunc)();
drfunc a[][5] = {fr_line,fr_rect,fr_oval,fr_rort,fr_arc,
pt_line,pt_rect,pt_oval,pt_rort,pt_arc,
er_line, er_rect,er_oval, er_rort,er_arc,
in_line,in_rect,in_oval, in_rort,in_arc};
drinit()
{
short i;
for (i = 0; i < 20; shapa[i++].kind = 0)
;
shapdx = 0;
}
drshape(code)
short code;
{
shapa[shapdx].kind = code;
}
droper(code)
short code;
{
shapa[shapdx].oper = code;
}
drsize(r)
Rect *r;
{
shapa[shapdx].size = *r;
}
drdraw(w)
WindowRecord *w;
{
Point startpt;
Point thispt;
Point endpt;
Point lastpt;
Rect thisrt;
Rect lastrt;
GrafPtrport;
drfunc frame;
drfunc draw;
short angle;
short dv,dh;
Point sp;
Point tp;
Point lp;
short shapx;
short operx;
SetPort((GrafPtr)w);
GetMouse(&startpt);
lastpt = startpt;
PenMode(patXor);
PenPat(gray);
shapx = shapa[shapdx].kind - 1;
operx = shapa[shapdx].oper - 1;
if ((shapx < 0) or (operx < 0)) /* to prevent trying */
return;/* to use unselected items */
frame = a[0][shapx];/* get address of frame func */
draw = a[operx][shapx]; /* get addr of shape/oper func */
do{
GetMouse(&endpt);
thispt = endpt;
LocalToGlobal(&endpt);
if (PtInRgn(endpt,w->contRgn) and
not EqualPt(thispt,lastpt))
{
(*frame)(startpt,lastpt);
(*frame)(startpt,thispt);
lastpt = thispt;
}
}
while (StillDown());
(*frame)(startpt,thispt);
PenMode(patCopy);
PenPat(black);
(*draw)(startpt,thispt);
}
abs(num)
short num;
{
if (num < 0 )
return -num;
return num;
/* num < 0 ? return -num : return num ;*/
}