Drawing with Cappuccino (part 2)

In the first tutorial about drawing with cappuccino, we learned the different options we have at our disposal (using or not CALayer). But all drawing was static. In the tutorial about how to port the DotView example from cocoa to cappuccino, we have started to add some user interaction. The circle was draw at the mouse click since we implemented: -mouseUp:(CPEvent *)event. In order to increase the user interaction, we decided to check if we could port the Sketch example. This will involve reponding to mouse click and drag, determining modifiers (shift click). We will also support keyboard events (the graphics can be moved with the arrows on the keyboard).

Implementing a CPDocument application

Classes involved in a CPDocument application

Since we wanted to port Sketch, we had also to implement the few classes involved in a CPDocument application. A cappuccino application has only one instance of CPDocumentController: [CPDocumentController sharedDocumentController]. This shared instance hold references to all opened CPDocument subclasses: -(CPArray)documents. Each CPDocument subclass manage an array of window controllers: -(CPArray)windowControllers. An array is required since a document may have more than one window displaying it's content (for example a 3D application can show a front, left and top view and a 2D application can display it's content at different zoom pourcent or different location). Each CPWindowController subclass manage one CPWindow. Well it is not complicated but we will need this information later when will will have to access the document data to display it in a CPView on a CPWindow.

So we have to create a document with cappuccino. We just have to call -(IBAction)newDocument:(id)aSender. We could also use -(void)openUntitledDocumentOfType:(CPString)aType display:(BOOL)shouldDisplay if our application manage different types of document.

-(IBAction)createDocument:(id)sender { var sharedDocumentController = [CPDocumentController sharedDocumentController]; var documents = [sharedDocumentController documents]; var defaultType = [sharedDocumentController defaultType]; [sharedDocumentController newDocument:self]; // [sharedDocumentController openUntitledDocumentOfType:@"MyBundle" display:YES]; }

Modifying "Info.plist" for a CPDocument application

So the shared document controller has to determine the default document type: -(CPString)defaultType. It is the first entry in the array _documentTypes.

- (CPString)defaultType { return [_documentTypes[0] objectForKey:@"CPBundleTypeName"]; }

By reading the CPDocumentController code, we can figure out the format of the "Info.plist" file. The _documentTypes is filled with the array for key CPBundleDocumentTypes.

You already knows that the key "CPApplicationDelegateClass" is used to determine which class to instanciate as the application delegate. This is done by the CPApplicationMain function in main.j and explains why you MUST include the AppController definition in the same file (with a @import "AppController.j"). If you fail to do it, Objective-J will fail to create the AppController instance.

It is exactly the same for newDocument: or openUntitledDocumentOfType:display:, you MUST import the definition of "PLDocument" (@import "PLDocument.j") in the same file. Well I had to trace my bug and I really hope to save your time : don't forget these two important import statements.

<plist version="1.0"> <dict> <key>CPApplicationDelegateClass</key> <string>AppController</string> <key>CPBundleName</key> <string>Tutorial-Drawing-part2</string> <key>CPPrincipalClass</key> <string>CPApplication</string> <key>CPBundleDocumentTypes</key> <array> <dict> <key>CPBundleTypeName</key> <string>MyBundle</string> <key>CFBundleTypeExtensions</key> <array> <string>pll</string> </array> <key>CPDocumentClass</key> <string>PLDocument</string> </dict> </array> </dict> </plist>

Implementing our CPDocument subclass: PLDocument

PLDocument is where the data model lives since multiple windows could display it. For now, I don't have implemented the storage/retrieval of data but you could do it yourself by implementing these methods:

- (CPData)dataOfType:(CPString)aType error:({CPError})anError - (void)readFromData:(CPData)aData ofType:(CPString)aType error:(CPError)anError

The only method you really have to implement is makeWindowControllers. This is where you decide how many window controllers you want per document. Since we have a one to one correspondance between window and window controller, this is the amount of window you want to create per document. As you can see, we only create one instance of PLWindowController.

- (void)makeWindowControllers { // debugger; var controller = [[PLWindowController alloc] init]; [self addWindowController:controller]; }

Implementing our CPWindowController subclass: PLWindowController

Coming soon

Well enough code for document support ! If you click on the "Create Document" button, you will be able to create multiple documents and see the classes at work.

Adding keyboard support

Coming soon

Adding mouse support

Coming soon

Download tutorial code

If you'd like to see the complete code listing from the tutorial, you can download it all in a single file: Tutorial-Drawing.zip. The web application is available online: Tutorial Drawing (part 2).

Copyright © 2009 - Philippe Laval. Cappuccino and Objective-J are registered Trademarks of 280 North.