This page contains tips and tricks I've found while developing Cocoa Browser.
Contents:
Through Cocoa Browser is not a normal document-based application, I've chosen it as the template to be able to open multiple windows.
As a result, Cocoa Browser does nothing on "Open...", "Save", etc. I'm sorry for this confusion.
Layouting NSTextView to the same size as the window, scrollbar's position doesn't align with the size box. To fix this, enlarge the frame rectangle to left, right, and bottom by 1 pixel each.
But in Cocoa Browser, NSTextView resides in NSSplitView which doesn't allow subviews to have arbitrary frame rectangle. Adding a dummy NSView between them solves the problem.
To display the contents of NSBrowser, add the following two methods.
- (int) browser: (NSBrowser *) sender numberOfRowsInColumn: (int) column;
- (void) browser: (NSBrowser *) sender willDisplayCell: (id) cell atRow: (int) row column: (int) column;
To do something when NSBrowser's is clicked, add an action method and set the target and action. (Also, to do something different when double clicked, call - setDoubleAction: in - awakeFromNil. It can't be done with Interface Builder...)
Notice the order of these calls. NSBrowser updates the cells before calling the action method, therefore, - browser:numberOfRowsInColumn: etc. are called before the action method.
It's very easy to display HTML document in NSTextView.
NSData *data = < HTML data >;
NSURL *url = < base URL >;
NSAttributedString *attrString = [[NSAttributedString alloc]
initWithHTML: data baseURL: url documentAttributes: (NSDictionary **) NULL];
[[textView textStorage] setAttributedString: attrString];
[attrString release];
NSTextView also displays the inline images. (Base URL is used to resolve the location of the images specified with relative paths.)
HTML data need not to be a full HTML, i.e., Cocoa Browser splits the original HTML document into small pieces and display one.
You don't need to set CVSROOT environment variable. See cvs -d. It would be useful especially when you have several repositories.
Put cvswrappers into CVSROOT/cvswrappers in the repository, instead of ~/.cvswrappers, to have different setting for each repository. Apple provides a sample in /Developer/Tools/cvswrappers, which enables .nib files to be managed with Project Builder.
I've modified it to manage .pbproj files, but it can't be diffed, and managed with Project Builder. Have I made a mistake?
Correction: I should have added "project.pbxproj" file in the .pbproj directory. It can be diffed (in Terminal), and my user preferences are ignored.
When you click on a link, NSTextView sends - textView:clickOnLink:atIndex: message to its delegate. In Cocoa Browser, internal links are processed and it return YES. For external links, it returns NO and Application Kit performs the default action -- tell the default web browser to open the link.
(I don't know how to change the mouse cursor when it's on a link. By the way, should internal and external links to be displayed in different colors?)
To add menu items in multiple .nib, add methods to the first responder class, and connect to its instance.
To enable/disable menu items according to the situation, implement - validateMenuItem: method.
How to unselect cells in NSBrowser? - selectRow:inColumn: doesn't work.
I've found that emptying NSBrowser once and reverting seems to work. i.e., add a flag, modify - browser:numberOfRowsInColumn: to retun 0 if the flag is not normal, and
< flag = not normal >;
[browser reloadColumn: 0];
< flag = normal >;
[browser reloadColumn: 0];
Correction: Simply sending - loadColumnZero does the trick.
Some people said "fonts are too small." Yes, I know it's small. But it's NSAttributedString's default behaviour. Two ideas came to mind: adding <font size="+1"> tag, and sending - modifyFont: with NSSizeUpFontAction to NSFontManager. Both seems to be troublesome, and neither is perfect.
Finally, I've found that NSView and any of its subclass can be scaled by - setBoundsSize: method. But, NSRunStorage throws exception when NSTextView's contents are changed. To fix this, add
[textView setString: @""];
before
[[textView textStorage] setAttributedString: attrString];
(Scroll position is changed by scaling. To fix this, - characterIndexForPoint: and - scrollRangeToVisible: seems to work. However, I don't know where to pass - characterIndexForPoint:. By the way, should NSBrowser's font be scaled?)
Cocoa Browser is open source. To compile, extract source files from src.tar.gz, open Cocoa Browser.pbproj, and compile.
Cocoa Browser ver 0.2 has a separate application to browse the Java
version of the reference document. To compile as the Java version,
delete #define LANGUAGE_OBJECTIVE_C from ClassNode.m.
Deployment binary was huge! I've found a solution in Witness of Teachtext: Tips & Tricks.
Max Horn taught me how to accept key events in NSBrowser; add the following method to the delegate.
- (BOOL) _browser: (NSBrowser *) sender
keyEvent: (NSEvent *) event inColumn: (int) column;
Because this is an undocumented API, it may not work on future versions of Mac OS X.
To update NSTextField while dragging NSSlider, connect them and set the action to - takeIntValueFrom:. Easy.
However, sometimes NSTextField fails to update. To improve this situation, send message to MyDocument and check if the value actually changed. Though it improves a bit, problem still remains...
In Mac OS X Dev-jp Mailing List (in Japanese), Mr. Ishikawa (in Japanese) taught me how to change link's cursor.
Using that method, I also changed the color of external links, and preserve scroll position on Magnify and Go Back and Forward.
To implement it, I created a subclass of NSTextView. But, Interface Builder doesn't allow me to specify the subclass. I don't like to put a CustomView and set the subclass, as I want to set the properties in Interface Builder, not by source code.
So, I added a method to replace NSTextView with the subclass and copy the properties, based on Aaron Hillegass' excellent book Cocoa Programming for Mac OS X.
I've followed /Developer/Examples/AppKit/SimpleToolbar to add the toolbar. (Most difficult task was, painting arrow icons!)
By the way, I don't like the way of implementation and API design of Toolbar in Cocoa. Do you?
Addendum: I'm very glad that I can have small icons in Toolbar on Mac OS X 10.2.
When I tried to remove '????' from document types, "New" and "Open..." stop working, and no window appears on launch.
To fix this, add delegate to NSApp and subclass of NSDocumentController.
I already use - setFrameAutosaveName: to preserve the window position of Find dialog, but it doesn't work for main windows because it works for single window only.
I also want to preserve the magnification and text contents for each window.
So, I generate an array of dictionaries of window status and save into user defaults. I use - stringWithSavedFrame and - setFrameFromString: methods to save and restore window position.
I separate MagnifyWindow.nib from MyDocument.nib to make .nib files simple and lower memory usage.
I also separate MagnifyController class to simplify MyDocument class a bit.
But, MyDocument class is still crowded. How can I improve it?
Node and ClassNode classes have ad-hoc design, and lacks expandability.
So I split ClassNode class into NodeWithFile and NodeParser classes. It will ease to support more documents, I hope.
Cocoa Browser had some problems on Mac OS X 10.2 (Jaguar).
First, a relative link stop working, even though I set the baseURL.
Second, "By" in "Methods Implemented By the Delegate" has been changed to "by", breaking "Delegate Methods". Thanks to Andy Lee (author of AppKiDo) for telling me about this.