TweetFollow Us on Twitter

Jul 99 Getting Started

Volume Number: 15 (1999)
Issue Number: 7
Column Tag: Getting Started

Preferences Files

By Dan Parks Sydow

How a Mac program sets and retrieves program preferences

Two months back -- in May's Getting Started article -- we looked at how a program reads resources stored in an external resource file. In last month's Getting Started article we discussed how a Mac program reads data from a resource of a programmer-defined type. This month we introduce two new topics: creating a new resource file "on-the-fly" (during application execution), and writing data to a resource of a programmer-defined type. When we combine the concepts we covered in the previous two Getting Started articles with this month's new topics, the result is all the code necessary to provide your own application with the ability to create, read, modify, and make use of a preferences file.

Custom Resource Recap

Last month we developed the RsrcTemplate program to demonstrate how a Mac application works with resources of a custom type. Recall that we used ResEdit to create a single template resource of the standard resource type TMPL. In that resource we defined a number of fields, with each field having a ResEdit data type that corresponded to a C/C++ data type. Figure 1 shows last month's one TMPL resource, which we named TSTD. Recall that the name we chose was somewhat arbitrary (TSTD could be thought of as standing for TeST Data) -- the template name doesn't in any way define or limit the eventual contents of the template resource.



Figure 1. The TSTD template with three items.

A template resource itself doesn't hold data. Instead, a single TMPL resource serves to format any number of resources of still another type. That other type of resource is a custom type that bears the same name as the template that formats it. Continuing on with last month's example, Figure 2 shows that a custom TSTD resource displays its data in an easily readable manner because the TMPL resource of the same name is automatically used by ResEdit to display the resource.



Figure 2. A custom TSTD resource displayed using the TSTD template.

Figures 1 and 2 use the TSTD TMPL resource and the TSTD resource type -- but you'll note that in the figures these resources are held in a file named PrefFile.rsrc. This month's program PrefFile uses the exact same TMPL and TSTD resources that were developed last month. Yes, we did in fact have a valid reason for spending time creating those resources!

Once a custom resource exists, its data can be used by an application. PrefFile, as last month's RsrcTemplate program did, uses the value of the write item to see if the other TSTD resource data should be written to a program window. A write value of 0 means no, a write value of 1 means yes. The score item holds a high score (again, we're assuming we're writing a game), and the name item holds the name of the person who holds the high score.

An application accesses custom resource data by loading the resource into an application-defined data structure. This data structure defines fields that match the order and type of the data held in the resource. For the TSTD resource we came up with a corresponding data structure that looked like this:

typedef struct
{	
	short		write;
	long			score;
	Str255		name;
} TemplateRecord, *TemplatePtr, **TemplateHandle;

When the application needs to access information from a TSTD resource, it calls the Toolbox function Get1Resource() to load the resource data into memory and to return a handle to the data.

Handle		dataHandle;

dataHandle = Get1Resource( 'TSTD', 128 );

Accessing data is now done by typecasting the generic Handle to a handle of our own data structure type. Once that's done, any field in the structure can be accessed. Here the value of the write field is assigned to a local variable named resValue:

short		resValue;

resValue = (**(TemplateHandle)dataHandle).write;

Opening the Preferences File

Last month's RsrcTemplate program read data from a custom resource. If an application is to save user-defined data on disk for use in subsequent executions of the program, then that application also needs to be able to write data to a custom resource. Before writing a resource, though, we need to make sure the resource fork to be written to is open. If the fork is the appLication resource fork, we're all set -- that fork is always open. If, however, the fork is a part of an external file (which is the case for a preferences file), we need to open that fork.

Two months back, in the May Getting Started article, we looked at how a program opens the resource fork of a file in order to gain access to that file's resources. May's ResFiles example program used its OpenResourceFork() function to accomplish the task.

void		OpenResourceFork( void )
{
	short		volRef = 0;
	long			dirID = 0;
	FSSpec		rsrcFSSpec;
	OSErr		err;

	FSMakeFSSpec( volRef, DirID, kRsrcFileName, &rsrcFSSpec );

	gFileRsrcForkRef = FSpOpenResFile( &rsrcFSSpec, fsRdPerm );
	err = ResError();
	if ( err != noErr )
		DoError( "\pOpening resource file failed" );

	UseResFile( gFileRsrcForkRef );  
}

The above version of OpenResourceFork() works fine for a file that we know will be in the same folder as the application -- but that's not always this case. For a preferences file, that's almost never the case. Instead, a program should keep its preferences file in the Preferences folder in the System Folder on the user's startup drive. So instead of setting both the volume reference number and directory ID to 0 (which indicates that the item in question is in the same folder as the application), we'll use the Toolbox function FindFolder() to obtain a path to the user's Preferences folder.

FindFolder() is used to obtain path information to the Preferences folder, Apple Menu Item folder, Control Panels folder, and other system directories. The following is a typical call to the function.

short		volRef;
long			dirID;

FindFolder( kOnSystemDisk, kPreferencesFolderType,
					 kDontCreateFolder, &volRef, &dirID );

The first argument specifies the reference number of the volume that holds the folder in question. System-related folders should of course be on the startup disk, so the Apple-defined constant kOnSystemDisk is used here. To determine which folder to search for, FindFolder() accepts an Apple-defined constant as its second argument. The constant kPreferencesFolderType works for us. The third argument, which can be still another Apple-defined constant, specifies whether or not a new folder of this name should be created if an existing one can't be found. We'll assume that the user certainly has a Preferences folder. In return for this information, FindFolder() fills the fourth and fifth parameters with the volume reference number and directory ID of the sought-after folder.

With the path to the preferences file established, a file system specification, or FSSpec, for the file can be created. That FSSpec is then used in a call to FSpOpenResFile() to open the preferences file's resource fork. Since your program may be writing information to the file, make sure to pass the Apple-defined constant fsRdWrPerm as the second argument. This opens the file with both read and write permission.

FSMakeFSSpec( volRef, dirID, kPrefFileName, &prefFSSpec );

gFileRsrcForkRef = FSpOpenResFile(&prefFSSpec, fsRdWrPerm);

If FSpOpenResFile() fails to find the preferences file, it returns a value of -1. While a preferences file won't disappear on its own, a user who doesn't recognize the file by name may at some point "clean up" his disk and delete the file. In such a case, your program needs to create a new preferences file. In just a bit we'll cover how this is done. For now, we'll just make the check and then call our own CreateNewPrefFile() function if the preferences file is missing.

if ( gFileRsrcForkRef == -1 )
CreateNewPrefFile( prefFSSpec );

Writing To a Custom Resource

Writing data to a custom resource is essentially the reverse of the operations that were performed to read the data from the resource. In a previous snippet we assigned a local short variable named resValue the value of the write field of a TemplateRecord that was referenced by a handle named dataHandle.

Handle		dataHandle;
short		resValue;

dataHandle = Get1Resource( 'TSTD', 128 );
resValue = (**(TemplateHandle)dataHandle).write;

Here we'll now write back the value of resValue to the write field of a different TemplateRecord data structure. We begin by creating a new generic handle and clearing its memory. Next we typecast the handle to one of type TemplateHandle and then assign the write field the value of the resValue variable.

Handle			newDataHandle;

newDataHandle = NewHandleClear( sizeof( TemplateRecord ) );

 (**(TemplateHandle)newDataHandle).write = resValue;

Typically a program that saves user preferences includes a dialog box where the user enters his or her program preferences. If the user brings up this dialog box, enters some values, and then clicks the OK button, it's a simple matter for the program to collect the value from each dialog box item and fill in a data structure with those values. A program could work with the data structure that holds the original preferences values, and include a scheme to keep track of which values have changed and which are the same. But it's often easier to simply fill in the new structure completely without regard for which values have changed and which are unchanged.

After all of a structure's fields have been filled, the contents of the structure need to be saved to a resource on disk (at this point the data handle references the structure in memory, and has nothing to do with a resource). With the assumption that we've opened the preferences file, we begin by obtaining a handle that references the custom resource that we're about to save the new data to.

Handle			oldDataHandle;

oldDataHandle = Get1Resource( 'TSTD', 128 );

What we're about to do is delete the old, original resource and simply replace it with a new one. Before doing that we can get information about the original resource. The Toolbox function GetResInfo() returns a resource's ID, type, and name. The call to Get1Resource() included the type and ID of the resource -- so our program knew the type and ID of the resource in question. But the resource's name might not be known. While our example program doesn't provide a name for the custom resource (it has a type, TSTD, but any resources of that type haven't been named), your program might.

short			resID;
ResType		resType;
Str255			resName;

GetResInfo( oldDataHandle, &resID, &resType, resName );

Now it's time to remove the original resource and replace it with a new one that contains the data in memory that's referenced by the handle newDataHandle. The Toolbox functions RemoveResource() and AddResource() take care of this work for us.

RemoveResource( oldDataHandle );
AddResource( newDataHandle, resType, resID, resName );

Adding a resource doesn't write the new resource to disk -- a call to the Toolbox function WriteResource() does that.

WriteResource( newDataHandle );

Creating a New Preferences File

When a program attempts to open a file, that program needs to ensure that the file exists. In some cases, an external file may be critical to the operation of a program. That isn't, or shouldn't be, the case with a preferences file. While a missing preferences file will be an inconvenience to a user (the user's preferred settings will need to be re-entered), the program should be able to execute. To overcome the problem of a missing preferences file, your program can simply create a new preferences file and store a set of default values to it. The next time the user sets the preferences, the user's values will overwrite the default values.

If a program stores a copy of the custom resource used to hold preferences data in its own resource fork, the program will then always have a backup copy of a default set of preferences. In the case of our PrefFile program, the application would keep a TSTD resource in its own resource fork. If the program attempts to open its preferences file and the file is missing, the program will first load a copy of this TSTD resource into memory. After that, information about the resource is obtained by calling GetResInfo():

Handle			handle; 
 
handle = Get1Resource( 'TSTD', 128 );
GetResInfo( handle, &resID, &resType, resName );  

Now the resource data is in memory, and ready to be written to a file. A call to the Toolbox function FSpCreateResFile() creates the new resource file:

FSpCreateResFile( &prefFSSpec, 'RSED', 'rsrc', 
								 smSystemScript );

The first argument is an FSSpec that holds the path to the file that's to be created. If this file-creation code is the result of a failed attempt to open a preferences file, then your program will already have a suitable FSSpec. In the above snippet we'll assume that prefFSSpec is an FSSpec variable that comes from the code shown in this article's Opening the Preferences File section.

The second and third arguments are the application signature (or creator) and the file type for the program that can, by default, open the file. In the above snippet 'RSED' is the signature and 'rsrc' is the creator for ResEdit. Thus double-clicking on the file created by the above call to FSpCreateResFile() results in the launching of ResEdit and the opening of the file. This shouldn't be a concern for the user (since the user shouldn't ever need to go into the Preferences folder in the System Folder to open a preferences file), but it makes it easy for you should you ever want to take a look at the contents of the file your program is creating.

The final argument is a script code. A file's script code identifies how the Finder displays the file's name. Here you can use the Apple-defined constant smSystemScript.

With the file created, its resource fork can be opened. This is the same code that was introduced in the earlier discussion on opening a resource file:

	gFileRsrcForkRef = FSpOpenResFile(&prefFSSpec, fsRdWrPerm);

You've already seen how calls to AddResource() and WriteResource() are used to save a resource to a file. Your program will again use these calls to add the default TSTD resource to the newly created preferences file. The complete code for creating a new preferences file appears in the CreateNewPrefFile() function in the PrefFile listing.

PrefFile

This month's program is PrefFile. Running PrefFile results in the appearance of the window shown in Figure 3. As they were for last month's RsrcTemplate program, the contents displayed in the window are read from a TSTD resource. Unlike RsrcTemplate, the data from this resource is read in from an external file -- a preferences file.



Figure 3. The PrefFile window.

The PrefFile program allows the user to change the value of the string and the number that get displayed in the window. The user can also specify whether the string and number are displayed in the window when the program launches. Typical of most Mac programs, this is done in a dialog box that appears when the user chooses Preferences from the Edit menu. While the Preferences item can appear in any menu, it's customary to make this item be the last item in the Edit menu. Figure 4 shows the program's preferences dialog box.



Figure 4. The PrefFile preferences dialog box.

When the user clicks the OK button, the information from the dialog box is stored in the program's preferences file -- it replaces whatever data had been in the one TSTD resource in a file named MyPrefFile in the user's Preferences folder in the System Folder. Changes to the preferences don't affect the program now, but the next time the PrefFile program is started, this new information is used.

Creating the PrefFile Resources

Start the project by creating a new folder named PrefFile in your CodeWarrior development folder. Launch ResEdit, then create a new resource file named PrefFile.rsrc. Make sure to specify the PrefFile folder as the resource file's destination. The resource file will hold resources of the types shown in Figure 5.



Figure 5. The PrefFile resources.

Figure 6 shows the three MENU resources that the PrefFile program uses. After creating the MENU resources, create a MBAR resource that references the ID of each of the three menus.



Figure 6. The MENU resources.

The size and screen placement of the preferences dialog box is defined by a DLOG resource -- in the PrefFile.rsrc file this resource should have an ID of 128. The values you use for these DLOG fields aren't critical. Instead, focus on the corresponding DITL resource. In Figure 7 you see the DITL resource that's used to display the preferences dialog box shown back in Figure 4. When creating this resource, make sure to assign the proper numbers to the enabled items (the two Edit Text boxes, the Check Box, and the Button). These items need to have numbers that match the values you use in the item constants in your code. PrefFile's code defines these dialog item constants:

#define		kOKButton			1
#define		kNameString			2
#define		kScoreString		3
#define		kShowInfoCheck	4



Figure 7. The DITL resource.

A single WIND resource wraps up the interface-related resources. Give this resource an ID of 128 and you're all set -- the size and placement of the window aren't significant.

You'll need to include a single template resource and one custom resource. There's no need to go into detail about these resources -- we're using the same TMPL and TSTD resources that we covered in detail in last month's Getting Started column. Look back at Figures 1 and 2 in this article to get another look at both resources. Note that whatever values you use for the data in the TSTD resource will be used in the preferences file as default values should the preferences file ever need to be re-created. Back in Figure 2 you see that the number 0 and the string "Winner" are in the TSTD resource, and in Figure 3 you see that the score and name displayed in the PrefFile program match this data.

Creating the PrefFile Project

Start CodeWarrior and choose New Project from the File menu. Use the MacOS:C_C++:MacOS Toolbox:MacOS Toolbox Multi-Target project stationary for the new project. You've already created a project folder, so uncheck the Create Folder check box before clicking the OK button. Name the project PrefFile.mcp, and make sure the project's destination is the PrefFile folder.

Add the PrefFile.rsrc resource_file to the new project. Now remove the SillyBalls.rsrc file. The project doesn't require any of the standard ANSI libraries, so go ahead and remove the ANSI Libraries folder from the project window if you want.

Choose New from the File menu to create a new source code window. Save the new window, giving it the name PrefFile.c. Next, choose Add Window from the Project menu to add this empty file to the project. Now remove the SillyBalls.c placeholder file from the project window. Now you're ready to type in the source code.

To save yourself quite a bit of typing, go to from MacTech's ftp site at ftp://ftp.mactech.com/src/mactech/volume15_1999/15.07.sit. There you'll find the PrefFile source code file available for downloading.

Walking Through the Source Code

The PrefFile listing begins with a host of constant definitions -- most of which are resource-related. One constant is worthy of note -- kPrefFileName. When it comes time for the program to open its preferences file, it will expect that this file be named MyPrefFile.

/********************* constants *********************/

#define		kDLOGResID			128
#define		kOKButton			1
#define		kNameString			2
#define		kScoreString		3
#define		kShowInfoCheck	4

#define		kWINDResID			128
#define		kTSTDResID			128
#define		kResTypeTSTD		'TSTD'

#define		kWINDResID			128
#define		kMBARResID			128
#define		kALRTResID			128
#define		kPrefFileName		"\pMyPrefFile"

#define		kSleep					7
#define		kMoveToFront		(WindowPtr)-1L

#define		mApple					128
#define		iAbout					1

#define		mFile					129
#define		iQuit					1

#define		mEdit					130
#define		iUndo					1
#define		iCut						3
#define		iCopy					4
#define		iPaste					5
#define		iClear					6
#define		iPreferences		8

So that the program knows the format of our custom TSTD resource, we define a data structure that includes fields that match the order and data type of each item in the custom TSTD resource. This same data structure was introduced in last month's Getting Started article.

/******************* data structures *****************/

typedef struct
{	
	short		write;
	long			score;
	Str255		name;
} TemplateRecord, *TemplatePtr, **TemplateHandle;

PrefFile declares six global variables. The first three come directly from last month's RsrcTemplate program: gWriteScoreInfo tells the program whether it should write the TSTD resource score and name values to a window, and the gHighScore and gName variables hold those two values. The gApplRsrcForkRef and gFileRsrcForkRef are used to keep track of which resource fork is to be used (since it's possible for a program to keep both the application resource fork and a preferences file resource fork open at the same time). The Boolean gDone signals the program when it's time to quit.

/****************** global variables *****************/

Boolean 		gWriteScoreInfo = false;
long	 			gHighScore;
Str255			gName;
short			gApplRsrcForkRef;
short			gFileRsrcForkRef = 0;
Boolean		gDone;

Next come the program's function prototypes.

/********************* functions *********************/

void		ToolBoxInit( void );
void		MenuBarInit( void );
void		WriteValuesToWindow( void );
void		OpenPrefFile( void );
void		CreateNewPrefFile( FSSpec pref_FSSpec );
void		GetPrefValues( void );
void		SetPrefValues( void );
void		OpenPrefDialog( void );
void		EventLoop( void );
void		DoEvent( EventRecord *eventPtr );
void		HandleMouseDown( EventRecord *eventPtr );
void		HandleMenuChoice( long menuChoice );
void		HandleAppleChoice( short item );
void		HandleFileChoice( short item );
void		HandleEditChoice( short item );
void		DoError( Str255 errorString );

The main() function begins by initializing the Toolbox and the menu bar. Then a reference number for the application resource fork is obtained for later use by the program. Next, a window is opened for the (possible) display of the preferences values. A call to the application-defined GetPrefValues() is made in order to load to memory the data from the preferences file's TSTD resource. Next, the value of gWriteScoreInfo (which was determined in GetPrefValues()) is checked to determine if the remaining TSTD data should be written to the window. If writing should take place, we delegate that work to the application-defined function WriteValuesToWindow() -- a routine we lifted from last month's program. After that the program enters an event loop.

/********************** main *************************/

void		main( void )
{  
	WindowPtr	window;

	ToolBoxInit();
	MenuBarInit();

	gApplRsrcForkRef = CurResFile();
  
	window = GetNewWindow( kWINDResID, nil, (WindowPtr)-1L );
	SetPort( window );
  
	GetPrefValues();

	if ( gWriteScoreInfo == true )
		WriteValuesToWindow();
  
	EventLoop();
}

ToolBoxInit() and MenuBarInit() are the same as prior versions.

/******************** ToolBoxInit ********************/

void		ToolBoxInit( void )
{
	InitGraf( &qd.thePort );
	InitFonts();
	InitWindows();
	InitMenus();
	TEInit();
	InitDialogs( nil );
	InitCursor();
}

/******************** MenuBarInit ********************/

void		MenuBarInit( void )
{
	Handle				menuBar;
	MenuHandle		menu;
	
	menuBar = GetNewMBar( kMBARResID );
	SetMenuBar( menuBar );

	menu = GetMenuHandle( mApple );
	AppendResMenu( menu, 'DRVR' );
	
	DrawMenuBar();
}

After GetPrefValues() executes (we'll get to that routine just ahead), the gHighScore variable holds the value that was stored in the score item in the TSTD resource, and the gName variable holds the string that was stored in the name item in that same resource. The WriteValuesToWindow() routine is invoked from main() to write these two values to the program's window.

/************** WriteValuesToWindow ******************/

void		WriteValuesToWindow( void )
{
	Str255	tempStr;

	MoveTo( 20, 20 );
	NumToString( gHighScore, tempStr );
	DrawString( tempStr );

	MoveTo( 20, 40 );
	DrawString( gName );
}

The application-defined function OpenPrefFile() is called to, yes, open the preferences file. Actually, OpenPrefFile() opens just the resource fork of this file. That's fine and dandy, though, because it's in this fork that all the preferences data is stored. The code that makes up this function was discussed earlier in this article. And, if much of it looks very familiar, you've probably got the May Getting Started ResFiles example on your mind -- OpenPrefFile() is similar to the ResFiles function OpenResourceFork().

/******************** OpenPrefFile ******************/

void		OpenPrefFile( void )
{
	short		volRef;
	long			dirID;
	FSSpec		prefFSSpec;
  
	FindFolder( kOnSystemDisk, kPreferencesFolderType,
						 kDontCreateFolder, &volRef, &dirID );
            
	FSMakeFSSpec( volRef, dirID, kPrefFileName, &prefFSSpec );

	gFileRsrcForkRef = FSpOpenResFile(&prefFSSpec, fsRdWrPerm);

	if ( gFileRsrcForkRef == -1 )
		CreateNewPrefFile( prefFSSpec );

	UseResFile( gFileRsrcForkRef );  
}

If OpenPrefFile() fails to find the specified file, it calls CreateNewPrefFile() to create a new preferences file. Most of the code found in CreateNewPrefFile() has been described earlier in this article -- you'll want to especially take note of the Creating a New Preferences File section.

/**************** CreateNewPrefFile *****************/

void		CreateNewPrefFile( FSSpec prefFSSpec )
{
	Handle			handle;  
	Short			resID;
	ResType		resType;
	Str255			resName;
   
	UseResFile( gApplRsrcForkRef );
  
	handle = Get1Resource( kResTypeTSTD, kTSTDResID );

	GetResInfo( handle, &resID, &resType, resName );  
	DetachResource( handle );

	FSpCreateResFile( &prefFSSpec, 'RSED', 
									'rsrc', smSystemScript );
	gFileRsrcForkRef = FSpOpenResFile(&prefFSSpec, fsRdWrPerm);
	UseResFile( gFileRsrcForkRef );
  
	AddResource( handle, resType, resID, resName );
	ChangedResource( handle );
	WriteResource( handle );  
	ReleaseResource( handle );
}

At startup, the PrefFile program calls GetPrefValues() to open the preferences file and to extract the user preferences from that file. GetPrefValues() calls the just-discussed OpenPrefFile() to open the file, then goes on to call Get1Resource() to load to memory the data from the custom TSTD resource.

/******************* GetPrefValues *******************/

void		GetPrefValues( void )
{
	short			write; 
	Handle			dataHandle;
	StringPtr	sourceStr;
	Size				bytes;

	OpenPrefFile();
	  
	dataHandle = Get1Resource( kResTypeTSTD, kTSTDResID );

Next, global variables are assigned values based on the resource data which is now in memory. The code to do that comes from last month's RsrcTemplate program. With the global variables assigned values, there's no longer a need for the preferences file to be open -- so we call CloseResFile() to close it.

	write = (**(TemplateHandle)dataHandle).write;
	if ( write == 0 )
		gWriteScoreInfo = false;
	else
		gWriteScoreInfo = true;

	gHighScore = (**(TemplateHandle)dataHandle).score;
  
	sourceStr = (**(TemplateHandle)dataHandle).name;
	bytes = (**(TemplateHandle)dataHandle).name[0] + 1;
	BlockMoveData( sourceStr, gName, bytes );

	CloseResFile( gFileRsrcForkRef );

	UseResFile( gApplRsrcForkRef );  
}

The entering of new preferences values is performed by the user through the use of a dialog box brought on by choosing Preferences from the Edit menu. Before looking at the code that implements that dialog box, let's see how the writing of a resource is the inverse of reading one. You've just seen the code for GetPrefValues() -- now here's the code for SetPrefValues(). Take a moment to compare the two routines.

/******************* SetPrefValues *********************/

void		SetPrefValues( void )
{
	Handle			oldDataHandle;
	Handle			newDataHandle;
	Short			resID;
	ResType		resType;
	Str255			resName;
	short			write;
	Size				bytes;
   
	OpenPrefFile();

	newDataHandle = NewHandleClear( sizeof( TemplateRecord ) );
	HLock( newDataHandle );
  
	if ( gWriteScoreInfo == true )
		write = 1;
	else
		write = 0;
	(**(TemplateHandle)newDataHandle).write = write;

	(**(TemplateHandle)newDataHandle).score = gHighScore;
 
	bytes = gName[0] + 1;
	BlockMoveData( gName, 
					 (**(TemplateHandle)newDataHandle).name, bytes );

	oldDataHandle = Get1Resource( kResTypeTSTD, kTSTDResID );
  
	GetResInfo( oldDataHandle, &resID, &resType, resName); 	
  
	RemoveResource( oldDataHandle );
	AddResource( newDataHandle, resType, resID, resName );
	WriteResource( newDataHandle );

	HUnlock( newDataHandle );

	ReleaseResource( newDataHandle );

	CloseResFile( gFileRsrcForkRef );

	UseResFile( gApplRsrcForkRef );  
}

When the user chooses the Preferences menu item, OpenPrefDialog() is invoked. This is a fairly long routine, but there's nothing too tricky about it. OpenPrefDialog() begins with the declaration of a number of local variables, and then a call to GetNewDialog().

/****************** OpenPrefDialog *******************/

void		OpenPrefDialog( void )
{
	DialogPtr	dialog;
	Boolean		done = false;
	short			oldValue;
	short			item;
	short			type;
	Handle			handle;
	Rect				rect;
	Str255			scoreStr;
  
	dialog = GetNewDialog( kDLOGResID, nil, (WindowPtr)-1L );

	GetDialogItem( dialog, kNameString, &type, &handle, &rect);

Before displaying the dialog box (shown back in Figure 4) on screen, we'll fill in the two edit boxes and set the state of the check box. At program startup we saved the preferences values to global variables, so here we can use those values -- there's no need to again open the preferences file and extract the custom resource data.

	SetDialogItemText( handle, gName );

	NumToString( gHighScore, scoreStr );
	GetDialogItem(dialog, kScoreString, &type, &handle, &rect);
	SetDialogItemText( handle, scoreStr );

	GetDialogItem( dialog, kShowInfoCheck, &type, 
								&handle, &rect );  
	if ( gWriteScoreInfo == true )
		SetControlValue( (ControlHandle)handle, 1 ); 
	else
		SetControlValue( (ControlHandle)handle, 0 ); 

	ShowWindow( dialog );
	SetPort( dialog );

Until the user clicks on the dialog box OK button, a while loop watches the user's actions. If the check box is clicked on, its state is toggled and the value of the global variable gWriteScoreInfo is adjusted. The text that's typed in either of the edit boxes isn't of concern until the user does click the OK button. Only at that time do we bother to read the string from each of these two edit boxes.

	while ( done == false )
	{
		ModalDialog( nil, &item );
     
		switch ( item )
		{
			case kShowInfoCheck:
				GetDialogItem( dialog, kShowInfoCheck, &type, 
											&handle, &rect );  
				oldValue = GetControlValue( (ControlHandle)handle ); 
				if ( oldValue == 1 )
				{
					SetControlValue( (ControlHandle)handle, 0 ); 
					gWriteScoreInfo = false;
				}
				else
				{
					SetControlValue( (ControlHandle)handle, 1 ); 
					gWriteScoreInfo = true;
				}
				break;

			case kOKButton:      
				GetDialogItem( dialog, kNameString, &type, 
											&handle, &rect );
				GetDialogItemText( handle, gName );

				GetDialogItem( dialog, kScoreString, &type, 
											&handle, &rect );
				GetDialogItemText( handle, scoreStr );
				StringToNum( scoreStr, &gHighScore );

				done = true;
				break;
		}   
	}

After the OK button is clicked the dialog box is dismissed, and the new user preferences are saved to the preferences file. A call to the previously described SetPrefValues() function takes care of the work of opening the preferences file and of saving the new values to a TSTD resource in that file. A call to DisposeDialog() frees the memory that had been reserved for the dialog box.

	SetPrefValues();
  
	DisposeDialog( dialog ); 
}

As is true of most of our examples, the PrefFile program is event driven -- so the remaining event-handling code should look quite familiar. EventLoop() is called from main() -- its purpose is to repeatedly grab an event and pass it to DoEvent(). DoEvent() determines the type of the event, and calls the routine appropriate to handling the event. If the event involves a menu item selection, then HandleMenuChoice() calls the appropriate menu-handling routine. In PrefFile our chief concern is the display of the preferences dialog box. That dialog box is posted by OpenPrefDialog() -- a function invoked from HandleEditChoice().

/******************** EventLoop **********************/

void		EventLoop( void )
{		
	EventRecord		event;

	gDone = false;
	while ( gDone == false )
	{
		if ( WaitNextEvent( everyEvent, &event, kSleep, nil ) )
			DoEvent( &event );
	}
}

/*********************** DoEvent *********************/

void		DoEvent( EventRecord *eventPtr )
{
	char		theChar;
	
	switch ( eventPtr->what )
	{
		case mouseDown: 
			HandleMouseDown( eventPtr );
			break;
		case keyDown:
		case autoKey:
			theChar = eventPtr->message & charCodeMask;
			if ( (eventPtr->modifiers & cmdKey) != 0 ) 
				HandleMenuChoice( MenuKey( theChar ) );
			break;
		case updateEvt:
			BeginUpdate( (WindowPtr)(eventPtr->message) );
			EndUpdate( (WindowPtr)(eventPtr->message) );
			break;
	}
}

/****************** HandleMouseDown ******************/

void		HandleMouseDown( EventRecord *eventPtr )
{
	WindowPtr		window;
	short				thePart;
	long					menuChoice;
	
	thePart = FindWindow( eventPtr->where, &window );
	
	switch ( thePart )
	{
		case inMenuBar:
			menuChoice = MenuSelect( eventPtr->where );
			HandleMenuChoice( menuChoice );
			break;
		case inSysWindow : 
			SystemClick( eventPtr, window );
			break;
	}
}

/****************** HandleMenuChoice *****************/

void		HandleMenuChoice( long menuChoice )
{
	short		menu;
	short		item;
	
	if ( menuChoice != 0 )
	{
		menu = HiWord( menuChoice );
		item = LoWord( menuChoice );
		
		switch ( menu )
		{
			case mApple:
				HandleAppleChoice( item );
				break;
			case mFile:
				HandleFileChoice( item );
				break;
			case mEdit:
				HandleEditChoice( item );
				break;
		}
		HiliteMenu( 0 );
	}
}

/***************** HandleAppleChoice *****************/

void		HandleAppleChoice( short item )
{
	MenuHandle		appleMenu;
	Str255				accName;
	short				accNumber;
	
	switch ( item )
	{
		case iAbout:
			SysBeep( 10 );
			break;
		default:
			appleMenu = GetMenuHandle( mApple );
			GetMenuItemText( appleMenu, item, accName );
			accNumber = OpenDeskAcc( accName );
			break;
	}
}

/***************** HandleEditChoice ******************/

void		HandleEditChoice( short item )
{
	switch ( item )
	{
		case iPreferences:
			OpenPrefDialog();
			break;
	}
}

/***************** HandleFileChoice ******************/

void		HandleFileChoice( short item )
{
	switch ( item )
	{
		case iQuit:
			gDone = true;
			break;
	}
}

/********************** DoError **********************/

void		DoError( Str255 errorString )
{
	ParamText( errorString, "\p", "\p", "\p" );
	
	StopAlert( kALRTResID, nil );
	
	ExitToShell();
}

Running PrefFile

Run PrefFile by selecting Run from CodeWarrior's Project menu. After compiling the code and building a program, CodeWarrior runs the program. A small window appears and, based on the first value stored in the preferences file, other preferences data is or isn't drawn in this window. Choose Preferences from the Edit menu to see the preferences-setting dialog box. Make some changes here, then dismiss the dialog box. Your changes won't be noticeable now. But the next time you run PrefFile, the changes will be in effect (hint, hint: quit PrefFile and rerun the program)!

Till Next Month...

The PrefFile program makes use of a very simple preferences file. Your real-world application will no doubt require a more sophisticated custom resource in which your program's settings are stored. Now that you know the basics of creating a new resource file, and of creating, reading, and writing custom resource data, you're all set to implement such a preferences file. To make sure you understand the many concepts covered in the last few articles, start out slowly. For instance, try modifying the TSTD template to include just one more field. Then edit the TSTD resource to include a value for the new field. Now modify the PrefFile code appropriately -- you'll need to add a corresponding field to the definition of the program's one struct. You'll also want to add to the GetPrefValues() and SetPrefValues() functions so that these routines properly read in, and write to, the preferences file. Modifying this month's project should keep you busy until next month when we visit a completely new topic...

 
AAPL
$99.02
Apple Inc.
+1.35
MSFT
$43.97
Microsoft Corpora
-0.53
GOOG
$590.60
Google Inc.
+1.58

MacTech Search:
Community Search:

Software Updates via MacUpdate

Acorn 4.4 - Bitmap image editor. (Demo)
Acorn is a new image editor built with one goal in mind - simplicity. Fast, easy, and fluid, Acorn provides the options you'll need without any overhead. Acorn feels right, and won't drain your bank... Read more
Bartender 1.2.20 - Organize your menu ba...
Bartender lets you organize your menu bar apps. Features: Lets you tidy your menu bar apps how you want. See your menu bar apps when you want. Hide the apps you need to run, but do not need to... Read more
TotalFinder 1.6.2 - Adds tabs, hotkeys,...
TotalFinder is a universally acclaimed navigational companion for your Mac. Enhance your Mac's Finder with features so smart and convenient, you won't believe you ever lived without them. Tab-based... Read more
Vienna 3.0.0 RC 2 :be5265e: - RSS and At...
Vienna is a freeware and Open-Source RSS/Atom newsreader with article storage and management via a SQLite database, written in Objective-C and Cocoa, for the OS X operating system. It provides... Read more
VLC Media Player 2.1.5 - Popular multime...
VLC Media Player is a highly portable multimedia player for various audio and video formats (MPEG-1, MPEG-2, MPEG-4, DivX, MP3, OGG, ...) as well as DVDs, VCDs, and various streaming protocols. It... Read more
Default Folder X 4.6.7 - Enhances Open a...
Default Folder X attaches a toolbar to the right side of the Open and Save dialogs in any OS X-native application. The toolbar gives you fast access to various folders and commands. You just click... Read more
TinkerTool 5.3 - Expanded preference set...
TinkerTool is an application that gives you access to additional preference settings Apple has built into Mac OS X. This allows to activate hidden features in the operating system and in some of the... Read more
Audio Hijack Pro 2.11.0 - Record and enh...
Audio Hijack Pro drastically changes the way you use audio on your computer, giving you the freedom to listen to audio when you want and how you want. Record and enhance any audio with Audio Hijack... Read more
Intermission 1.1.1 - Pause and rewind li...
Intermission allows you to pause and rewind live audio from any application on your Mac. Intermission will buffer up to 3 hours of audio, allowing users to skip through any assortment of audio... Read more
Autopano Giga 3.6 - Stitch multiple imag...
Autopano Giga allows you to stitch 2, 20, or 2,000 images. Version 3.0 integrates impressive new features that will definitely make you adopt Autopano Pro or Autopano Giga: Choose between 9... Read more

Latest Forum Discussions

See All

Traps n’ Gemstones Review
Traps n’ Gemstones Review By Campbell Bird on July 28th, 2014 Our Rating: :: CASTLEVANIA JONESUniversal App - Designed for iPhone and iPad Fight mummies, dig tunnels, and ride a runaway minecart to discover ancient secrets in this... | Read more »
The Phantom PI Mission Apparition Review
The Phantom PI Mission Apparition Review By Jordan Minor on July 28th, 2014 Our Rating: :: GHOSTS BUSTEDUniversal App - Designed for iPhone and iPad The Phantom PI is an exceedingly clever and well-crafted adventure game.   | Read more »
More Stubies Are Coming Your Way in a Ne...
More Stubies Are Coming Your Way in a New Update Posted by Jessica Fisher on July 28th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
The Great Prank War Review
The Great Prank War Review By Nadia Oxford on July 28th, 2014 Our Rating: :: PRANKING IS SERIOUS BUSINESSUniversal App - Designed for iPhone and iPad Though short, The Great Prank War offers an interesting and fun mix of action and... | Read more »
Marvel Contest of Champions Announced at...
Marvel Contest of Champions Announced at Comic-Con Posted by Jennifer Allen on July 28th, 2014 [ permalink ] Announced over the weekend at San Diego Comic-Con was the fairly exciting looking Marvel Contest of Champions. | Read more »
Teenage Mutant Ninja Turtles Review
Teenage Mutant Ninja Turtles Review By Jennifer Allen on July 28th, 2014 Our Rating: :: DULL SWIPINGUniversal App - Designed for iPhone and iPad The pizza power is weak when it comes to this Teenage Mutant Ninja Turtles game.   | Read more »
Exploration Focused Puzzle Game Beatbudd...
Exploration Focused Puzzle Game Beatbuddy Set to Make Transition from PC to iOS this September Posted by Jennifer Allen on July 28th, 2014 [ permalink ] | Read more »
PlanetHD
PlanetHD By Nadia Oxford on July 28th, 2014 Our Rating: :: SPACE MADNESSUniversal App - Designed for iPhone and iPad PlanetHD will keep players busy for a while, though its unpredictable physics are a handful to deal with.   | Read more »
This Week at 148Apps: July 21-25, 2014
Another Week of Expert App Reviews   At 148Apps, we help you sort through the great ocean of apps to find the ones we think you’ll like and the ones you’ll need. Our top picks become Editor’s Choice, our stamp of approval for apps with that little... | Read more »
Reddme for iPhone - The Reddit Client (...
Reddme for iPhone - The Reddit Client 1.0 Device: iOS iPhone Category: News Price: $.99, Version: 1.0 (iTunes) Description: Reddme for iPhone is an iOS 7-optimized Reddit client that offers a refreshing new way to experience Reddit... | Read more »

Price Scanner via MacPrices.net

13-inch 2.5GHz MacBook Pro on sale for $1099,...
Best Buy has the 13″ 2.5GHz MacBook Pro available for $1099.99 on their online store. Choose free shipping or free instant local store pickup (if available). Their price is $100 off MSRP. Price is... Read more
Roundup of Apple refurbished MacBook Pros, th...
The Apple Store has Apple Certified Refurbished 13″ and 15″ MacBook Pros available for up to $400 off the cost of new models. Apple’s one-year warranty is standard, and shipping is free. Their prices... Read more
Record Mac Shipments In Q2/14 Confound Analys...
A Seeking Alpha Trefis commentary notes that Apple’s fiscal Q3 2014 results released July 22, beat market predictions on earnings, although revenues were slightly lower than anticipated. Apple’s Mac’... Read more
Intel To Launch Core M Silicon For Use In Not...
Digitimes’ Monica Chen and Joseph Tsai, report that Intel will launch 14nm-based Core M series processors specifically for use in fanless notebook/tablet 2-in-1 models in Q4 2014, with many models to... Read more
Apple’s 2014 Back to School promotion: $100 g...
 Apple’s 2014 Back to School promotion includes a free $100 App Store Gift Card with the purchase of any new Mac (Mac mini excluded), or a $50 Gift Card with the purchase of an iPad or iPhone,... Read more
iMacs on sale for $150 off MSRP, $250 off for...
Best Buy has iMacs on sale for up to $160 off MSRP for a limited time. Choose free home shipping or free instant local store pickup (if available). Prices are valid for online orders only, in-store... Read more
Mac minis on sale for $100 off MSRP, starting...
Best Buy has Mac minis on sale for $100 off MSRP. Choose free shipping or free instant local store pickup. Prices are for online orders only, in-store prices may vary: 2.5GHz Mac mini: $499.99 2.3GHz... Read more
Global Tablet Market Grows 11% in Q2/14 Notwi...
Worldwide tablet sales grew 11.0 percent year over year in the second quarter of 2014, with shipments reaching 49.3 million units according to preliminary data from the International Data Corporation... Read more
New iPhone 6 Models to Have Staggered Release...
Digitimes’ Cage Chao and Steve Shen report that according to unnamed sources in Apple’s upstream iPhone supply chain, the new 5.5-inch iPhone will be released several months later than the new 4.7-... Read more
New iOS App Helps People Feel Good About thei...
Mobile shoppers looking for big savings at their favorite stores can turn to the Goodshop app, a new iOS app with the latest coupons and deals at more than 5,000 online stores. In addition to being a... Read more

Jobs Board

*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Sr. Product Leader, *Apple* Store Apps - Ap...
**Job Summary** Imagine what you could do here. At Apple , great ideas have a way of becoming great products, services, and customer experiences very quickly. Bring Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
WW Sales Program Manager, *Apple* Online St...
**Job Summary** Imagine what you could do here. At Apple , great ideas have a way of becoming great products, services, and customer experiences very quickly. Bring Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.