[N.A.B.G. picture]

Symantec Think Class Library Tutorial


1 Dice

1.1

A First Program

The first example program is simple. It is a "Dungeons and Dragons" (DD) dice roller.

DD games involve many situations where the results of game actions are determined by a roll of dice. DD dice are not restricted to being standard six sided dice. A game may specify things like "Roll two 4-sided dice to determine how much damage the player has suffered". At different stages in the game, you may need "two-sided" dice (i.e. a toss of a coin), or "four-sided" dice, or 6-, or 8-, or 10-, or 12-, or 20-, or even 100-sided dice.

Such games are difficult to play if you don't have the right dice available. Of course, a program that uses a random number generator can act as a substitute. If you search the archives of public domain Macintosh programs you can find DD-dice programs and "desk accessories". Here, you are going to build another.

It doesn't compare well with the more fancy programs in the archives. It has no pretty graphics, no colour, no animations, no sounds of dice rolling. But it will allow the user to select the right kind of dice and then "roll" a single dice of that type showing the resulting score. If the game players need to roll several dice, they have to "roll" several times and add the individual scores themselves.

The program is to display a window with "radio button" and "action button" controls; something like that illustrated in Figure 1.1. The radio buttons are used to select the type of dice for the next roll. The "Roll" action button results in the display of a random number scaled to the range appropriate for a particular kind of dice. Apart from picking and rolling dice, the only thing the program can do is obey the File/Quit menu option.

It is a simple program. But it is enough to introduce the use of some of the tools that will be used later. The windowing interface has to be built (using Visual Architect) and the program specific code (for picking and scaling random numbers) has to be integrated into the automatically generated code.
[SYM1.1]

Figure 1.1 The dice roller.

1.2 The New Project

Launch the Symantec Project Manager.

The normal "preferences" set for the Project Manager will result in the program immediately displaying a dialog asking you to identify a project. If this dialog does not appear automatically, select the File/New Project ... option.

Greedy for disk space

Select a disk with at least 10 Megabytes of free space! Create a new folder for your project, e.g. "dice1"; name the project, e.g. "Dice", and select its model. You should pick "VA Application", as shown in Figure 1.2.

You can pick the alternative "VA App w/Shared TCL". If you use a shared copy of the TCL library, you will save some disk space and, sometimes, some compilation time. However, you won't be able to "browse" the library. Since browsing the class library is very helpful, it is better to use "VA Application" as your project model.
[SYM1.2]

Figure 1.2 Starting a new project.

The project manager will create some folders within your main project folder and copy in a few standard files. When this process is complete, you will see a display something like that shown in Figure 1.3.
[SYM1.3]

Figure 1.3 Initial project display.

This display shows that the project has two "groups" of standard code (the standard Runtime Libraries and the THINK Class Library). There are also two "resource files".

Resource files

Resource files are used to hold predefined, pre-initialized data structures used by your program. You will use the file Project Resources.rsrc to store resources such as icons, pictures, and sounds. You will normally use the ResEdit program to edit this file. Some resources, such as icons, can be created directly in ResEdit; most are created in other applications and transferred via the clipboard, or the scrapbook.

The second resource file, Visual Architect.rsrc, holds data structures that define the windows and views used by your program. These resources are built using Symantec's Visual Architect program.

If you switch to Finder, you will see the two resource files, a project file, and a directory "Source" that will contain the source code for your project. At this stage, it will contain just a single file ("PLACEHOLDER.cpp"). This file is really a hang over from the Symantec 7 system, you never use it.

1.3 A First encounter with the Architect

Double click on the Visual Architect.rsrc entry from the project display. The Symantec system will launch the Visual Architect program.

Visual Architect will open a window (named Visual Architect.rsrc after the file) that it uses to list all "View" resources in the file. The file starts with an automatically generated "View" called Main. Double click on the "Main" entry to open another editing window.

1.3.1 Editing the "Main" window

The automatically generated "Main" view contains a coloured picture with circles arranged like the petals of a flower along with the novel greeting "hello, world!". Also shown in faint outline are suggestions of "scroll bars" and a size box. This default view is displayed in a real window with real scroll bars and a real size box.

You don't want those flower petals or the greeting so delete them (mouse click on item, then delete key).

Changing the window type

Most programs use resizable windows, so this is the default used. A fixed size window seems more suitable for the Dice program. You can change the form of the window that will get generated by using the View/View Info ... menu option. When you pick this menu option, a fairly complex looking dialog will appear. This will show a variety of forms for windows, most should be familiar as you will have seen them in other Macintosh programs that you have used. The "fixed size window with close box" (the second shown) is suitable; select this by clicking with the mouse. While you have this dialog displayed, un-check the two check boxes "Use file" and "Print"; the Dice program doesn't require files or printing. The dialog has numeric entry fields that can be used to change the size of the window. The defaults should be satisfactory though you can change the size if you wish.

1.3.2 Adding "panes" such as "Radio Buttons" and "Static Texts"

When you have finished with the View/View Info... dialog, "tear off" the palette of tools that appear in the Tools entry in the Visual Architect's menu bar. You will use these tools to add the radio buttons and so forth to the fixed sized window that you now have for "Main".

A "rectangle" button for visual adornment

First, use the empty rectangle tool to add a rectangle to the Main view (see Figure 1.4). This rectangle is actually a strange kind of action button; but in this program it will have no associated actions. It exists simply to provide a visual indication that the radio buttons form a group.
[SYM1.4]

Figure 1.4 Using the tools in Visual Architect.

Radio buttons to represent the possible choices for the dice

Next add the first radio buttons (the "radio button tool" should be obvious). The Visual Architect program waits for you to type a label, e.g. "Two".
Naming "panes"

Every element (Symantec calls most of them "panes") that you add to a view has an associated name. Default names are automatically generated by the Visual Architect. This first radio button will be named something like Rdio4. These names are related to the names of variables in automatically generated code. You will have to use these variables when you want to access controls from your own code. The default names are not meaningful. You will normally want to change them.

A pane's name can be changed using the Pane/Info ... dialog. Change the name associated with this radio button to BTwo, see Figure 1.5.
[SYM1.5]

Figure 1.5 Renaming a pane.

The Pane/Info dialog actually tells you how a particular class of pane, like a radio button pane, fits into the class hierarchy. Its class is CRadioControl, which is a specialization of CButton. Class CButton specializes CControl and so forth. You can click on the arrow markers by the classes to open up subparts of the dialog and edit properties inherited from other classes. No edits are needed for this first button.

Adding the other radio buttons

The simplest way to add the other buttons is to select the first, and use Edit/Duplicate. You position the duplicate below the first, then duplicate again and again until you have sufficient buttons. This gets them evenly spaced vertically.

Next, select all the radio buttons (standard Macintosh "shift-click" multiple selection) and use the Pane/Align/Left menu option to get them aligned.

The new buttons must each be edited in turn (Pane/Info dialog applied to each). You need to rename them (BFour, BSix, etc) and you also have to change the text strings that they display. The text is one of the data members defined by class CControl. Open the CControl section of the dialog and change contrlTitle value. When editing the entry for button "Six", also change the contrlValue field to 1 (this will make this button be set initially).
[SYM1.6

Figure 1.6 Setting properties of a pane.

Radio button "cluster"

You will know from your experience with other Macintosh programs that a set or cluster of "radio buttons" is supposed to have either exactly one button switched on (or, sometimes, at most one button switched on). If a different button is clicked, it should become switched on while any previously selected button should be switched off. In a complex dialog, there can be different clusters of buttons (e.g. a "File/Print dialog typically has a cluster with "All pages" and "Range" buttons, and another Destination cluster with "File" and "Printer" buttons).

Visual Architect's menu Options/Show Button Groups can be used to get each button tagged in the display by a number identifying its group. If you use this menu option, you should see that all your radio buttons are in the same group, group 1. This is what is required in this case.

If there are supposed to be different groups, you must identify the group to which each button belongs. The rectangle that you added first has nothing to do with the actual definition of button groups; it is simply a visual marker. If you do need different groups, select the buttons that form a group (usual Macintosh style shift-click multiple selection) and use the menu option Pane/Set Button Group. You can play with the button groupings now, but make certain you leave them all in the same group before continuing.

A static text field to display the score

Dialogs and other windows can contain two kinds of text pane. "EditText" panes are for data entry; static text panes are either for constant strings (e.g. dialog labels such as "Identifier") or for program controlled output. The Dice program requires a pane to display a score.
Text tools in the palette

The Tools palette has tools for adding both kinds of text pane. The large letter A identifies the "static text" tool; the small A at the left end of a rectangle represents the "edit text" tool.

Use the static text tool to add a static text pane to the window. When the pane is added, the Visual Architect program waits for you to enter its initial text (in this case, 0). Next, use Pane/Info to change the pane's attributes. As usual, it should be given a more informative name. Since it will be used to display numeric data, it is best if characters are right justified; justification is one of the data members of the parent class CEditText. (The class hierarchy is a little odd; a static text is an edit text that is not editable!) Figure 1.7 illustrates the Pane/Info dialog being used to change the parameters.
[SYM1.7]

Figure 1.7 Changing the properties of a static text pane.

The "Roll" button

The final interface button that must be added is the "Roll" button used to get the dice to roll. Use the "action button" ("push button") tool to place the button and then adjust some of its properties with the Pane/Info dialog. The first button has, by default, the label string "OK" (the second button has "Cancel", others are unlabelled).
[SYM1.8]

Figure 1.8 Naming the "roll" action button.

There is a bit more to be done than just change the label and the control's name (as shown in Figure 1.8). Buttons are after all intended to be mechanisms whereby a user can "send a message to an object". Such user initiated messages are identified by integer command numbers. The program developer (i.e. you) has to pick a unique number and identify the "object" that is to handle the command.

Command handling objects

Programs built using frameworks like TCL contain a number of "command handling" objects. One is the "application" object. There is only one of these. In a typical program it is responsible for doing things like creating different "document" objects. "Document" objects are things that own program specific data (often indirectly through intermediaries like lists) and do things like display the windows and organize transfer of data between memory and disk files.

Roll command handled by the "document"

In the Dice program, a user's roll request can be handled by the "document" object. Actually, this object is an instance of a specialized subclass, class CMain, that is derived from the CDocument class of the TCL framework.

Changing the command number

A command number can be assigned, and details of the handling class can be provided, using another dialog. Open the "CButton" part of the Pane/Info dialog. It will show the "command number" associated with the button. By default this will correspond to the named constant cmdOK. This has to be changed. Clicking on the command number field will result in the appearance of a pop-up list with the names of all the standard commands used in the TCL framework itself. The very top entry is "Other ...". Pick this. The "commands" dialog, see Figure 1.9, will be displayed.
[SYM1.9]

Figure 1.9 The "Commands" dialog.

Naming a command

You actually invent a command name, the Visual Architect assigns the number. The name can be entered in the edit text field of the "commands" dialog (start by typing a 'return' character, if you simply type some letters the program processes the action as an attempt to rename an existing command). By convention, command names start with the letter 'c'. The name cROLL seems appropriate; the Visual Architect has allocated this new command the number 512. (When it generates the code describing all this, it will output something like #define cROLL 512.)

Identifying the command handler

The "Actions" part of the commands dialog lets you identify the type of object that is to handle this command. The "In Class" pop-up menu lists possible command handler classes. Only two possibilities will be offered, CApp (the application), and CMain (the document). (Again there are naming conventions, all TCL classes have names starting 'C'.) Pick CMain.

Defining the handling mechanism

The "Do" pop-up menu will now offer some choices as to what should be done by a CMain object that receives a cROLL command. We require the system to call a function where we can put code to do things like pick the random number. For this we need to select "Call" from among the "Do" options.

When it gets to generate some initial code for our project, the Visual Architect is going to arrange that the CMain class has a function called DoCROLL() and that there will be a call to this function inserted elsewhere in the code. Of course, the Visual Architect can not guess what we want to do, so the function it generates will be empty. We will get to add the required code later.

Trying out the completed window

Now that all the panes have been added, the window can be "tried out" (View/Try Out menu option). The radio buttons should switch on and off, and the action button flashes when clicked. But, of course, nothing much else happens.

When you have finished "trying out" the window, close first it and then the editing window "Main".

1.3.3 Adjusting menus and other properties

It is usually appropriate to now make some changes to the menus that will get used by the program. Sometimes it is worth adding other information such as initial definitions for some program specific classes.
Cutting out some of the menus

The Dice program requires merely some simplification of the menus. If you use the Edit/Menu Bar option you will see that the Dice program is to be given three menus - the standard Apple, File, and Edit menus. This is OK. But we should simplify the File menu because all we will need is File/Quit.

Use the Edit/Menus... option to get a list of all the possible menus, select the File menu and then use the Edit Menu Items button. You will be shown a dialog where the options initially in the File menu are listed. Delete all the entries like New, Save As and so forth. Figure 1.10 shows the menu editing dialog part way through the process of removing unwanted menu options.

You can also do things like use the Edit/Application ... dialog (this lets you change the copyright notice that will be put into your program files). But nothing else is needed in the relatively simple Dice example.

Once you have completed editing the menus and making any other changes, you should use File/Save to get the Visual Architect program to save the resource file.
[SYM1.10]

Figure 1.10 Simplify the menus.

1.3.4 Generating skeleton code

The Visual Architect program can now generate the basic skeleton for the code of the Dice program.

Programs built with a class library do of course make use of the same concrete classes. Thus, they all use instances of classes like CButton, CScrollBar and so on. But there are stronger similarities. The different programs built with a class library like TCL all have the same basic structure.

Structure of a TCL program

The main program is a good OO style main(); it creates the principal object and tells it to "run". The principal object is always an instance of slightly specialized subclass (class CApp) of the "Application" class provided in the framework. The main duty of the CApp object is handling menu requests like File/New, File/Open, File/Quit, and Apple/About .... The File/New and File/Open commands result in creation of instances of the CMain class. Class CMain is a specialization of the framework's CDocument.

Most of the code for defining the standard interactions of a CApplication object with a CDocument object is already written and stored in the library files. But there are little bits that do vary from program to program.

For example, in the Dice program class CMain should have a function DoCROLL() that will eventually have the code dealing with random numbers. Class CMain should also have some extra data members. The code that picks a random score will need to check which radio button is selected. Consequently, the CMain class better have some CRadioControl* pointer data members that can hold links to these radio buttons and have a function that sets these pointers using information about the display structure that we just built using the Visual Architect.

Automatic code generation

Although code to set a pointer to link to a specific radio button is program specific, it has a very standard form. The Visual Architect program can fill in such code. This is what happens during the code generation step.

Generate All

You should now pick the Generate All option from the menus. (You use "Generate All" when first starting a project. Later, if you make minor changes like adding an extra dialog or changing a menu, you use the "Generate" option. "Generate" replaces any existing files that have to be changed to deal with the update).

The "Generate All" request will take the Visual Architect a minute or two to complete. A dialog is displayed that shows the names of the files that are being created.

When the generation process is complete, use File/Quit to terminate this session with the Visual Architect and return to the main Symantec Project Manager program.

1.4 Code

1.4.1 The Generated Code files

The files generated by the Visual Architect are:
AppCommands.h
CApp.cp
CApp.h
CMain.cp
CMain.h
main.cp
MainItems.h
References.cp
References.h
x_CApp.cp
x_CApp.h
x_CMain.cp
x_CMain.h
You will be making quite a few extensions to information in the CMain files. Some of the other files you may never need to look at.

AppCommands.h

This file contains #defines for all program specific commands. If you look at this AppCommands.h file you will see the #define for cROLL.

MainItems.h

This file defines an enumerated type. Its members are names for identifiers that get associated with each of the visual components in the display structure just built with the Visual Architect.

References.h and References.cp

These files provide information needed in the final linking process when the executable program is created.

The linker tries to limit the size of executables. It leaves out code for any library classes that are not used.

However, the linker can get fooled. It may get the idea that a program makes no use of instances of some class like CCheckBox and so may try to omit this code. If a CCheckBox is in fact used inside one of the dialogs, the omission of the necessary code from the linked executable will cause problems when that dialog is used.

The References.h header file simply declares a single function, ReferenceStdClasses(). This function is defined in References.cp. If you look at its code, you will see that it consists simply of a series of "macro calls" to the TCL_FORCE_RERERENCE macro. The macro takes the name of the class. Its expansion adds some special code that makes sure that the linker will include the code for that class.

main.cp

This file contains the main program:
void main()
{
	SetMinimumStack(32768);

	CApp	*application = TCL_NEW(CApp,());

	application->ICApp();
	application->Run();
	application->Exit();
}
As already explained, it is basically the standard OO style main(): create the principal object and tell it to "run". Apart from the call to Run(), there are calls to extra initialization and termination routes.

The SetMinimumStack() is a global function in the run-time library. As its name suggests, it informs the Macintosh OS about stack requirements of this program.

You were probably expecting the application object to be created using a statement like:

	CApp	*application = new CApp;
rather than via a macro call. The TCL_NEW macro is essentially equivalent to the use of operator new, supplemented with a little extra code to deal with problems like no memory.

CApp.cp, CApp.h and x_CApp.cp, x_CApp.h

The remaining files, the headers and implementation files for the "application" and "main document" classes, come in pairs. Thus, there are the CApp files and the x_CApp files.

The files like CApp.cp and CMain.cp start by containing the standard skeleton code for these classes. You edit these files as you expand the functionality.

Some of the member functions for these classes are generated by the Visual Architect and have to be regenerated if you ever subsequently reuse the Visual Architect program to change the display structure.

It is hard for the Visual Architect to scramble through a file, that some programmer has been editing, in order to find the code for which it is responsible.

In order to avoid such difficulties, Symantec chose to split things. Code that is solely the responsibility of the Visual Architect is made separate from code that gets initially sketched by the Visual Architect but is then changed by the developer. The x_ files are the property of the Visual Architect (you shouldn't edit these but you will generally need to read them).

The way Symantec has chosen to solve these difficulties involves the introduction of extra classes in the hierarchy. Instead of a program specific CApp class being derived directly from the library class CApplication, there is a hierarchy. Class CApp is derived from x_CApp, and class x_CApp is derived from the standard CApplication. The intermediary x_ class is a place where the Visual Architect can declare the data members and member functions for which it is responsible.

In the Dice program, there is really nothing of interest in the "application" group of files. Everything in them is standard. None of the code that we need to add will go in these files.

CMain.cp, CMain.h and x_CMain.cp, x_CMain.h

The CMain group of files contain the skeleton that we have to flesh out with our own code to get our working program.

At this stage, most of the interesting stuff is actually in the x_CMain files. The header file x_CMain.h contains the declaration for class x_CMain:

#define x_CMain_super	CDocument
class x_CMain : public x_CMain_super
{
public:

	TCL_DECLARE_CLASS

	// Pointers to panes in window
	CRectOvalButton	*fMain_Rect3;
	CRadioControl	*fMain_BTwo;
	CRadioControl	*fMain_BFour;
	...
	CRadioControl	*fMain_BTwenty;
	CRadioControl	*fMain_BHundred;
	CStaticText	*fMain_Score;
	CButton	*fMain_RollButton;

	void 	Ix_CMain(void);

	virtual	void	DoCommand(long theCommand);
	virtual	void	UpdateMenus(void);

protected:
	virtual	void	MakeNewWindow(void);
	
	virtual	void	NewFile(void);
	virtual void	OpenFile(SFReply *macSFReply) {}
	virtual	void	MakeWindowName(Str255 newName);
	virtual	void	MakeNewContents(void);

	virtual void	DoCROLL(void);
};
(Ignore the TCL_DECLARE_CLASS macro for now; it is explained later in section X.X.)

An x_CMain is a kind of CDocument. But it has extra data members like the series of pointers CRadioControl *fMain_BTwenty etc. (Now you can see the result of using the Pane/Info tool to change the names of the panes.) In addition to extra data members, the class has some member functions. Some of these are pretty much standard and will take much the same form in all programs, for example

void x_CMain::UpdateMenus()
{
	CDocument::UpdateMenus();
	gBartender->EnableCmd(cROLL);
}

Other member functions are specific to the program, but are totally the responsibility of the Visual Architect. For example, the following function is provided to get a window to show the display structure that we just built and to get those pointer data members to link to the objects that represent the components like those radio control buttons:

void x_CMain::MakeNewWindow(void)
{
	itsWindow = TCLGetNamedWindow("\pMain", this);
	
	itsMainPane = (CPane*) TCLGetItemPointer(itsWindow, 0);

// Initialize pointers to the subpanes in the window
...

	fMain_BTwo = (CRadioControl*) itsWindow->FindViewByID(
						kMain_BTwoID);
	ASSERT(member(fMain_BTwo, CRadioControl));

	fMain_BFour = (CRadioControl*) itsWindow->FindViewByID(
						kMain_BFourID);
	...
}
Command handling functions

Our "document" is supposed to handle one non-standard command - the cROLL command. This is catered for by the following automatically generated code:

void x_CMain::DoCommand(long theCommand)
{
	switch (theCommand)
	{
		case cROLL:
			DoCROLL();
			break;
		default:
			CDocument::DoCommand(theCommand);
	}
}

void x_CMain::DoCROLL()
{
	// Subclass must override this function to
	// handle the command
}
"Subclass must override!"
You need to read these "X-files" to spot things like that comment in x_CMain::DoCROLL(). We have to define an overriding implementation for DoCROLL() in class CMain.
CMain files

The initial CMain files have little of interest. File CMain.h contains the class declaration:

class CMain : public x_CMain
{
public:
	TCL_DECLARE_CLASS
	void	ICMain(void);
	virtual	void	MakeNewContents(void);
	virtual	void	ContentsToWindow(void);
	virtual	void	WindowToContents(void);
};
You will note that DoCRoll() is not declared. As well as providing an effective definition we will have to add it as an extra member function in this class declaration.

The four functions that are declared have "do nothing" implementations defined in the CMain.cp file. We don't need these functions in the simple Dice example and so we can leave the empty implementations generated by the Visual Architect.

1.4.2 Adding program specific code

To begin with, we just want some code that will respond to a mouse click in the "Roll" button by displaying a random number in the static text field.

Much of the necessary code is already available through functions in the Macintosh "Toolbox". We can use the functions Random() (prototype declared in Quickdraw.h) and NumToString() (prototype declared in TextUtils.h). Function Random() returns a pseudo-random number in the range -38768 to 32767. Function NumToString() converts a number to a text string (it uses a "Pascal" string with a leading length byte rather than a C style null-terminated string). A static text string object can be given such a Pascal string and be told to change the text that it displays.

A first version of the function is:

void CMain::DoCROLL()
{
	int n = Random();
	Str255 nstr;
	NumToString(n, nstr);
	fMain_Score->SetTextString(nstr);
}
(Type Str255 is a standard type used in C/C++ programs to represent a "Pascal string".) The code is straightforward. Get a random number. Convert it to a string. Give it to the static text object. The static text object is accessed via the pointer data member fMain_Score (declared in the parent x_CMain class).

As well as adding this definition for CMain::DoCROLL() to the CMain.cp file, we have to add the function declaration to the class declaration in CMain.h. This DoCRoll() function is supposed to override the virtual function in class x_CMain. It should be given the same access status. You will often need to check the declarations of "x classes" to find the access status of functions. The DoCRoll() function in x_CMain is protected. Consequently, the revised declaration for class CMain should be:

class CMain : public x_CMain
{
public:
	TCL_DECLARE_CLASS
	void	ICMain(void);
	virtual	void	MakeNewContents(void);
	virtual	void	ContentsToWindow(void);
	virtual	void	WindowToContents(void);
protected:
	virtual void	DoCROLL(void);
};
Header files

As you might expect, some more header files have to be #include at the start of CMain.cp. Actually, it is not necessary to #include either Quickdraw.h or TextUtils.h. These headers are amongst a large group of header files that are, in effect, always scanned when compiling any file from a VA project.

However it is necessary to #include the header CStaticText.h. The C++ compiler has to be able to check our call to the function SetTextString(). (If you omit a header, the error message that you get may be quite confusing. For example, if you didn't include CStaticText.h, the compiler would give an error message for the statement fMain_Score->SetTextString(nstr);. The error message would state that class CStaticText does not have a SetTextString() member.)

Save the edited files before continuing.

1.4.3 Compilation

The project window should now appear similar to that shown in Figure 1.11.
[SYM1.11]

Figure 1.11 Project window identifying files that must be (re)compiled.

The window shows the various files and file groups, each file is tagged with various information. A filled tag in the column under the tick mark identifies a file (or file group) that has to be (re)compiled. Filled marks in the column under the "bug" icon identify files for which supplementary debugging information will be included.

At this stage, there are a large number of files that must be recompiled. If you scroll down to the bottom of the list, you will see summary data. It should indicate that almost 160 files need to be compiled. Most of these are the files with the code from the TCL library. The Dice program itself contributes only six rather small files.

Time for a break

Use the "Build/Bring Up To Date" menu option to start the compilation process. Compilation of 160 files does take some time, so now you have the chance to go off and do something like read a novel (something like Ulysses, or War and Peace, or A la recherche du temps perdu).

1.5 The program

1.5.1 Initial version

When you get back to your computer, you will find the compilation completed and, provided that you didn't make any typing errors when adding declarations and code to the CMain files, the process should have been successful. (If you check in Finder, you will find that the Dice project file has grown from 120K bytes to about 9.5 Mbyte.)

Use the Run option from the Project menu. (You don't yet want "Run with debugger". If the Project menu offers the debug option, use the option key to get the alternative simple Run option. There is a parameter in the project preferences file that determines whether the default "run" option involves the debugger. See your Symantec system manuals for details.)

A temporary window will appear and will be used to provide details of progress in the final linking steps. Eventually, the window for the Dice program will appear. Try it. The radio cluster should work (at least in terms of switching between choices) while activation of the Roll button should result in a number being displayed in the static text field (see Figure 1.12). Of course, the choice of radio button has no effect on the numbers displayed. Another limitation is that the same sequence of "random" numbers will appear every time the program is run.
[SYM1.12]

Figure 1.12 Initial program display.

1.5.2 Completion of program code

Seed the random number generator
If we are to get different sequences of random numbers on different runs of the program, we have to "seed" the random number generator with some value that will vary with each run of the program. Typically, data values such as a date-time value or the "tick count" are used to seed the generator. The seed value is stored in a global data structure qd (which also holds data defining some basic properties of the Quickdraw graphics system). It can be set as follows:
qd.randSeed = TickCount();
This code belongs in one of the start up routines such as CMain::ICMain() or CApp::ICapp().

If the program is to work correctly, the DoCROLL() function will have to scale the random number into a range that is appropriate to the currently selected radio button.

Interacting with the radio buttons

Unfortunately, there is no object that "owns" the cluster of radio buttons that we could ask "Which of your buttons is set?". However, the CMain object does have pointers to each individual button. So we can find which is set by asking each in turn. This code had better be a separate member function of class CMain;. The function might as well return an integer representing the range. A possible definition is as follows:

int CMain::Range()
{
	if(fMain_BTwo->GetButtonValue()) 
		return 2;
	else
	if(fMain_BFour->GetButtonValue()) 
		return 4;
	else
	...
	...
	else
	if(fMain_BTwenty->GetButtonValue()) 
		return 20;	
	else 	
		return 100;
}
(Member function Range() has to be added to the declaration for class CMain; it might as well have protected access status.)
Class Browser

How do you find that a radio button has a GetButtonValue() member function? You can read the class library handbook but it is often easier to call up the Class Browser (via the option in the Windows menu). Figure 1.13 illustrates the form of the Browser window.

As explained in the Symantec manuals, the browser can display the classes in alphabetical order or hierarchically ordered. The hierarchical display is often more useful because you normally need to check a class's ancestry before you can get an idea as to its full functionality. This browser is really quite limited, and it is sometimes difficult to find a class in the hierarchical view. Try finding CStaticText in the hierarchy (hint: start at the "auto destruct" entry and descend the path through CCollaborator).
[SYM1.12]

Figure 1.13 Using the Class Browser to get information on a class.

Function DoCRoll() can be rewritten to use Range():

void CMain::DoCROLL()
{
	long n = labs(Random());
	int  r = Range();
	n = (n % r) + 1;
	Str255 nstr;
	NumToString(n, nstr);
	fMain_Score->SetTextString(nstr);
}
Negative values returned from Random() would confuse; so labs(), declared in stdlib.h, is used to take the absolute value. The value is then reduced modulo the current range (giving a value in the range 0...5 for a six sided disk) and adjusted.

Once you have added the necessary changes to CMain.h and CMain.cp, you will again have to compile the program. Although you only changed CMain.cp you will see that the files x_CApp.cp and x_CMain.cp are also tagged as needing recompilation.

The Symantec project management system keeps track of "header dependencies" for you. It has noted that you have changed CMain.h so it checks to determine which files this affects. Since both x_CApp.cp and x_CMain.cp #include "CMain.h" they also have to be recompiled.

Recompilation of three small files is quicker than the initial compilation. You may not even have time to get another coffee.

The extended code should now perform as required.

1.5.3 "Bells and whistles"

The program can be prettied up a little. For example, rather than instantaneously displaying the new score, the program can loop a few times briefly displaying possible scores before settling on a final score. This would provide some suggestion of a dice rolling.

Such an extension might seem simple. All that is needed is an extra loop in which the different random numbers are selected and displayed. There would have to be a short delay between each number. Code such as the following would seem sufficient:

void CMain::DoCROLL()
{
	int times = (labs(Random()) % 15) +1;
	while(times) {
		long n = labs(Random());
		int  r = Range();
		n = (n % r) + 1;
		Str255 nstr;
		NumToString(n, nstr);
		fMain_Score->SetTextString(nstr);
		long dummy;
		Delay(10, &dummy);
		times--;
		}
	
}
You can get information about the Delay() function using the THINK Reference program. Delay() is declared in the header file OSUtils.h. Once again, this is one of those standard header files that is always scanned so there is no need for an explicit #include. The delay argument 10 implies a request for a delay of about one sixth of a second.

If you try that code, you will find that there is definitely a delay but nothing gets shown until a final score is displayed.

Delayed drawing to screen

The framework library attempts to optimize drawing to the screen. Changes, like the change of the context of a text field, do not usually result in immediate changes to the screen. Instead, the system notes that part of the screen is "invalid". At some later time, all the "invalid" regions of the screen are combined into an update area and the system performs a general updating of the screen. This can only happen if control returns to the main "run" routine of the application object. The tight little loop picking random numbers and so forth never gives the system a chance to handle screen updates.

Although usually you leave it to the system to decide when to draw data, you do sometimes have to force immediate drawing. You can tell any kind of CPane object to redraw a specified area.

The following code gets the different randomly chosen values to appear on the screen:

void CMain::DoCROLL()
{
	int times = (labs(Random()) % 15) +1;
	while(times) {
		long n = labs(Random());
		int  r = Range();
		n = (n % r) + 1;
		Str255 nstr;
		NumToString(n, nstr);
		fMain_Score->SetTextString(nstr);
		Rect area;
		area.top = 0; area.left = 0; 
		area.bottom = 20; area.right = 120;
		fMain_Score->Draw(&area);
		long dummy;
		Delay(10, &dummy);
		times--;
		}
	
}
Sound?

You would probably like to add sound effects. It is relatively easy just to play a sound, so you can arrange to play a sound at the end of that loop to indicate that the dice roll had stopped. The actual sound handling code is pretty obscure! It is full of low level calls to the Macintosh Toolbox and illustrates the kind of code that motivated the development of the frameworks. Before the frameworks were developed, you would have had to write similar code to do things like open a window, check a menu selection, or draw text.

The code for playing a sound is:

SndListHandle borrowed_snd;
borrowed_snd = (SndListHandle) GetResource('snd ', 5150);
if(borrowed_snd != NULL) {
	OSErr err;
	HLock((Handle) borrowed_snd);
	err = SndPlay( NULL, borrowed_snd, FALSE);
	HUnlock((Handle) borrowed_snd);
	ReleaseResource((Handle) borrowed_snd);
	}
The header file Sound.h contains the declarations of data types, such as SndListHandle, and functions like SndPlay(); this header has to be #included. The other functions, HLock(), GetResource() etc, are once again in standard header files that always get included.

You have to arrange that your desired sound is encode as an 'snd ' resource in the resource file of the project (see below for an explanation of how you can do this using ResEdit). Each resource of a specific type has a unique identifier number; in the example code this is assumed to be the arbitrary value 5150.

The call to GetResource() loads the sound from the resource file (or sets the variable borrowed_snd to NULL if the sound effect is no longer there). If the sound was successfully loaded it is "locked in memory". The call to SndPlay() then plays the sound. When the sound effect finishes, the resource can be unlocked and then freed.

Of course you have to get a sound as a 'snd ' resource. You can record your own (Apple-Control Panels/Sound) or "borrow" a sound from another program with sound effects. One way or the other, you have to have the sound as an 'snd ' resource in an existing file. You copy the original 'snd ' into the project's resource file using ResEdit.

While in the Symantec development environment double click on the Project Resources.rsrc file. The ResEdit program will be launched and the file opened.

ResEdit will open a window showing the existing resources. There will be "ICN#" resources (collections of icons) and FREF resources but no 'snd ' resources. Use ResEdit's File/Open option to open the second file, the one from which you propose to copy the 'snd ' resource that you want. Select its 'snd ' resources. You will get a display similar to that shown in Figure 1.14. Use standard "copy & paste" techniques to transfer the sound.
[SYM1.13]

Figure 1.14 Display from ResEdit during transfer of 'snd ' resource to project.

You probably wanted something more sophisticated. You wanted to have the sound of dice rolling while the numbers flickered. When the sound stopped, the numbers should also stop.

That kind of effect requires "asynchronous sound". You tell the Mac OS to play the sound and then get on (simultaneously) with the work of your program; checking occasionally for a signal from the OS that would indicate that the sound had finished. Unfortunately, this is a little too complex. It involves you in setting up "sound channels" and providing call back routines. Forget about serious sound effects for a while at least!

Exercise

Add a second group of radio buttons. This group is to allow the user to select from one to four dice for the roll. A single score pane, showing the total for all dice rolled, should suffice.
Last modified April 1996. Please email questions to nabg@cs.uow.edu.au