Apr 00 Getting Started
Volume Number: 16
Issue Number: 4
Column Tag: Getting Started
Opening a File
by Dan Parks Sydow
How a program opens a file and displays that file's
contents in a window
In past Getting Started articles we've had occasion to have our example programs open
files. For instance, back in February of 1999 the SoundPlayer program opened a sound
file, and a few months back, in December of 1999, our MoreQT program opened two
QuickTime movie files. In both those examples our programs relied on the desired files
being in the same folder as the application, and we had the applications take control of
opening those files. That's not always the way it works in the real world. Your
user-friendly program might offer the user the opportunity to select the file to open.
This month you see how to write a program that does just that. In particular we'll
examine how a program opens a text file and displays that file's text in a window. Many
of the techniques you read about here apply to opening other types of files - such as
sound, picture, and movie files - as well.
Windows and File Data
This article's example program opens a window that displays the text that's stored in a
text file. In doing that the program opens a text file and reads the file's text to memory.
The program keeps a handle to the data in memory, and associates that handle with one
particular window. While the example program doesn't allow for the opening of a
second window, we want to establish the foundation for a program that does. And if a
program is capable of opening two, three, or thirty windows each displaying the text
from a different file, we certainly want to make sure that the program displays the
proper text in the proper window. This is true for a program that opens text files, but
it's also true for any program that displays the contents of files in widows.
One such scheme for associating a file's contents with a particular window is to employ
the use of a document record data type. This application-defined data type holds
information about any one of a program's windows. While such a data type can include
any number of fields, in our simple example one suffices:
typedef struct
{
TEHandle windText;

} WindData, *WindDataPtr, **WindDataHandle;
As you'll see ahead, a TEHandle is an Apple-defined data type that serves as a handle to
editable text (with the TE standing for TextEdit). So our structure of type WindData
exists to keep track of a single handle that references a block of memory that holds the
text copied from a file. The above snippet also defines a pointer to such a structure (a
WindDataPtr) and a handle to such a structure (a WindDataHandle). To create a new
WindData structure and return a handle to it, make use of the Toolbox function
NewHandleClear().
WindData theData;
theData = (WindDataHandle)NewHandleClear( sizeof(WindData) );
Our WindData structure is initially empty - we'll soon remedy that by filling its one
field (the windText TEHandle field) with a reference to some text. Before doing that,
let's open a window:
WindowPtr theWindow;
theWindow = GetNewWindow( 128, nil, (WindowPtr)-1L );
At this point we have memory space reserved for a WindData structure, and we have an
open window - but we don't have a connection between the two. That is, there is no
association between the theData data structure and the theWindow window. To do that
we make a call to the Toolbox function SetWRefCon().
SetWRefCon( theWindow, (long)theData );
A WindowPtr is a pointer to a WindowRecord. One of the fields of a WindowRecord is
refCon - a long that can be used to hold any four bytes of data. Programmers often use
the refCon field of a window as a reference to application-defined data that pertains to
that window. That's what we're doing here. Four bytes doesn't sound like much room to
store data, and it's not. So these four bytes are instead used to hold a pointer (or a
handle) that lead to a memory block of any size - and it's in this memory block that the
application-defined window information is stored. As you ponder this concept, keep in
mind two memory-related issues. First, a handle is a pointer to a pointer. That is, a
handle is a type of pointer. Second, a pointer is held in four bytes of memory. The
above call to SetWRefCon() accepts a WindowPtr as its first argument and any long as
its second argument. By storing a reference to application-data in the second argument
we wed the WindData structure to a window.
Opening a File
To this point we've created an empty data structure and associated it with a window.
But we haven't opened an existing text file, brought that file's text into memory, and
then created a tie between the text in memory and a window. We'll open the file now.
Your program can display the standard open file dialog box to give the user the ability
to open any existing text file. A call to the Toolbox function StandardGetFilePreview()
does the trick:
SFTypeList typeList = { 'TEXT', 0, 0, 0 };
StandardFileReply reply;
StandardGetFilePreview( nil, 1, typeList, &reply );
Before discussing the particulars of working with StandardGetFilePreview(), a quick
Mac OS X note is in order. You may be familiar with the Toolbox function
StandardGetFile(). That routine has a parameter list that's identical to the list for
StandardGetFilePreview(). And both functions perform essentially the same task -
they each display a standard open dialog box. The difference is that
StandardGetFilePreview() is capable of displaying a small view of a part of a selected
file. For a selected QuickTime movie, for instance, the dialog box will show a
thumbnail image of the first frame of the movie. For our text file-opening example the
dialog box will display the first several words in the selected text file (see Figure 3).
Here's where Mac OS X comes into play. If you don't consider the display of a preview
of the first bit of a text file to be of particular importance to the user, then you might
consider using StandardGetFile() rather than StandardGetFilePreview(). Before
making that move, consider this. StandardGetFile() won't be supported as a Carbon
function for Mac OS X development, while StandardGetFilePreview() will be
supported. So you'd be wise to use StandardGetFilePreview().
The first three arguments to StandardGetFilePreview() tell the File Manager which
types of files to display in the dialog box. The open dialog box can readily display all
types of files, or just files of up to four different types (such as text files, picture
files, movie files, and so forth). If your application is capable of opening more than
four different types, and you want the open dialog box to display files of each of these
types (but not files of all types), you'll need to pass a pointer to a filter function as
the first argument. This application-defined filter function will then serve as the
means of displaying the appropriate files. If your program is capable of opening four
or fewer types of files, then pass nil as the first argument to indicate that no filter
function is used.
In the second argument to StandardGetFilePreview() pass the number of file types to
be displayed. We'll pass 1 since it's assumed our program only works with one type of
file - text files).
The third argument is of type SFTypeList. The SFTypeList variable is a list that
specifies the four-character file type (surrounded in single quotes) of each of the
types of files your program can work with. Fill the SFTypeList variable upon
declaration, as shown above. A text file has a file type of 'TEXT', so that serves as one of
the four elements in the above list. Fill the list with four values, using a 0 for each
unused file type. Assuming a program is to only display text files in the open dialog
box, there'll be three 0's in the list. Other common file types are 'PICT' for a picture
file and 'moov' for a QuickTime movie file.
The last argument is a pointer to a variable of type StandardFileReply. When the user
clicks on the Open or Cancel button in the standard open file dialog box, the
StandardGetFilePreview() fills the members of the StandardFileReply structure.
You'll be interested in two of the many members of this structure: sfGood and sfFile.
By examining the value of sfGood your program can determine if the user clicked on
the Open button (sfGood will have a value of true) or the Cancel button (sfGood will
have a value of false).
if ( reply.sfGood == false )
// handle canceling of file opening
else
// handle opening of selected file
If the sfGood field has a value of true, your program should go on to open the selected
file. An FSSpec - a file system specification - for that file is held in the sfFile field of
the StandardFileReply variable filled in by StandardGetFilePreview(). Pass a pointer
to this FSSpec as the first argument to the Toolbox function FSpOpenDF().
short fileRefNum;
FSpOpenDF( &reply.sfFile, fsRdPerm, &fileRefNum );
The FSpOpenDF() function finds the requested file and opens its data fork. If a
permission level of the Apple-defined constant fsRdPerm is specified, the application
will only be able to read the file (a value of fsRdWrPerm can be used if your program
needs to also be able to alter the file's contents). After opening the file, FSpOpenDF()
returns a file reference number. It is this number that your application can then use
to reference the file.
Bringing a File's Text Into Memory
With a file open, it's time to bring that file's contents into memory. The Toolbox
function FSRead() will take care of that, but first we need to do a little preparatory
work:
long fileLength;
Ptr textBuffer;
GetEOF( fileRefNum, &fileLength );
SetFPos( fileRefNum, fsFromStart, 0 );
textBuffer = NewPtr( fileLength );
GetEOF() gets the size in bytes of the contents of a file. Here we use the file reference
number returned by FSpOpenDF() to tell GetEOF() which file we're interested in.
SetFPos() moves the file mark - the position marker used to keep track of the current
position to read from or write to - to a particular byte location in a file. The
Apple-defined constant fsFromStart tells SetFPos() to count from the start of the file,
while the third argument indicates how many bytes to move the file mark. Here we're
moving the file mark to the very start of the file (start counting from the start of the
file, fsFromStart, and then move the mark 0 bytes). The ability to move the mark isn't
important to our opening a text file, but it is important in some other cases. For
instance, a file of some file types (such as a file of type 'PICT') includes several bytes
of header information that is unrelated to the main data in the file. When opening a
picture file a program will set the file mark past this header information before
reading the file's contents.
GetEOF() returned the number of bytes in the open file, and we use that value when
creating a new buffer (a block of memory) in which we'll store the file's contents. The
call to NewPtr() returns a pointer to the memory block. Then, it's time to read the
file's contents, storing the file data in the block of memory referenced by the
textBuffer pointer.
FSRead( fileRefNum, &fileLength, textBuffer );
The call to FSRead() reads in the text from the text file with the reference number
fileRefNum. The data has now been stored in memory, but we want our program to be
able to easily work with the text. To do that, create a text edit record. A call to
TENew() creates such a record. Before creating the text edit record, set up two
rectangles. The destination rectangle is the area in which text is drawn. The view
rectangle is the area in which text is displayed. While the boundaries of these two
rectangles are often the same, they don't have to be. If the view rectangle is inset from
the destination rectangle, then when it comes time to display the edit record text in a
window, some text will be clipped. Figure 1 shows a view rectangle that's smaller than
a destination rectangle. Figure 2 shows a window displaying text of a text edit record
that uses the rectangles from Figure 1.
Rect destRect;
Rect viewRect;
TEHandle textHandle;
SetRect( &destRect, 10, 10, 410, 270 );
viewRect = destRect;
textHandle = TENew( &destRect, &viewRect );
Figure 1.A view rectangle that's smaller than the destination rectangle.
Figure 2.Displaying text from a text edit record that uses the rectangles from Figure
1.
The above snippet creates a destination rectangle 400 pixels across and 260 pixels in
height. We'll soon be placing this rectangle snuggly in a window, so giving the
destination rectangle a left boundary and top boundary of 10 means that there'll be
small left and top margins (as opposed to the text touching the left and top of the
window in which it's displayed). The view rectangle is set to the same coordinates as
the destination rectangle, and these two Rects are used in the call to TENew().
TENew() creates a new empty text edit record. A call to TESetText() fills the new
record with text.
HLock( (Handle)textHandle );
TESetText( textBuffer, fileLength, textHandle );
HUnlock( (Handle)textHandle );
The call to TESetText() moves the text from the buffer referenced by the textBuffer
pointer to the memory referenced by the TEHandle textHandle. Keeping in mind that
during routine memory management the system can move memory referenced by a
handle, but can't move memory referenced by a pointer, we play it safe and lock the
handle. Doing that prevents the system from moving the memory referenced by
textHandle while TESetText() is copying text from one area of memory to another.
Associating a File's Contents With a Window
A file is open and it's contents are held in a block of memory that's referenced by a
handle (the textHandle variable). And an application-defined data structure (a
WindData structure) that includes a handle as its one field (the windText field), and
that is associated with a window (theWindow), is also held in memory. If we now
create a tie between the handle that references the file's contents in memory
(textHandle) and the handle that is a part of the application-defined data structure in
memory (windText), we in effect associate the file contents with the window.
(**theData).windText = textHandle;
The above line dereferences the handle twice to allow access to the structure member
windText. The windText member is of type TEHandle, as is the textHandle variable - so
we can simply give one the value of the other. After the above code executes windText
references the same block of memory as textHandle.
At this point our program now has a simple means of accessing the additional data
associated with a window. All we need to do is follow the path from the window's refCon
field to the WindData structure. To store a handle in the refCon field we called the
Toolbox function SetWRefCon(). To retrieve that same handle we now call the Toolbox
function GetWRefCon().
long windRefCon;
windRefCon = GetWRefCon( theWindow );
The variable windRefCon now holds a value that is a handle to the window's WindData
structure. The refCon field held this value as a long, whereas WindData is of our
application-defined type WindDataHandle. So we need to typecast it to our specific type
in order to make use of it.
WindDataHandle theData;
theData = (WindDataHandle)windRefCon;
Double-dereferencing the handle makes the field of the structure accessible. If we
want to access the text edit record we've associated with the window, we can assign the
structure's windText field to a TEHandle variable.