TextBoxer
Volume Number: | | 8
|
Issue Number: | | 3
|
Column Tag: | | Getting Started
|
Related Info: Quickdraw TextEdit Window Manager
TextBoxer
A vehicle for experimenting with QuickDraw's text and shape-drawing routines
By Dave Mark, MacTutor Regular Contributing Author
Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.
About the author
Dave Mark is an accomplished Macintosh author and an Apple Partner. He is the author of The Macintosh Programming Primer Series which includes: Macintosh C Programming Primer, Volumes 1 and 2; Macintosh Pascal Programming Primer, Volume 1, and his latest book, Learn C on the Macintosh. These books are available through the MacTutor Mail Order Store located in the back of the magazine. Dave is also the professor on the Learn Programming Forum on CompuServe. To get there, type GO MACDEV, then check out section 11.
In last months column, we covered the basics of programming using windows and QuickDraw. The Macintosh Toolbox was introduced, and a function was presented that properly initializes the Toolbox. This month, well dig a little deeper into the relationship between windows and QuickDraw.
TextBoxer: Still Life With Text and QuickDraw
This months application, TextBoxer, gives you a vehicle to experiment with QuickDraws text and shape-drawing routines. In its initial incarnation, TextBoxer creates the window shown in Figure 1. Notice that the content region of the window contains two rectangle (one inside the other), as well as a text string.
Figure 1. TextBoxer in action.
Creating the TextBoxer Project
Launch THINK C, creating a new project called TextBoxer.Π (The Π character is created by typing option-p). THINK C will create a project window with the title TextBoxer.Π. Select New from the File menu to create a new source code window. Type the following source code into the window:
/* 1*/
#define kVisible false
#define kMoveToFront (WindowPtr)-1L
#define kNoGoAwayfalse
#define kNilRefCon 0L
#define kPascalString\pAll applaud the strongly-jawed
#define kFontSize12
#define kBottomOffset7
#define kLeftOffset7
void ToolBoxInit( void );
WindowPtr WindowInit( void );
/****************** main ************/
main()
{
Rect shapeRect;
WindowPtrwindow;
ToolBoxInit();
window = WindowInit();
shapeRect = window->portRect;
InsetRect( &shapeRect, 5, 5 );
FrameRect( &shapeRect );
InsetRect( &shapeRect, 2, 2 );
FrameRect( &shapeRect );
TextFont( monaco );
TextSize( kFontSize );
MoveTo( shapeRect.left + kLeftOffset,
shapeRect.bottom - kBottomOffset );
DrawString( kPascalString );
while ( ! Button() ) ;
}
/****************** ToolBoxInit ***********/
void ToolBoxInit( void )
{
InitGraf( &thePort );
InitFonts();
InitWindows();
InitMenus();
TEInit();
InitDialogs( nil );
InitCursor();
}
/****************** WindowInit *************/
WindowPtr WindowInit( void )
{
WindowPtrwindow;
Rect windowRect;
SetRect( &windowRect, 20, 40, 260, 74 );
window = NewWindow( nil, &windowRect, \pBox o Text,
kVisible, documentProc, kMoveToFront,
kNoGoAway, kNilRefCon );
if ( window == nil )
{
SysBeep( 10 ); /* Couldnt create a window!!! */
ExitToShell();
}
ShowWindow( window );
SetPort( window );
return( window );
}
Select Save from the File menu and save the source code under the name TextBoxer.c. Next, select Add (not Add...) from the Source menu to add TextBoxer.c to the project. Finally, select Add... from the Source menu and add the MacTraps library to the project. Youll find MacTraps inside your Development folder, inside the THINK C Folder, inside the Mac Libraries folder. As mentioned last month, MacTraps contains the interfaces to the routines that make up the Macintosh Toolbox.
Once MacTraps has been added to the project, the project window should look like the one shown in Figure 2.
Figure 2. The TextBoxer project window, before compilation.
Running TextBoxer.Π
Select Run from the Project menu, asking THINK C to compile and run your project. If you run into any compile or link errors, check the code over carefully. Once your project runs, you should see something similar to Figure 3. To exit TextBoxer, just click the mouse button.
Figure 3. Running TextBoxer.
Walking Through the Source Code
TextBoxer.c consists of three routines, main(), ToolBoxInit(), and WindowInit(). As usual, we start off by defining some useful constants. Ill explain each of these as they occur in context.
/* 2 */
#define kVisible false
#define kMoveToFront (WindowPtr)-1L
#define kNoGoAwayfalse
#define kNilRefCon 0L
#define kPascalString\pAll applaud the strongly-jawed
#define kFontSize12
#define kBottomOffset7
#define kLeftOffset7
Next come the function prototypes. Be sure to prototype all your functions. This practice will go a long way towards catching compile errors.
/* 3 */
void ToolBoxInit( void );
WindowPtr WindowInit( void );
main() starts off with a couple of local variable declarations. shapeRect is declared to be of type Rect. Rect is a widely used Toolbox type and is defined in Inside Macintosh. A Rect has four fields: left, top, right, and bottom. Typically, youll fill a Rects fields so they define the position and size of a rectangle.
You can set the fields of a Rect individually, like this:
/* 4 */
Rect myRect;
myRect.left = 20;
myRect.top = 30;
myRect.right = 40;
myRect.bottom = 50;
or you can use a Toolbox routine like SetRect(). SetRect() is defined in IM(I:174):
/* 5 */
SetRect( Rect *myRect, int left, int top, int right, int bottom );
The second local variable in main() is window, used to store a pointer to the TextBoxer window.
/* 6 */
/****************** main ************/
main()
{
Rect shapeRect;
WindowPtrwindow;
Next, main() calls ToolBoxInit() to initialize the Macintosh Toolbox. As mentioned last month, with few exceptions, every Mac program you write will start off this way. Once the Toolbox is initialized, you can call the Toolbox as much as you like.
/* 7 */
ToolBoxInit();
Once the Toolbox is initialized, well call WindowInit() to create our window. WindowInit() returns a WindowPtr, an extremely important Toolbox data type. A WindowPtr is a pointer to a GrafPort, QuickDraws basic data structure:
/* 8 */
struct GrafPort {
short device;
BitMap portBits;
Rect portRect;
RgnHandle visRgn;
RgnHandle clipRgn;
Pattern bkPat;
Pattern fillPat;
Point pnLoc;
Point pnSize;
short pnMode;
Pattern pnPat;
short pnVis;
short txFont;
Style txFace;
char filler;
short txMode;
short txSize;
Fixed spExtra;
long fgColor;
long bkColor;
short colrBit;
short patStretch;
Handle picSave;
Handle rgnSave;
Handle polySave;
QDProcsPtr grafProcs;
};
typedef GrafPort *GrafPtr;
typedef GrafPtr WindowPtr;
Typically, youll create a GrafPort, customize it, draw in it, and eventually dispose of it. When you create a new window, a GrafPort is automatically created for you. For example, the Toolbox function NewWindow() creates a WindowRecord:
/* 9 */
struct WindowRecord {
GrafPort port;
short windowKind;
Boolean visible;
Boolean hilited;
Boolean goAwayFlag;
Boolean spareFlag;
RgnHandle strucRgn;
RgnHandle contRgn;
RgnHandle updateRgn;
Handle windowDefProc;
Handle dataHandle;
StringHandle titleHandle;
short titleWidth;
ControlHandle controlList;
struct WindowRecord *nextWindow;
PicHandle windowPic;
long refCon;
};
typedef struct WindowRecord WindowRecord;
typedef WindowRecord *WindowPeek;
Notice that the first field in the WindowRecord is a GrafPort. The WindowPtr returned by NewWindow() is actually a pointer to this GrafPort. You can cast the WindowPtr to the type WindowPeek when you want to access the rest of the fields in the WindowRecord.
Though this may seem confusing, its actually quite easy, once you get used to it. As we go over more and more examples, youll learn how to create and manage your own windows and GrafPorts. Lets get back to the TextBoxer code.
As we said earlier, WindowInit() creates a new WindowRecord (and, therefore, a GrafPort as well). The WindowPtr returned by WindowInit() is a pointer to the GrafPort embedded in the WindowRecord.
/* 10 */
window = WindowInit();
The windows portRect field is a Rect that defines the boundary of the window in screen pixels. On a typical Macintosh, the upper left corner of the screen corresponds to the (x,y) coordinate 0, 0. As you move to the right, the x coordinate increases. As you move down the screen, the y coordinate increases. The coordinates of the main screen serve as a reference point to all the GrafPorts on the screen and are known as global coordinates. Each GrafPort you create has its own coordinate system, known as local coordinates.
Figure 4 shows the point (0,0) in global coordinates. It also shows the point (0,0) in a windows local coordinate system. Typically, a windows local coordinates start in the upper left corner of the windows content region. The content region starts just below the windows title bar, if it has one.
The windows portRect field is copied into a local variable, shapeRect. Well inset the Rect (make it uniformally smaller) by 5 pixels in each direction, then call the drawing routine FrameRect() to draw a one pixel rectangle using this inset Rect as a guide. This rectangle will appear 5 pixels inside the border of the windows content region.
/* 11 */
shapeRect = window->portRect;
InsetRect( &shapeRect, 5, 5 );
FrameRect( &shapeRect );
Figure 4. The point (0,0) in both global and local coordinates.
Next, well inset the Rect 2 more pixels and frame another rectangle inside the first. InsetRect() is described in IM(I:175). FrameRect() is described in IM(I:176). Later in the column, well discuss some of the other functions that relate to drawing.
/* 12 */
InsetRect( &shapeRect, 2, 2 );
FrameRect( &shapeRect );
Once the rectangles are drawn, were ready to tackle the text. First, well set the current font to monaco, then well set the font size to kFontSize. Its important to note that weve only made this change to the current port. If we had two windows set up, we could set one window to use 18 point Geneva, and the other to 24 point Times. Youll see how to set the current port when we discuss WindowInit()
/* 13 */
TextFont( monaco );
TextSize( kFontSize );
Every GrafPort has a pen associated with it. The location of the pen determines where the next drawing operation will take place. Several of the QuickDraw drawing routines produce results based strictly on the location of this pen. One of these routines is DrawString(), a routine that draws a pascal string in the current port, at the current pen location. The function MoveTo() sets the pen location using the local coordinates of the current GrafPort.
/* 14 */
MoveTo( shapeRect.left + kLeftOffset, shapeRect.bottom - kBottomOffset
);
DrawString() draws the text using the current pen location as the left side of the baseline of the first character in the specified string. Remember, pascal strings start with a length byte, followed by that number of characters (up to 255). In THINK C, you can specify a pascal string by starting your string with the characters \p (look at the #define for kPascalString above).
/* 15 */
DrawString( kPascalString );
Finally, leave the window up there till the mouse button is clicked.
/* 16 */
while ( ! Button() ) ;
}
ToolBoxInit() is the same as it was in last months column.
/* 17 */
/****************** ToolBoxInit ***********/
void ToolBoxInit( void )
{
InitGraf( &thePort );
InitFonts();
InitWindows();
InitMenus();
TEInit();
InitDialogs( nil );
InitCursor();
}
WindowInit() will create a new window and return a pointer to it.
/* 18 */
/****************** WindowInit *************/
WindowPtr WindowInit( void )
{
WindowPtrwindow;
Rect windowRect;
For starters, SetRect() is used to set windowRect to the global coordinates wed like our window to appear at.
/* 19 */
SetRect( &windowRect, 20, 40, 260, 74 );
Next, NewWindow() is called to create the new window. Check out last months column for more info on NewWindow() and its parameters.
/* 20 */
window = NewWindow( nil, &windowRect, "\pBox o Text",
kVisible, documentProc, kMoveToFront,
kNoGoAway, kNilRefCon );
If the window could not be created, NewWindow() will return nil. In that case, we beep once and exit.
/* 21 */
if ( window == nil )
{
SysBeep( 10 ); /* Couldn't create a window!!! */
ExitToShell();
}
Since the constant kVisible (passed as a parameter to NewWindow()) was set to false, the window is not visible upon creation. Thats ShowWindow()s job! ShowWindow() makes the specified window visible and HideWindow() makes the window invisible.
Try commenting out the call to ShowWindow() to find out what an invisible window looks like.
/* 22 */
ShowWindow( window );
SetPort() makes the specified window the current port. Use SetPort() to switch between multiple GrafPorts.
/* 23 */
SetPort( window );
Finally, we return the pointer to our newly created window.
/* 24 */
return( window );
}
More Routines to Play With
QuickDraw allows you to do a lot more than frame rectangles. On one hand, you can use routines such as FrameOval(), FrameRoundRect(), and FrameArc() to frame each of the different QuickDraw shapes. On the other hand, for each shape you can perform paint, erase, invert, and fill operations in addition to the framing already discussed. For example, check out the QuickDraw routines PaintRect(), EraseRect(), InvertRect(), and FillRect().
These routines are completely described in Inside Macintosh, Volume I, Chapter 6. Be sure to check the parameters for each routine. Different shapes require different parameters.
I would strongly recommend that you read the aforementioned QuickDraw chapter from cover to cover. Play around with TextBoxer. Try different fonts and font sizes. Take a look at the file Quickdraw.h found in the THINK C Folder, in the Mac #includes folder, inside the Apple #includes folder. Youll find constants for the basic Mac fonts and a list of the different QuickDraw Toolbox routines.
Next Month...
In next months column, well poke around some of QuickDraws nooks and crannies. Well also learn about resources, the Macs mechanism for separating data from code. For those of you on baby-watch, there are only eight more weeks to go!!! Got any suggestions for the best type of car seat, crib, swing, diapers, bottles, etc. to buy? Let me know. I could sure use the help!
As always, you can reach me on CompuServe in MACDEV, Section 11 (Learn Programming).