MACINTOSH C CARBON: A Hobbyist's Guide To Programming the Macintosh in C
Version 1.0
� 2001 K. J. Bricknell
CHAPTER 18
FILES AND NAVIGATION SERVICES

Introduction
This chapter addresses:
- Creating, opening, reading from, writing to, and closing files.
- Navigation Services, an application programming interface that allows your application to provide a user interface for navigating, opening, and saving Mac OS file objects.

Files
Types of Files
A file is a named, ordered sequence of bytes stored on a volume. The files associated with an application are typically:
- The application file itself, which comprises the application's executable code and any application-specific resources and data.
- Document files created by the user using the application, which the user can edit.
- A preferences file created by the application to store user-specified preference settings for the application.
The Operating System also uses files for certain purposes. For example, as stated at Chapter 9, the File Manager uses a special file called the volume's catalog file to maintain the hierarchical organisation of files and folders in a volume.
Characteristics of Files
File Forks
Macintosh files comprise two forks, called the data fork and the resource fork. The resource fork contains a resource map and resources. Unlike the bytes stored in the resource fork, the bytes in the data fork do not have to have any particular internal structure. Your application must therefore be able to interpret the bytes in the data fork in an appropriate manner.
All Macintosh files contain a data fork and a resource fork; however, one or both of these forks may, in fact, be empty. Fig 1 shows the typical contents of the data and resource forks of an application file and a document file.
If your data can be structured as a resource, you might elect to store that data in the resource fork, in which case you use Resource Manager functions to both store and retrieve it. Retrieving data from a resource fork is a comparatively simple matter because all you have to do is pass the resource type and ID to the relevant Resource Manager function.
If it is neither possible nor advisable to store the data in the resource fork, you must store it in the data fork. This is normally the favoured option for storing, for example, a document's text. In this case, you use File Manager functions to store and retrieve the data. With File Manager functions, unlike Resource Manager functions, you can access any byte, or group of bytes, individually.
Generally speaking, unless the data created by the user will occupy only a small number of resources, you should store it in the data fork. Always bear in mind that the Resource Manager was not designed as a general purpose data storage and retrieval system.
File Size
Volumes
A volume, which can be an entire disk or only part thereof, is that part of a storage device formatted to contain files. Ordinarily, file size is limited only by the size of the volume that contains it.
Logical Blocks and Allocation Blocks
Volumes are formatted into logical blocks. Each logical block can contain up to 512 bytes, the actual size being of interest only to the disk device driver. When the File Manager allocates space for a file, it allocates it in units called allocation blocks, which are groups of consecutive logical blocks. A non-empty file fork always occupies at least one allocation block.
The size of an allocation block is the chief distinguishing feature between the volume format known as the Hierarchical File System (HFS) and the newer, and optional, Hierarchical File System Plus (HFS Plus or HFS+) introduced with Mac OS 8.1. The differences are as follows:
- HFS (Mac OS Standard Format). For HFS-formatted volumes, the File Manager can access a maximum of 65,535 allocation blocks on any volume. Thus the larger the volume, the larger is the allocation block. For example, on a 500 MB volume, the allocation block size is 8KB under HFS.
- HFS Plus (Mac OS Extended Format). For HFS Plus-formatted volumes, the File Manager can access a maximum of 4.29 billion allocation blocks on any volume. This means that even huge volumes can be formatted with very small allocation blocks. The default volume format for Carbon is HFS Plus.
 |
Beginning with Mac OS 9, HFS Plus introduced support for long Unicode filenames, files larger than 2GB, and extended file attributes. The additional File Manager constants, data types, and functions introduced at that time are often referred to as the HFS Plus API. |
On large volumes, the significant reduction in allocation block size under HFS Plus results in significant space savings. For example, on a 4 GB volume, a file containing only 4 KB of information requires 64 KB of space under HFS, whereas the same file requires only 4KB of space under HFS Plus.
Physical and Logical End-Of-File
There is a difference between the amount of space allocated to a file and the number of bytes of actual data in the file. This is reflected in the two numbers used to describe the size of a file:
- Physical End-Of-File. The physical end-of-file is the number of bytes currently allocated to the file by the File Manager. Since the file's first byte is byte number 0, the physical end-of-file is 1 greater than the number of the last byte in its last allocation block. The physical end-of-file is thus always an exact multiple of allocation block size.
- Logical End-Of-File. The logical end-of-file is one greater than the number of bytes that currently contain data.
Fig 2 illustrates logical end-of-file and physical end-of-file.
Your application can adjust the size of a file by moving the logical end-of-file. If, when you increase the size of a file, the logical end-of-file is moved past the physical end-of-file, one or more allocation blocks are automatically added to the file by the File Manager. By the same token, the File Manager automatically deletes the unneeded allocation block if you move the logical end-of-file more than one allocation block short of the current physical end-of-file.
Clumps and Combating File Fragmentation
The volume's clump size determines the number of allocation blocks added to the file when you move the logical end-of-file past the physical end-of-file. The File Manager enlarges files by adding clumps (which are groups of contiguous allocation blocks) as a way of reducing file fragmentation and improving input/output performance..
Your application can also takes steps to reduce file fragmentation. Suppose you are extending a file with multiple write operations. If you know before you begin how large the file is likely to become, you should first call SetEOF to set the file to that size.
File Access
The operations your application can perform on a file depend on whether it is open or closed. For example, reading and writing operations can only be performed on open files, and deleting operations can only be performed on closed files.
Access Path and File Reference Number
When a file is opened, the File Manager reads in file information and creates an access path to the file. The file information is stored in a file control block (FCB). The access path, which is assigned a unique file reference number, specifies the volume on which the file is located and the location of the file on that volume.
File Mark
The File Manager maintains a file mark (a current-position marker) for each access path. The file mark, which is moved each time a byte is read or written, is the number of the next byte to be read or written. By setting the file mark or specifying an offset, you can control the beginning point of a read or write operation.
Data Buffer
When it transfers data to or from your application, the File Manager uses a data buffer in RAM. You must therefore pass the address of this data buffer whenever you read or write a file's data.
Disk Cache
The File Manager uses an intermediate buffer, called the disk cache, when reading data from, or writing data to, the file system.
During a write operation, data is transferred from your application's data buffer to the disk cache. During a read operation, the File Manager looks for data in the disk cache and, if data is found in the cache, transfers that data to your application's data buffer. If the File Manager finds no data in the disk cache, it reads the requested number of bytes from the disk directly to your application's data buffer.
The Hierarchical File System
Directories and Directory ID
The method used to organise files on a Macintosh volume is called a hierarchical file system. In this system, files are grouped into directories (also called folders). These directories may, in turn, be grouped into other directories (see Fig 3). As shown at Fig 3, each directory has a number associated with it called the directory ID.
Root Directory
The Finder and the File Manager work together to maintain the organisation of files and folders on a volume, ensuring that the representation on the desktop corresponds directly to the hierarchical directory structure on the volume. In file system parlance, the volume is referred to as the root directory, and the folders are referred to as subdirectories (or simply as directories).
Mounted Volumes
When a volume is mounted, the File Manager places information about the volume in a volume control block (VCB) and assigns a volume reference number by which you can refer to the volume until it is unmounted. Mounted volumes appear on the desktop.
You can identify a volume by its volume reference number or by its volume name. To avoid confusion between volumes with the same name, you should ordinarily use the volume reference number to refer to a volume.
When a volume is unmounted, the volume control block is released and the volume is no longer known to the File Manager.
Parent Directory and Parent Directory ID
The directory in which a subdirectory is located is referred to as a parent directory. A parent directory is assigned a parent directory ID. A special parent directory ID is assigned by the File Manager to a volume's root directory. All this facilitates a consistent method of identifying files and directories using the volume reference number, the parent directory ID, and the file or directory name.
Generally speaking, your application does not need to keep track of the location of files in the file system hierarchy. The location of most of the files your application opens and saves is provided by either the Finder or Navigation Services.
Aliases
An alias is a special kind of file that represents another file, folder, or volume. The Finder and Navigation Services automatically resolve aliases.
Identifying Files and Directories - File System Specification Structure and File System Reference
Three pieces of information are all that is needed to identify a file or directory: a volume reference number; a parent directory ID; the name of the file or directory. Of relevance is this regard are two data types, namely, the file system specification structure and the opaque file system reference:
struct FSSpec
{
short vRefNum; // Volume reference number.
long parID; // Directory ID of parent directory.
Str63 name; // Filename or directory name.
};
typedef struct FSSpec FSSpec;
typedef FSSpec *FSSpecPtr, **FSSpecHandle;
struct FSRef
{
UInt8 hidden[80];
};
typedef struct FSRef FSRef;
typedef FSRef *FSRefPtr;
The opaque data type FSRef, whose purpose is similar to that of the file system specification structure, was introduced with the HFS Plus API. . Note that there is no need to call the File Manager to dispose of an FSRef when it is no longer needed.
Creating, Opening, Reading From, Writing To, and Closing Files
Your application typically creates, opens, reads from, writes to, and closes files in response to the user choosing commands from the File menu. In addition, your application opens, reads from, writes to, and closes files in response to the required Apple events (see Chapter 10).
The following describes how to perform typical file operations within the context of a user choosing commands from an imaginary application's File menu. For the purposes of illustration, the assumption is made that the files involved store text documents and that, when retrieved from file, the documents are displayed in a window with scroll bars.
General File Menu and Required Apple Events Handling Strategy
A suggested general strategy for handling user choices of the New, Open..., Close, Save, Save As..., Revert, and Quit commands, and for responding to the required Apple events, is illustrated at Fig 4.
Preliminaries - Creating a Document Structure
The contents of document files are displayed in windows. Ordinarily, your application should define a document structure which contains information about the window and information about the file whose contents are displayed in the window. The following is an example document structure for an application that handles text files:
typedef struct
{
ControlHandle vScrollBarHdl; // Handle to vertical scroll bar.
ControlHandle hScrollBarHdl; // Handle to horizontal scroll bar.
SInt16 fileRefNum; // File reference number for window's file.
FSSpec fileFSSpec; // File's file system specification structure.
TEHandle textEditHdl; // Handle to TextEdit structure.
Boolean windowTouched; // Has window's data changed?
} documentStructure;
typedef documentStructure *documentStructurePtr;
typedef documentStructure **documentStructureHdl;
Note the fileRefNum and fileFSSpec fields. Note also that the last field (windowTouched) is used to record whether the content of the document in memory differs from that in the associated file. Your application should set this field to false when it first reads in the file and immediately after each save, and to true when the content of the document in memory is changed by the user after the first read-in and after the subsequent saves. If the windowTouched field is set to true and the user attempts to close the document window, your application should present an alert asking the user whether the changed version of the document should be saved.
Document structures can be associated with the relevant window by storing a handle to the structure in the window object using the function SetWRefCon.
Creating a New Document Window
The user creates a new untitled document window using the New... command in the File menu. In addition, it is usual for an application to open a new untitled document window when it receives an Open Application Apple event from the Finder. (See doNewCommand at Fig 4.)
Although the function which responds to the user choosing the New� command and Open Application Apple event opens a new window, it should not create a new file. The reason for this is that, in the event, the user may elect not to save the document. It is thus preferable to wait until the user decides to save the new document before creating a file. Accordingly, the fileRefNum field of the new window's document structure should be set to 0 to indicate that no file is currently associated with this window.
Opening a File and Reading in Data
Your application will need to open a file when the user chooses the Open... command from the File menu (see doOpenCommand at Fig 4) and when it receives Open Documents and (on Mac OS 8/9) Print Documents Apple events.
Opening the Navigation Services Open Dialog Box
Your application's initial response to the user choosing the Open... command from the File menu should be to elicit a file selection from the user by by creating and displaying a Navigation Services Opne dialog (see Fig 6).
Calls to the Navigation Services 3.0 functions NavCreateGetFileDialog and NavDialogRun create and display the Navigation Services Open dialog. When the user addresses the dialog, selects one or more files, and clicks the Open button, your application examines the selection field of a NavReplyRecord structure (see Navigation Services, below) and disposes of the dialog. The selection field is an Apple Event descriptor list (AEDescList). You can determine the number of files in the list by calling the Apple Event Manager function AECountItems. Each selected file object is described in an AEDesc structure. You can coerce this descriptor into a file system specification (FSSpec) structure to perform operations such as opening the file.
Creating a Window and Opening the File
The next steps are to call a function (doNewDocWindow at Fig 4) to create a window and associated document structure and then open the selected file's data fork (doOpenFile at Fig 4).
The file's data fork is opened using FSpOpenDF:
OSErr FSpOpenDF(spec,permission,refNum);
FSSpec *spec; File system specification structure.
SInt8 permission; Access mode.
short *refNum; Returned file reference number.
FSpOpenDF takes the FSSpec returned by Navigation Services as its first parameter. The permission field specifies the access mode for opening the file. The access mode may be specified using one of the following constants:
Constant |
Value |
Description |
fsCurPerm |
0 |
Whatever permission is allowed. |
fsRdPerm |
1 |
Read permission. |
fsWrPerm |
2 |
Write permission. |
fsRdWrPerm |
3 |
Exclusive read/write permission. |
fsRdWrShPerm |
4 |
Shared read/write permission. |
FSpOpenDF returns, in its third parameter, a file reference number. This reference number should be saved to the window's document structure so that it can be readily retrieved for use as a parameter in calls to functions which read from and write to the file.
Reading File Data
When you have opened a file, you can read data from it. Ordinarily, you will want to read data from the file when the user first opens it. And your application will have to read data from the file when the user chooses the Revert command in the File menu to revert to the last saved version of the document (see doRevertCommand at Fig 4). Typically, a function for reading file data:
- Retrieves the file reference number from the document structure.
- Calls SetFPos to set the file mark to the beginning of the file:
OSErr SetFPos(refNum,posMode,posOff);
short refNum; File reference number.
short posMode; Positioning mode.
long posOff; Positioning offset.
The posMode parameter must contain one of the following constants:
Constant |
Value |
Description |
fsAtMark |
0 |
Remain at current mark. |
fsFromStart |
1 |
Set mark relative to beginning of file. |
fsFromLEOF |
2 |
Set mark relative to logical end of file. |
fsFromMark |
3 |
Set mark relative to current mark. |
rdVerify |
64 |
Add to above for read-verify. |
- Determines the number of bytes in the file by calling GetEOF:
OSErr GetEOF(refNum,logEOF);
short refNum; File reference number.
long *logEOF; Receives length of file, in bytes.
- Calls FSRead to read the specified number of bytes from the file into the specified buffer:
OSErr FSRead(refNum,count,buffPtr);
short refNum; File reference number.
long *count; On input: bytes to read. On output: actual bytes read.
void *buffPtr; Address of buffer into which bytes are to be read.
Note that FSRead returns, in the count parameter, the actual number of bytes read.
Saving a File
The user can indicate that the current contents of a document should be saved:
- By choosing Save or Save As... from the File menu.
- By clicking the Save button in the Navigation Services Save Changes alert you present when the user attempts to close a "touched" window.
- By clicking the Save button in the Navigation Services Save Changes alert you present when the user attempts to quit the application while a "touched" window remains open.
Handling the Save Command
To handle the Save command (see doSaveCommand at Fig 4), your application should:
- Check the file reference number field of the window's document structure to determine if the window already has a file.
- If the window already has a file, call the function for writing files to disk (see doWriteFile at Fig 4). If the window does not have a file, call the function for handling the Save As... command.
Handling the Save As... Command
To handle the Save As... command (see doSaveAsCommand at Fig 4), your application should proceed as follows:
- Call the Navigation Services 3.0 functions NavCreatePutFileDialog and NavDialogRun to create and display the Navigation Services Save Location dialog (see Fig 10).
When the user addresses the dialog and clicks the Save button, your application examines the selection field of a NavReplyRecord structure (see Navigation Services, below) and disposes of the dialog. The selection field is an Apple Event descriptor list (AEDescList). The file object is described in an AEDesc structure. If your application is running on Mac OS X, you will be able to coerce this data to type FSRef. If this coercion fails (meaning that your application is running on Mac OS 8/9) you will be able to coerce the data to type FSSpec. The FSRef or FSSpec will be required for the save operation.
- Save Using FSRef. If the coercion to type FSRef succeeds:
- Call AEGetDescData to extract the data from the dataHandle field of the AEDesc structure. This is the FSRef for the parent directory.
- Call CFStringGetCharacters to extract into a buffer the contents of the string referenced in the saveFileName field of the NavReplyRecord structure.
- If the replacing field of a NavReplyRecord structure contains true, call the HFS Plus API function FSMakeFSRefUnicode to create an FSRef for the file, passing in the FSRef for the parent directory and the extracted filename characters. Pass this FSRef in a call to FSDeleteObject to delete the file:
OSErr FSDeleteObject(ref);
const FSRef *ref; Pointer to FSRef specifying file or directory to delete.
- Call FSCreateFileUnicode, passing in the FSRef for the parent directory and the extracted filename characters, to create a new file with a Unicode name:
OSErr FSCreateFileUnicode(parentRef,nameLength,name,whichInfo,catalogInfo,
newRef,newSpec);
const FSRef *parentRef; FSRef for directory where file to be created.
UniCharCount nameLength; Length of file's name.
const UniChar *name; Unicode name for file.
FSCatalogInfoBitmap whichInfo; Catalog information fields to be set, if any.
const FSCatalogInfo *catalogInfo; Values of file's catalog infoformation.
FSRef *newRef; On return, FSRef for new file.
FSSpec *newSpec; On return, FSSpec for new file.
- Call FSpGetFInfo, passing in the FSSpec received in the last parameter of the call to FSCreateFileUnicode. Assign the file type and creator to the relevant fields of the obtained Finfo structure and call FSpSetFInfo to set the Finder information.
- Assign the file system specification (FSSpec) structure to the file system specification structure field of the window's document structure.
- Call FSpOpenDF to open the data fork.
- Assign the file reference number returned by FSpOpenDF to the file reference number field of the window's document structure.
- Call SetWTitle to set the window's title, using the string extracted from the name field of the file system specification (FSSpec) structure.
- Call the function for writing files to disk (see doWriteFile at Fig 4).
- Save Using FSSpec. If the coercion to type FSRef does not succeed:
- Call the Navigation Services 3.0 function NavDialogGetSaveFileName to get the file name from the edit text field of the Save Location dialog, convert it to a Pascal string using CFStringGetPascalString, and assign that string to the name field of the file system specification (FSSpec) structure.
- If the replacing field of the NavReplyRecord structure does not contain true, call FSpCreate to create a new file and set the file type and creator:
OSErr FSpCreate(spec,creator,fileType,sciptTag);
FSSpec *spec; File system specification structure.
OSType creator; File creator.
OSType fileType; File type.
ScriptCode scriptTag; Code of script system in which filename is displayed.
- Assign the coerced file system specification (FSSpec) structure to the file system specification structure field of the window's document structure.
- If the window already has a file (that is, if the file reference number field of the document structure does not contain 0), close that file with a call to FSClose:
OSErr FSClose(refNum);
short refNum; File reference number.
- Call FSpOpenDF to open the data fork.
- Assign the file reference number returned by FSpOpenDF to the file reference number field of the window's document structure.
- Call SetWTitle to set the window's title, using the string extracted from the name field of the sfFile field of the Standard File reply structure.
- Call the function for writing files to disk (see doWriteFile at Fig 4).
Writing File Data
The function for writing data (see doWriteFile at Fig 4) should write to a temporary file, not to the document file itself. If you write directly to the document's file, you risk corrupting that file if the write operation does not complete successfully. The broad approach for saving data safely to disk is therefore to write the data to a temporary file and then, assuming the temporary file has been written successfully, swap the contents of the temporary file and the document's file.
The procedure for updating a file safely is as follows:
- Get the file system specification from the document structure.
- Create a temporary filename for the temporary file.
- Call FindFolder to find the temporary folder on the file's volume, or create it if necessary:
OSErr FindFolder(vRefNum,folderType,createFolder,foundVRefNum,foundDirID);
short vRefNum; Volume reference number.
OSType folderType; Folder type for volume.
Boolean createFolder; kCreateFolder or kDontCreateFolder.
short *foundVRefNum; Volume reference number for folder found.
long *foundDirID; Directory ID of folder found.
- Call FSMakeFSSpec to make a file system specification structure for the temporary file:
OSErr FSMakeFSSpec(vRefNum,dirID,fileName,spec);
short vRefNum; Volume reference number.
long dirID; Parent directory ID.
ConstStr255Param fileName; Full or partial pathname.
FSSpec spec; Pointer to FSSpec structure.
- Call FSpCreate to create the temporary file, and FSpOpenDF to open the temporary file's data fork.
- Call the function for writing data to a file (see doWriteData at Fig 4). This function should:
- Retrieve the address and length of the buffer (for example, from a TextEdit structure).
- Call SetFPos to set the file mark to the beginning of the file.
- Call FSWrite to write the buffer to the file:
OSErr FSWrite(refNum,count,buffPtr);
short refNum; File reference number.
long *count; On input: bytes to write. On output: bytes written.
const void *buffPtr; Address of buffer containing data to write.
- Call SetEOF to resize the file to the number of bytes actually written:
OSErr SetEOF(refNum,logEOF);
short refNum; File reference number.
long logEOF; Logical end-of-file.
- Call GetVRefNum to determine the volume containing the file:
OSErr GetVRefNum(refNum,vRefNum);
short refNum; File reference number.
short *vRefNum; Receives volume reference number.
- Call FlushVol to flush the volume:
OSErr FlushVol(volName,vRefNum);
ConstStr63Param volName; Pointer to name of mounted volume
short vRefNum; Volume reference number.
Flushing the volume ensures that both the file's data and the file's catalog entry are updated.
 |
The catalog entry for a file contains fields that describe the physical data (such as the first allocation block and the physical and logical ends of both the resource and data forks) and fields that describe the file within the file system, such as file ID and parent directory ID.
|
- Call FSClose to close the temporary file.
- Call FSClose to close the existing file.
- Call FSpExchangeFiles to swap the contents of the temporary file and the existing file:
OSErr FSpExchangeFiles(source,dest);
const FSSpec *source; Source file.
const FSSpec *dest; Destination file.
FSpExchangeFiles does not actually move the data on the volume. It merely changes the information in the volume's catalog file and, if the files are open, their file control blocks (FCBs).
- Call FSpDelete to delete the temporary file:
OSErr FSpDelete(spec);
const FSSpec *spec; File system specification.
- Call FSpOpenDF to re-open the data fork of the existing file.
As a final step for Mac OS 8/9, you should call a function which copies the missing application name string resource (see Chapter 9.) from the resource fork of the application file to the resource fork of the newly created file. This function (doCopyAppNameResource at Fig 4) should:
- Call FSpCreateResFile to create the new file's resource fork:
void FSpCreateResFile(spec,creator,fileType,sciptTag);
const FSSpec *spec; File system specification structure.
OSType creator; File creator.
OSType fileType; File type.
ScriptCode scriptTag; Code of script system.
- Call FSpOpenResFile to open the resource fork:
short FSpOpenResFile(spec,permission);
const FSSpec *spec; File system specification structure.
SignedByte permission; Permission code.
The constants used to specify the access mode in the FSpOpenDF call (see above) are also used to specify the permission code in the FSpOpenResFile call.
- Call a function (doCopyResource at Fig 4), which copies specified resources from one resource file to another, to copy the missing-application name 'STR ' resource (ID -16396) from your application's resource fork to the resource fork of the newly-created file.
- Call FSClose to close the resource fork.
Reverting to a Saved File
To allow the user to revert to the last saved version of a document, your application can include a Revert command in the File menu. To handle this command (see doRevertCommand at Fig 4), you should call the Navigation Services 3.0 functions NavCreateAskDiscardChangesDialog and NavDialogRun to create and display a Navigation Services Discard Changes alert (see Fig 13). When the user addresses the dialog, and clicks on the OK button, you simply call your function for reading file data (doReadFile at Fig 4) to read the file back into the window.
Closing a File
Your application should ordinarily close a file when the user clicks in the close box of the associated window or chooses the Close command from the File menu. You may also need to close files when the user chooses Quit from the File menu or a Quit Application Apple event is received from the Finder.
When your application needs to close a file, it should first check whether the associated window has been "touched" (see doCloseCommand at Fig 4). If the window has been "touched", you should call the Navigation Services 3.0 functions NavCreateAskSaveChangesDialog and NavDialogRun to create and display a Navigation Services Save Changes alert (see Fig 12). When the user addresses the dialog:
- If the Save button is clicked, call the function for saving files (doSaveCommand at Fig 4), call FSClose to close the file, call FlushVol to ensure that both the file's data and the file's catalog entry are updated, set the file reference number field in the document structure to 0, and release memory associated with the storage of the file's data. Then dispose of the document structure and, finally, the window.
- If the Don't Save button is clicked, perform the same actions as are performed when the Save button is clicked except for the call to the function for saving files.
If the window has not been "touched", perform the same actions as are performed when the Save button is clicked in an Save Changes alert except for the call to the function for saving files.
File Synchronisation Functions
It is always possible that, while a document file is open, the user may drag its Finder icon or proxy icon to another folder (including the Trash) or change the name of the file via the Finder icon. The application itself has no way of knowing that this has happened and will assume, unless it is informed otherwise, that the document's file is still at its original location with its original name. For this reason, applications often include a frequently-called file synchronisation function which synchronises the application with the actual current location (and name) of its currently open document files.
In applications which use the Classic event model, file synchronisation functions should be called after every call to WaitNextEvent. In applications that use the Carbon event model, a timer should be installed to trigger repeated calls to the file synchronisation function. For each of the application's document windows, the synchronisation function should update the application's internal data structures to match that of the document file as it exists on disk. The function should also ensure that, where necessary, the name of the document window is changed to match the current name of the document file on disk and close the document window if the document file has been moved to the Trash folder.

Navigation Services
The user interface for opening and saving files, confirming saves and discarding changes, choosing a volume, folder, file, or file object, creating a new folder, file format translation, and easy navigation of the file system is provided by Navigation Services.
The following reflects Navigation Service 3.0, which was introduced with CarbonLib 1.1, and which established a new model for the creation, display, and handling of Navigation Services dialogs and alerts. Navigation Services 3.0 also introduced support for Unicode and, on Mac OS X, support for sheets and the ability to specify the modality of a dialog.
Navigation Services Dialogs and Alerts
The primary dialogs created by Navigation Services are as follows:
- Open.
- Save Location.
- Choose a Volume.
- Choose a Folder.
- Choose a File.
- Choose a File Object.
The primary alerts created by Navigation Services are as follows:
- Save Changes.
- Discard Changes.
The secondary dialogs and alerts created by Navigation Services are as follows:
- New Folder dialog.
- Replace Confirmation alert.
- Mac OS 8/9 Stationery option dialog.
A further alert, which is applicable only on Mac OS X, and for which no Navigation Services creation function existed at the time of the first release of Mac OS X, is the Review Changes alert.
Modality
On Mac OS 8/9, all primary Navigation Services dialogs are movable modal provided an application-defined event handling (callback) function is provided.
On Mac OS X, your application should ensure that:
- The Save Location dialog, Save Changes alert, and Discard Changes alert are window-modal (that is, sheets).
- The other primary dialogs are application-modal.
Standard User Interface Elements in Primary Dialogs
The standard user interface elements in Navigation Services primary dialog boxes are shown at Fig 5.
Preview
On Mac OS 8/9, the user can toggle a preview area on an off using the Show/Hide Preview push button in the Open dialog. A preview of any file that contains a valid 'pnot' resource will be displayed in this area.
On Mac OS X, the preview appears in the column browser as shown at Fig 5. For files of type 'TEXT' a preview is automatically created.
Persistence
Persistence is the ability of Navigation Services to store information, and to store it on a per-application basis. For example, when a primary dialog is displayed, the browser defaults to the directory location that was in use when that particular dialog box was last closed by that particular application. In addition, if a file or folder was selected when the dialog box was last closed, that file or folder is automatically selected when the dialog is re-opened. For dialogs that are not sheets, the size, position, and, on Mac OS 8/9, sort key and sort order are also remembered for each application.
Creating and Displaying an Open Dialog
The Open dialog (see Fig 6) is created by a call to NavCreateGetFileDialog and displayed by a call to NavDialogRun. You pass a universal procedure pointer to an application-defined event handling function in the inEventProc parameter of NavCreateGetFileDialog.
The NavDialogCreationOptions Structure
You pass a pointer to a NavDialogCreationOptions structure, which specifies options controlling the appearance and behaviour of the dialog, in the inOptions parameter of NavCreateGetFileDialog. The NavDialogCreationOptions structure is as follows:
struct NavDialogCreationOptions
{
UInt16 version;
NavDialogOptionFlags optionFlags;
Point location;
CFStringRef clientName;
CFStringRef windowTitle;
CFStringRef actionButtonLabel;
CFStringRef cancelButtonLabel;
CFStringRef saveFileName;
CFStringRef message;
UInt32 preferenceKey;
CFArrayRef popupExtension;
WindowModality modality;
WindowRef parentWindow;
char reserved[16];
};
typedef struct NavDialogCreationOptions NavDialogCreationOptions;
Field Descriptions
optionsFlags |
One of the following constants of type NavDialogOptionFlags:
Constant |
Description |
kNavDefaultNavDlogOptions |
Use default options. The defaults are as follows:
kNavDontAddTranslateItems
kNavAllowStationery
kNavAllowPreviews
kNavAllowMultipleFiles
|
kNavNoTypePopup |
Don't show file type pop-up menu button. |
kNavDontAutoTranslate |
Don't auto-translate files. (Application will translate.) |
kNavDontAddTranslateItems |
Don't add translation options in Show pop-up menu. |
kNavAllFilesInPopup |
Add "All Documents" menu item in file type pop-up. |
kNavAllowStationery |
Allow stationery files. |
kNavAllowPreviews |
Allow preview to show. |
kNavAllowMultipleFiles |
Allow multiple items to be selected. |
kNavAllowInvisibleFiles |
Allow invisible items to be shown. |
kNavDontResolveAliases |
Don't resolve aliases. |
kNavSelectDefaultLocation |
Make the default location the browser selection. |
kNavSelectAllReadableItem |
Make dialog select All Readable Documents on open. |
kNavSupportPackages |
Recognise file system packages. |
kNavAllowOpenPackages |
Allow opening of packages. |
kNavDontAddRecents |
Don't add chosen objects to Recents list. |
kNavDontUseCustomFrame |
Don't draw the custom area bevel frame. |
kNavDontConfirmReplacement |
Don't show the "Replace File?" alert on save conflict. |
| location |
Location of the upper-left of the dialog (global coordinates). The dialog will appear at the location at which it was last closed if the optionFlags field is assigned NULL or this field is assigned (-1,-1). |
clientName |
Name of your application. This will appear in the dialog's title bar. |
windowTitle |
Overrides the default window title. |
actionButtonLabel |
Title for the dialog's OK push button. If a title is not assigned, the push button will use the default title (Open or Save). |
cancelButtonLabel |
Title for the dialog's Cancel push button. If a title is not assigned, the push button will use the default title (Cancel). |
savedFileName |
Default file name for a saved file. |
message |
A string, which is displayed in the dialog, providing additional instructions to the user. (For an example, see Fig 13). |
preferenceKey |
A value that identifies the set of dialog box preferences that should be used. Assign 0 if you do not wish to provide a preference key. |
popupExtension |
A handle to one or more structures of type NavMenuItemSpec used to add extra menu items to an Open dialog box's Show pop-up menu or a Save dialog box's Format pop-up menu. |
modality |
The dialog's modality (relevant on Mac OS X only). Relevant Window Manager constants are:
kWindowModalityNone
kWindowModalitySystemModal
kWindowModalityAppModal
kWindowModalityWindowModal
|
parentWindow |
The dialog's parent window. (Relevant on Mac OS X only when the dialog is modal-to-window.) |
The function NavGetDefaultDialogCreationOptions may be called to initialise a structure of type NavDialogCreationOptions with the default dialog box options, which are as follows:
- Show and Format pop-up menu buttons are displayed in the Open and Save Location dialogs (Mac OS 8/9).
- Files are auto-translated. (This implies that the application will not translate.)
- Translation options are not included in the Show pop-up menu in the Open dialog.
- The All Documents item is not included in the Show pop-up menu in the Open dialog.
- The Stationery Option... item is included in the Format pop-up menu in the Save Location dialog.
- Previews of selected files, when available, are displayed in the Open dialog.
- Selection of multiple files in the browser list/column browser in the Open dialog is allowed.
- Invisible files are nor displayed.
- Aliases are not resolved.
- The default location in the browser list/column browser is not selected.
- The All Readable Documents is not made the default selection in the Show pop-up menu in the Open dialog.
- File system packages are not displayed.
- File system packages cannot be opened and navigated.
- Chosen file objects are added to the Recents list.
- A border is drawn around the custom area.
- The default titles for the OK and Cancel buttons are used.
- No message is displayed in the dialog.
The Show Pop-up Menu
The types of files to be displayed in the browser list may be chosen from a list of available file types in the Show pop-up menu in the Open dialog (see Fig 7). This list is built from information supplied by your application in a structure of type NavTypeList (see below), a handle to which you pass in the inTypeList parameter of NavCreateGetFileDialog.
The Show pop-up menu button will not appear in the Open dialog if you pass NULL in the inTypeList parameter of the NavCreateGetFileDialog function or if you set the kNavNoTypePopup bit in the optionsFlags field of the NavDialogCreationOptions structure passed in the call to NavCreateGetFileDialog.
If a handle to a NavTypeList structure is passed in the inTypeList parameter and the kNavNoTypePopup bit is set:
- All items in the browser will be deactivated except for the file types specified in the NavTypeList structure whether they were created by the application or not.
- The Show pop-up menu button will not appear.
Native File Types Section
The first item in the native file types section of the Mac OS 8/9 Show pop-up menu defaults to All Known Documents if you do not assign the name of your application to the clientName field of the NavDialogCreationOptions structure passed in the dialogOptions parameter of the NavCreateGetFileDialog function.
The remaining items in the native file types section will default to <Application Name> Document unless you provide kind strings to describe the file types included in your NavTypeList structure (see below). You can do this by including a kind resource (a resource of type 'kind') in your application's resource fork. Fig 8 shows the structure of a compiled 'kind' resource and such a resource being created using Resorcerer
 |
The kind strings from your application's 'kind' resource also appear in the Kind column in Finder window list views.
|
For Mac OS X, the 'kind' resource is ignored if you provide necessary information in your application's 'plst' resource. The relevant keys are CFBundleDevelopmentRegion, CFBundleSignature, and CFBundleDocumentTypes. 'apnm' as a CFBundleTypeOSTypes has same effect as in 'kind' resource.
The NavTypeList Structure
The NavTypeList structure, which defines the list of file types that your application is capable of opening, is as follows:
The NavTypeList Structure
The NavTypeList structure is as follows:
struct NavTypeList
{
OSType componentSignature; // Your application signature.
short reserved;
short osTypeCount; // How many file types will be defined.
OSType osType[1]; // A list of file types your application can open.
};
typedef struct NavTypeList NavTypeList;
typedef NavTypeList *NavTypeListPtr;
typedef NavTypeListPtr *NavTypeListHandle;
You can create your file type list dynamically or you can use an 'open' resource. Fig 9 shows the structure of a compiled 'open' resource and such a resource being created using Resorcerer.
Creating and Displaying a Save Location Dialog
The Save Location dialog (see Fig 10) is created by a call to NavCreatePutFileDialog and displayed by a call to NavDialogRun. You pass a universal procedure pointer to an application-defined event handling function in the inEventProc parameter of NavCreatePutFileDialog.
As with NavCreateGetFileDialog, you pass a pointer to a NavDialogCreationOptions structure in the inOptions parameter of NavCreatePutFileDialog. Other parameters allow you to specify file type and file creator.
Creating and Displaying a Choose a Folder Dialog
The Choose a Folder dialog (see Fig 11) is created by a call to NavCreateChooseFolderDialog and displayed by a call to NavDialogRun. You pass a universal procedure pointer to an application-defined event handling function in the inEventProc parameter of NavCreateChooseFolderDialog and a pointer to a NavDialogCreationOptions structure in the inOptions parameter.
The other dialogs in the Choose family are created and displayed in a similar manner:
- The Choose a Volume dialog is created by a call to NavCreateChooseVolumeDialog.
- The Choose a File dialog is created by a call to NavCreateChooseFileDialog, and may be used when you want the user to select a file for a purpose other than opening. The file could be, for example, a preferences file or a dictionary file.
- The Choose a File Object dialog is created by a call to NavCreateChooseObjectDialog, and may be used when you need the user to select an object that might be one of several different types.
Creating and Displaying Primary Alerts
Save Changes Alert
The Save Changes alert (see Fig 12) is created by a call to NavCreateAskSaveChangesDialog and displayed by a call to NavDialogRun. You pass a universal procedure pointer to an application-defined event handling function in the inEventProc parameter of NavCreateAskSaveChangesDialog and a pointer to a NavDialogCreationOptions structure in the inOptions parameter.
One of the following constants is passed in the inAction parameter of the NavCreateAskSaveChangesDialog function:
kNavSaveChangesClosingDocument = 1
kNavSaveChangesQuittingApplication = 2
kNavSaveChangesOther = 0
Discard Changes Alert
To support the Revert command in your application's File menu, Navigation Services provides the Discard Changes alert. The Discard Changes alert (see Fig 13) is created by a call to NavCreateAskDiscardChangesDialog and displayed by a call to NavDialogRun. You pass a universal procedure pointer to an application-defined event handling function in the inEventProc parameter of NavCreateAskDiscardChangesDialog and a pointer to a NavDialogCreationOptions structure in the inOptions parameter.
Review Changes Alert - Mac OS X
On Mac OS X, when the user attempts to quit your application when there is more than one document with unsaved changes open, your application should present a Review Changes alert (see Fig 14). No Navigation Services creation function existed at the time of writing; accordingly, at the time of writing, it was necessary to create, display, and handle this alert using StandardAlert or CreateStandardAlert.
A click on the Discard Changes button should cause all windows to close (without saving changes) and the application to close down. A click on the Cancel button should cancel the Quit command, keeping the application running. A click on the Review Changes... button should cause each window with unsaved changes to be sequentially presented to the user with a Save Changes alert presented.
Event Handling in the Primary Dialogs
Event-Handling Function
As previously stated, you pass a universal procedure pointer to an application-defined event handling (callback) function in the inEventProc parameter of those functions which create the Navigation Services primary dialogs. For an event handling function named myNavEventFunction, you would declare the function as follows:
void myNavEventCallback(NavEventCallbackMessage callBackSelector,
NavCBRecPtr callBackParms,NavCallBackUserData callBackUD)
callBackSelector |
The type of event, as represented by an event message constant. Typical event message constants and their meanings are as follows:
Constant |
Value |
Description |
kNavCBUserAction |
12 |
The user has taken an action such as clicking on an Open or Save button. |
kNavCBEvent |
0 |
An event has occurred. Receipt of this event type allows your handler to update other windows, track controls, etc. |
kNavCBTerminate |
3 |
The dialog is about to be closed. |
|
callBackParms |
A pointer to a NavCBRec structure, which contains data used by your application to process the event:
struct NavCBRec
{
UInt16 version;
NavDialogRef context;
WindowRef window;
Rect customRect;
Rect previewRect;
NavEventData eventData;
NavUserAction userAction;
char reserved[218];
};
typedef struct NavCBRec NavCBRec;
typedef NavCBRec *NavCBRecPtr;
|
callBackUD |
A pointer to a value passed in the inClientData parameter of the dialog creation functions. |
kNavCBUserAction Message Received
When the kNavCBUserAction message is received, your application typically calls NavDialogGetReply to obtain the results of the dialog session, which are returned in a NavReplyRecord structure.
The NavReplyRecord Structure
struct NavReplyRecord
{
UInt16 version;
Boolean validRecord;
Boolean replacing;
Boolean isStationery;
Boolean translationNeeded;
AEDescList selection;
ScriptCode keyScript;
FileTranslationSpecArrayHandle fileTranslation;
UInt32 reserved1;
CFStringRef saveFileName;
char reserved[227];
};
typedef struct NavReplyRecord NavReplyRecord;
Field Descriptions
validRecord |
true if the user closed a dialog by clicking the Open button in an Open dialog, the Save button in a Save Location dialog, or the Choose button in a Choose dialog, or by pressing the Return or Enter key. If this field is false, the remaining fields do not contain valid data. |
replacing |
true if the user chooses to save a file by replacing an existing file.
|
isStationery |
true if the file about to be saved should be saved as a stationery document. |
translationNeeded |
true if translation was or will be needed for selected files in Open and Save Location dialogs. |
selection |
An Apple event descriptor list (AEDescList) created from FSSpec or FSRef references to selected items. You can determine the number of items in the list by calling AECountItems. Each selected item is described in an AEDesc structure by the descriptor type typeFSS or typeFSRef. This descriptor can be coerced into an FSSpec or FSRef preparatory to, for example, opening the file. |
keyScript |
Keyboard script system used for the filename. |
fileTranslation |
Handle to a FileTranslationSpec structure, which contains a corresponding translation array for each file reference returned in the selection field. |
saveFileName |
The save file name in CFString form.
This field was introduced with Navigation Services 3.0 because there is no way to fit a 255-character Unicode name into the name field of an FSSpec or into an FSRef. (See selection field.)
Note 1: On Mac OS 9, you will never get a file name that won't fit into the name field of an FSSpec structure.
Note 2: On Mac OS X, you cannot reliably convert the name in the saveFileName field to a 31-byte Pascal string.
|
When your application has finished using this structure, it should dispose of it by calling the function NavDisposeReply.
Responding to User Actions
If the validRecord field of the NavReplyRecord structure contains true, your application typically calls NavDialogGetUserAction to determine the user action, as represented by user action constants. Typical user action constants are as follows:
|