Introduction

OpenStep can provide your application a document-based model. It helps to simplify the process of creating such applications. GNUstep is compatible with this paridgm as well; this document aims to provide an overview of what is required in the creation and design of such an application.

It is assumed you are familiar with GNUstep, and have some understanding as how to create Gorm's nib files, as well as some understanding of how they work.

Importantly, such applications have certain components that are required, and should be considered as part of their design. At a programmatic level, classes that will be used as part of the AppKit are:

NSDocument

This class is central to the OpenStep document based model. Such an application declares a new instance of a subclass of NSDocument for each “document” that is opened. It will be necessary to subclass NSDocument to implement this.

Each instance of your NSDocument subclass may have one or more windows associated with it. These are used as an interface for loading and saving a representation of your document type. Each window will have an “NSWindowController” instance associated with it, where NSDocument maintains a list of these.

NSDocumentController

This class acts a controller. It maintains a list of documents (instances of your NSDocument subclass) and is responsible for loading and instantiating new documents. It is usually not necessary to implement a subclass of this, but often it may be useful to implement special functionality (especially related to open documents as a whole).

NSWindowController

As mentioned above, NSWindowController instances are individually associated with one NSWindow instance, in such a way that a document maintains a list of window controllers that are reponsible for rendering the views associated with a document.

You will most likely not have to subclass NSWindowController. Instead, you associate a nib file (the Gorm output) with the subclass of NSDocument, and NSDocumentController will load this nib file for each document that is created or loaded.

For example, a text based application that is used for the editing of plain text files, may subclass NSDocument with a TextDocument class. Each instance of this class would have an NSWindowController instance associated with it, which in turn manages a window instance that is instantiated from a nib file. The NSDocumentController instance, as managed and instatiated by GNUstep, would be responsible for loading and saving documents in your application. It even prompts the user to save their documents when they try to quit the application.

File Components

In building your application you will need to create a number of special files. From scratch, the following should be a rough guide to getting it working. These include the Makefile, your application's property list and the interface files.

The Makefile

For this type of application, no special makefile is needed: it just has to be a normal application. Project Builder should be able to spit out the required makefile and Gorm files that are needed for a generic application. I believe it is also capable of providing support for document-based applications. Otherwise use the following as a template:

For more information as to customising this file, as well as setting up compiler options for third party libraries and includes, see the GNUstep makefile manual.

The application dictionary

For those that have edited this file in their project before, it is a dictionary with various values entered. For our purposes, we need an array within the main dictionary, called "NSTypes". This is a array of unnamed dictionaries, with one dictionary for each type.

An entire example of this file, for a "theoretical" TextEdit application (please note that such an application does exist as an example application from Next I think, and again on MacOS X but I have constructed a much simpler version, used only for plain text files).

Taking a look at the single dictionary entry in the NSTypes array, the following key-value pairs are needed:

NSDocumentClass: This is (string) the subclass name you use in your code, the "objective-c name" of your document subclass. This is used by NSDocumentController's default implementation to create instances of your document type.

NSName: The generic file type (string). This is completely arbitrary, and can be anything you like. For the purpose of conventions, it may be more appropriate not to use the "NS" or "GS" prefix (the latter of which I not sure), and instead use either your own, or no prefix at all.

NSHumanReadableName: The human readable name of your document type (string)? TODO: explain where this is used.

NSUnixExtensions: An array of strings, each containing a file extension connected to this document type on "unix" platforms. TODO: explain how "unix" is interpreted by GNUstep.

NSDOSExtensions: An array of strings, each containing a file extenstion connected to this document type on "DOS" platforms (could be loosely interpreted as any MS platforms GNUstep runs on, i.e. Windows 2000/XP).

NSRole: A string, either "Viewer" or "Editor", depending on how your document operates on this file type.

NSIcon: A icon name associated with this document type. TODO: Investigate how this works.

Interface files

Two interface files are needed: one for your main application, and another that will be instantiated with each instance of your document subclass. These both should be listed as resources in your makefile, (as shown the GNUmakefile example above). It may be helpful to name the document nib file after your class.

Application Interface File

The application one should just be a menu (though it could be more if necessary). When constructing it, take the following into account:

Use a standard Gorm template for an Application.

Delete the window instance in the "Objects" pane (unless you need a main window, in which case I don't know how to operate).

Set NSOwner's class to "NSDocumentController" (this is the custom class option of NSOwner in the property inspector). This ensures openDocument, newDocument, etc. messages, as sent to NSFirst, get picked up by NSDocumentController. Do not instantiate it (I believe you can't anyway).

If you intend to subclass NSDocumentController, subclass it in the Classes pane. To make sure that this class is instantiated and used, make sure you instantiate it in Gorm or in your main.m file, and set NSOwner to be your subclass.

Drag a Document menu from the palette onto your main menu. This already has the proper linkage for each relevant message for a document setup to be directed to NSFirst. In the case of generic messages (such as openDocument:, not associated with any document instance), these are forwarded to the shared instance of NSDocumentController (see it's reference documentation). When a document is active, more specific messages (such as close: or saveDocument:) are forwarded to the relevant document instance of your NSDocument subclass.

Add any appropriate menus such as "Info" and "Format" which may be relevant to your application.

Document Interface File(s)

The document interface file is (generically) a window containing the view(s) required for the implementation of one instance of your document type. For example, in the case of a TextDocument type, a window instance with a NSTextView drawn on it is used for each open window. This nib file is instantiated every time a person creates a new document (the newDocument: message goes to NSDocumentController) or opens an existing document (openDocument:).

You will need to:

Subclass NSDocument with your document type in Gorm (this is done in a couple of ways, see custom classes).

Set NSOwner: Set your subclass as NSOwner's Custom class (done using the Property inspector). DO NOT INSTANTIATE IT.

Another thing that you may want to consider is custom outlets. If you place certain views or controls on your window (including the window itself), you may want to refer to them directly in yout code, e.g. the NSTextView in our TextEdit example is accessed directly, so that we can save it's contents to a file. To enable this, we add outlets to our class (see custom classes). We then connected NSOwner to the appropriate controls/views, and selected the outlets in the Connections property inspector (see editing the interface).

Code

Various things need to be done at a code level, to ensure that your NSDocument subclass is properly instantiated. This section aims to tell you what is necessary.

NSDocument subclass

When creating your subclass, you may have chosen to have Gorm create the class files. If not, you should create two files for your subclass: a header (.h) file and an implementation file (.m). The header file should take the following layout (we're using the TextEdit example again):

Firstly, take note that we inherit from NSDocument. As a result, we need to override some methods for a basic (but working) implementation.

loadDataRepresentation:ofType: and dataRepresentationOfType:

The methods, loadDataRepresent:ofType: and dataRepresentationOfType: are used by NSDocumentController to load and save our files. The first method is called when the user trys to open a document (NSDocumentController gets the file name and copies it's data using an open panel). The method is passed the raw data in this file, as well as a string representing its type. It is important to note that this type string is the same one used in your property list. It is used by NSDocumentController to tell you what type it thinks you should open the data with. If this is not possible, or if there is an error trying to parse the data, your method returns FALSE/NO. Otherwise, you should store a local, meaningful representation of the data, which you can insert into your document window a little later (and return YES).

If this is not suffice, or if your program can open the data in a more efficient manner, it may be worth looking into overriding -initWithContentsOfFile:ofType: and -initWithContentsOfURL:ofType: (see the GUI manual for more details).

dataRepresentationOfType: is used to save the data. Your method is expected to return an instance of NSData that contains what should be saved to a file, for the type specified. Again, the type variable should be checked to ensure that you can write data of that type. Return nil if the file cannot be saved as that type.

If a more sophisticated save implementation is needed, consult the documentation for information on the writeTo methods.

windowNibName

This method must be overriden to return the a string of the name of your nib file (without the file extention). More complex implementations will use other methods to load the interface files. TODO: information on what methods need to be overriden.

windowControllerDidLoadNib:

Use this method to add code that should be run as soon as windowController has been initialised. Your document may have many window controllers; each window controller taking possession of one window.

In the TextEdit example, this method is used to copy the fileContents object ivar to the text view. It otherwise initialises the document window.

Also note that you can override windowControllerWillLoadNib: to perform pre-nib loading initialisation.

makeWindowControllers

This is an optional method override, which must be used if you have multiple window controllers per document. It is responsible for creating each window controller from it's nib and then adding it to the document. Use the addWindowController: method.

Other considerations

You need a way to notify NSDocument that your document has changed, so that NSDocumentController can enable the menu item to allow saving. This is done by a call to the updateChangeCount: method. For example, the TextDocument class adds itself as a delegate to the textView, for any notification of changes. It then calls updateChangeCount: in the delegate's method.

You may also wish to add a close: method (not the close method already in NSDocument, see below) to respond to the menu item permitting your document to close. This could perform cleanup operations before a call to [super close] is called e.g.

- (void) close:(id)sender /* Note this is not the method found in NSDocument (they differ by a parameter) */
{
[self close];
}

NSDocumentController

This class can optionally be subclassed, to implement some specific functionality. For example, programs handling multiple document types may override newDocument: to create a window so that the user can select which document type to create.

You should use separate implementation files and add the .m file to GNUmakefile.

NSWindowController

This class can also be optionally subclassed, to implement functionality specific to a window controller for a document type. You may need to override makeWindowControllers: in the NSDocument subclass in order to make sure your specific window controller is instantiated.

You should use separate implemenatino files and add the .m file to GNUmakefile.