Home » Learning Curve
File Management Macintosh StyleSometimes it's easier to just cross the street.
The advent of NeXTSTEP to Cupertino meant enormous savings in coding for third party vendors. The NeXTSTEP platform is the most streamlined after Smalltalk the world has ever seen. Developers commonly claim their task times are a fifth or less of what they used to be. John Carnack of id Software says DOOM wouldn't have been possible without NeXTSTEP and Sir Tim Berners-Lee said the same about the WWW.
Which makes the reaction of the legacy Apple developers all the more perplexing. Anno 2005, a full eight years after Steve Jobs brought NeXT to Cupertino, they're still foundering in old Macintosh cruft and refuse to make the jump.
The following code is taken from 'Learning Carbon', an 'Apple Developer Connection recommended title' published by O'Reilly. Chapter 11 is about file management. It extends from page 198 to page 251 - fifty four pages long. Although considerable copy is used to explain file system intrinsics (the Macintosh way) the bulk of the chapter consists of code snippets the author wants the reader to copy ad hoc into the sample project.
The code given below - with a minimum of explanatory text - is what is needed with legacy Carbon/toolbox Apple programming to manage the simple 'new file', 'open file', and 'save file' operations. [If you're not a programmer, don't be scared off: just look at the bulk of the code.]
'File Management Macintosh Style'.
[This is a long article. If you want to jump to the punch line right now, click here.]
The Application Domain
The 'Moon Travel Planer' sample application in 'Learning Carbon' is eminently simple. Its objective is to be able to display files exactly as depicted below (p. 250 - even line breaks and misspellings are preserved).

The program does not allow the user to edit the itinerary files, but it must allow new files to be created, existing files to be opened, and all files to be saved.
All the following code has to be written into the application to make ordinary 'new file', 'open file', and 'save file' operations work.
Navigation Services
Following is the chain of events used in saving files. Note all steps involve participation on behalf of program code in the client application.
Set up dialog options | Call NavGetDefaultCreationOptions | Initialise new Save File dialog | Call NavCreatePutFileDialog | Display dialog box | Call NavDialogRun | Wait for user to respond | Go to event loop | User responds to dialog | Nav Services notifies your callback function with kNavCBUserAction constant | Get reply record | Call NavDialogGetReply | Save file or cancel as appropriate | Process user action | Dispose of reply record | Call NavDisposeReply | Dialog box is closed | Nav Services notifies your callback function with kNavCBTerminate constant | Dispose of dialog | Call NavDisposeDialog |
Constants & Global Variables
#define kMTPOpenItineraryCommand 'oPit'
#define kMTPSaveAsItineraryCommand 'sAit'
#define kMTDocType 'iTin'
#define kMTPCFStringProperty 'cfst'
NavEventUPP gEventProc;
Window Event Handler
OSStatus MTPItineraryWindowEventHandler (EventHandlerCallRef handlerRef,
EventRef event, void *userData)
{
OSStatus err;
HICommand command;
UInt32 eventKind;
UInt32 eventClass;
eventClass = GetEventClass(event);
eventKind = GetEventKind(event);
err = eventNotHandleErr;
if ((eventClass == kEventClassWindow) && (eventKind == kEventWindowClose))
{
SelectWindow (gMainWindow);
err = MTPDoCloseItinerary((WindowRef) userData);
}
else if (eventClass == kEventClassCommand)
{
GetEventParameter (event, kEventParamDirectObject, typeHICommand,
NULL, sizeof (HICommand), NULL, &command);
switch (command.commandID)
{
case kMTPOpenItineraryCommand:
err = MTPDoOpenItinerary((WindowRef) userData);
break;
case kMTPSaveAsItineraryCommand:
err = MTPDoSaveItineraryAs((WindowRef) userData);
break;
case kMTPCloseCommand:
err = MTPDoCloseItinerary((WindowRef) userData);
break;
}
}
return err;
}
The Open Dialog
OSStatus MTPOpenItinerary()
{
OSStatus err = noErr;
NavDialogRef theOpenDialog;
NavDialogCreationOptions dialogOptions;
if (( err = NavGetDefaultDialogCreationOptions(
&dialogOptions)) == noErr ) {
dialogOptions.modality = kWindowModalityAppModal;
gEventProc = NewNavEventUPP( MTPNavEventCallback );
if ((err = NavCreateGetFileDialog( &dialogOptions, NULL,
gEventProc, NULL,
NULL, NULL, &theOpenDialog )) == noErr) {
if ( theOpenDialog != NULL ) {
if (( err = NavDialogRun( theOpenDialog )) != noErr) {
NavDialogDispose( theOpenDialog );
DisposeNavEventUPP( gEventProc );
}
}
}
}
return err;
}
The Navigation Event Handler
pascal void MTPNavEventCallback( NavEventCallbackMessage callBackSelector,
NavCBRecPtr callBackParms, void* callBackUD)
{
OSStatus err = noErr;
switch (callBackSelector) {
case kNavCBUserAction: {
NavReplyRecord reply;
NavUserAction userAction = 0;
if ((err = NavDialogGetReply (callBackParams->Context,
&reply )) == noErr ) {
userAction = NavDialogGetUserAction (
callBackParams->context);
switch (userAction) {
case kNavUserActionOpen: {
MTPOpenTheFile (&reply);
break;
}
}
err = NavDisposeReply (&reply);
}
break;
}
case kNavCBTerminate: {
NavDialogDispose (callBackParms->context);
DisposeNavEventUPP (gEventProc);
break;
}
}
}
Retrieve a File to Open
void MTPOpenTheFile(NavReplyRecord *reply)
{
AEDesc actualDesc;
FSRef fileToOpen;
HFSUniStr255 theFileName;
CFStringRef fileNameCFString;
WindowRef newWindow;
OSStatus err;
if ((err = AECoerceDesc(&reply->selection,
typeFSRef, &actualDesc)) == noErr)
{
if (&actualDesc != NULL) {
if (err = AEGetDescData (&actualDesc,
(void *) (&fileToOpen),
sizeof(FSRef)) == noErr))
{
err = FSGetCatalogInfo( &fileToOpen, kFSCatInfoNone,
NULL, &theFileName,
NULL, NULL );
fileNameCFString = CFStringCreateWithCharacters (NULL,
theFileName.unicode,
theFileName.length );
newWindow = MTPDoItinerary( fileNameCFString);
ShowWindow(newWindow);
err = MTPReadFile(&fileToOpen, newWindow);
}
}
AEDisposeDesc(&actualDesc);
}
}
Create a New Window
WindowRef MTPDoNewItinerary(CFStringRef inName)
{
IBNibRef itineraryNib;
EventTypeSpec itinerarySpec[2] = {
{kEventClassCommand,kEventCommandProcess},
{kEventClassWindow, kEventWindowClass}};
OSStatus err;
WindowRef theWindow;
err = CreateNibReference(CFSTR("itinerary"), &itineraryNib);
require_noerr( err, CantGetNibRef );
err = CreateWindowFromNib(itineraryNib,
CFSTR("Itinerary), &theWindow);
require_noerr( err, CantCreateWindow );
DisposeNibReference(itineraryNib);
err = InstallWindowEventHandler (theWindow,
NewEventHandlerUPP (MTPItineraryWindowEventHandler),
2,
itinerarySpec,
(void *) theWindow, NULL);
err = SetWindowTitleWithCFString ( theWindow, inName);
return theWindow;
CantCreateWindow:
CantGetNibRef:
return NULL;
}
Read Data from a File
OSStatus MTPReadFile(FSRef *inFSRef, WindowRef theWindow)
{
ByteCount count, actualCount;
SInt16 forkRefNum=0;
UniChar buffer[256];
OSStatus err = noErr;
CFMutableStringRef theText;
HFSUniStr255 forkName;
theText = CFStringCreateMutable (NULL, 0);
err = FSGetDataForkName (&forkName);
err = FSOpenFork (inFSRef, forkName.length,
forkName.unicode, fsRdPerm, &forkRefNum);
if (err == noErr) {
do {
count = 256 * sizeof(UniChar);
err = FSReadFork (forkRefNum, fsAtMark,
0, count, &buffer, &actualCount);
actualCount/= sizeof(UniChar);
CFStringAppendCharacters (theText, buffer, actualCount);
} while (err == noErr);
if (err == eofErr) {
err = noErr;
MTPDisplayFile (theText, theWindow);
err == SetWindowProperty (theWindow,
kMTPApplicationSignature,
kMTPCFStringProperty,
sizeof (CFStringRef),
(void *)&theText);
}
err = FSCloseFork(forkRefNum);
}
return err;
}
Display Contents of a File
void MTPDisplayFile (CFStringRef stringToDisplay,
WindowRef inWindow)
{
Rect bounds;
GrafPtr oldPort;
GetPort (&oldPort);
SetPortWindowPort (inWindow);
EraseRect (GetWindowPortBounds (inWindow, &bounds));
TXNDrawCFStringTextBox (stringToDisplay, &bounds, NULL, NULL);
SetPort (oldPort);
}
Create and Display a Save Dialog
OSStatus MTPDoSaveItineraryAs (WindowRef theItineraryWindow)
{
OSStatus err = noErr;
NavDialogRef theSaveDialog;
NavDialogCreationOptions dialogOptionsl;
if (( err = NavGetDefaultDialogCreationOptions
(&dialogOptions )) == noErr ) {
err = CopyWindowTitleAsCFString( theItineraryWindow,
&dialogOptions.saveFileName );
dialogOptions.parentWindow = theItineraryWindow;
dialogOptions.modality = kWindowModalityWindowModal;
gEventProc = NewNavEventUPP( MTPNavEventCallback );
if ((err = NavCreatePutFileDialog(&dialogOptions,
kMTPDocType,
kMTPApplicationSignature,
gEventProc,
(void *)(theItineraryWindow),
&theSaveDialog)) == noErr) {
if ( theSaveDialog != NULL ) {
if (( err = NavDialogRun
(theSaveDialog)) != noErr) {
NavDialogDispose( theSaveDialog );
DisposeNavEventUPP( gEventProc );
}
}
}
if ( dialogOptions.saveFileName != NULL )
CFRelease( dialogOptions.saveFileName );
}
return err;
}
Additional Navigation Event Handler Code
switch (userAction) {
case kNavUserActionOpen: {
MTPOpenTheFile (&reply);
break;
}
case kNavUserActionSaveAs: {
err = MTPDoFSRefSave ((WindowRef)(callBackUD), &reply );
break;
}
}
Create a Save File [sic]
OSErr MTPDoFSRefSave (WindowRef theItineraryWindow, NavReplyRecord* reply )
{
OSErr err = noErr;
FSRef fileRefParent;
AEDesc actualDesc;
if ((err = AECoerceDesc (&reply->selection,
typeFSRef, &actualDesc )) == noErr) {
if ((err = AEGetDescData( &actualDesc, (void *)&fileRefParent,
sizeof( FSRef ) )) == noErr ) {
UniChar* nameBuffer = NULL;
UniCharCount sourceLength = 0;
sourceLength = (UniCharCount) CFStringGetLength(
reply->saveFilename );
nameBuffer = (UniChar*)NewPtr (sourceLength *2);
CFStringGetCharacters ( reply->saveFileName,
CFRangeMake( 0, sourceLength),
&nameBuffer[0] );
if (nameBuffer != NULL) {
if ( reply->replacing ) {
FSRef fileToDelete;
if ((err = FSMakeRefUnicode (&fileRefParent,
sourceLength, nameBuffer,
kTextEncodingUnicodeDefault,
&fileToDelete )) == noErr) {
err = FSDeleteObject( &fileToDelete );
}
}
if ( err == noErr ) {
FSRef newFSRef;
FileInfo *fileInfo;
FSCatalogInfo catalogInfo;
fileInfo = (FileInfo *)
&catalogInfo.finderInfo[0];
BlockZero(fileInfo, sizeof(FileInfo));
fileInfo->fileType = kMTPDocType;
fileInfo->fileCreator =
kMTPApplicationSignature;
if ((err = FSCreateFileUnicode (&fileRefParent,
sourceLength, nameBuffer,
kFSCatInfoFinderInfo,
&catalogInfo, &newFSRef,
NULL)) == noErr {
MTPWriteFile (&newFSRef,
theItineraryWindow);
}
}
}
DisposePtr ((Ptr)nameBuffer );
}
AEDisposeDesc( &actualDesc );
}
return err;
}
Write a File to Disk
OSStatus MTPWriteFile (FSRef *inRef, WindowRef theItineraryWindow)
{
HFSUniStr255 forkName;
SInt16 forkRefNum;
CFIndex length;
CFStringRef theString;
OSStatus err;
UniChar * buffer;
err = GetWindowProperty (theItineraryWindow,
kMTPApplicationSignature,
kMTPCFStringProperty,
sizeof(CFStringRef), NULL,
(void *)&theString);
length = CFStringGetLength(theString);
buffer = (UniChar*)NewPtr(length * sizeof(UniChar));
CFStringGetCharacters (theString, CFRangeMake(0, length), buffer);
err = FSGetDataForkName (&forkName);
if (( err = FSOpenFork (inRef, (UniCharCount)forkName.length,
forkName.unicode,
fsRdWrPerm, &forkRefNum)) == noErr) {
err = FSWriteFork(forkRefNum, fsFromStart,
0, length, (void *) buffer, NULL);
err = FSCloseFork(forkRefNum);
}
free(buffer);
return err;
}
Close a Window
OSStatus MTPDoCloseItinerary(WindowRef inWindow)
{
CFStringRef theString;
OSStatus err = noErr;
err = GetWindowProperty (inWindow, kMTPApplicationSignature,
kMTPCFStringProperty, sizeof(CFStringRef),
NULL, (void *)&theString);
if (theString != NULL) CFRelease (theString);
DisposeWindow(inWindow);
return err;
}
File Management NeXTSTEP Style
The complete code corresponding to all the above for the given application is represented by the following seven (7) lines of code in NeXTSTEP. (No additional code is needed to create new files.)
-(BOOL)readFromFile:(NSString *)file ofType:(NSString *)type {
[textView setString:[NSString stringWithContentsOfFile:file]];
return YES;
}
-(BOOL)writeToFile:(NSString *)file ofType:(NSString *)type {
return [[textView string] writeToFile:file atomically:NO];
}
Sometimes it's easier to just cross the street.
|