Rixstep
 About | ACP | Buy | Industry Watch | Learning Curve | News | Products | Search | Substack
Home » Learning Curve

File Management Macintosh Style

Sometimes it's easier to just cross the street.


Get It

Try It

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 optionsCall NavGetDefaultCreationOptions
Initialise new Save File dialogCall NavCreatePutFileDialog
Display dialog boxCall NavDialogRun
Wait for user to respondGo to event loop
User responds to dialogNav Services notifies your callback function with kNavCBUserAction constant
Get reply recordCall NavDialogGetReply
Save file or cancel as appropriateProcess user action
Dispose of reply recordCall NavDisposeReply
Dialog box is closedNav Services notifies your callback function with kNavCBTerminate constant
Dispose of dialogCall 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.

About | ACP | Buy | Industry Watch | Learning Curve | News | Products | Search | Substack
Copyright © Rixstep. All rights reserved.