Polishing an App 2 - Secrets Not In The Manual

Applescript Studio’s ‘Secret Handshake’
In any group, be it Masons or Girl Scouts, there are always secrets within that group, things that are known only to the group and not to the outside world. (With Girl Scouts it’s what is in the cookies, I think.) Among Applescript Studio coders there are also some arcane secrets, bits of information and tricks that get passed from one to another of us (usually through the Macscripter BBS). Over the last year I’ve found out some of these bits that might make your next application (or the one you’re working on now) a bit slicker.

Undocumented Documents
While XCode has an option for creating a document-based Applescript Studio application, the actual process of setting up a document type isn’t really covered in the Applescript Studio Programming Guide or the Applescript Studio Terminology Reference - The Terminology Reference tells you how to use the data representation, load data representation, read from file, and write to file handlers to save and open documents but doesn’t give you more than the barest of information to get by.

Creating a document-based application is incredibly easy, once you know the secrets. First of all create an Applescript Document-based Application project in XCode. In the “Groups and Files” pane of XCode’s window you’ll find a Document.nib in the Resources folder, right next to the MainMenu.nib you’re used to. That document nib is used to create a window for the document class that XCode created for you. In fact, there is already a basic window in the nib file, you just need to outfit it with the kind of data container(s) you need. For example, if you were making a program that handled text files, like a text editor, you would create a big text view in the window that takes up most (if not all) of the window.

OK, most of us figured that part out fairly easily. But how do you tell Applescript Studio what kind of files you want to be able to open and save? For that, you need to go to the Project menu and select “Edit Active Target ‘YourAppName’.” Once the window opens, click the “Properties” tab if it isn’t already selected. You’ll find there all the info about your application, from its name to the application icon and there, at the bottom of the window, is the “document types” list.

If you haven’t already, you should create a document icon; usually such an icon bears your application’s icon in a document page image. Also, if you intend to distribute your application you should get a creator code from Apple’s Developer site. A creator code helps the Finder to identify your application’s documents so that when your document is double-clicked it opens in your application and not some other one. This is particularly important if your application reads and writes a standard type of file like text or HTML. For a text editor, you would fill out the document type information like this:

Name: YourAppName Text Document (what you see in a “get info” window in the Finder)
UTI: public.text (Universal Type Identifier is how Mac OS X defines files types. See Uniform Type Identifiers Overview
Extensions: txt text (Possible file extensions that can be used)
Mime Types: text/plain (The mime type, if applicable)
OS Types: TEXT (This is the old, pre-OS X four character type, if it applies)
Class: NSDocument (the class XCode created for you)
Icon File: myicon.icns (The name of your document icon)
Store Type: Binary (usually binary)
Role: Editor (choose this unless your app doesn’t allow editing, then it would be ‘viewer’).
Package: checked (Allows package type files, like rich text file directories - ‘rtfd’)

When your document has been edited, don’t forget to set the modified property of the document to true. That ensures that if the user clicks the close box of the document that your application will ask if they want to save the document before closing, always more elegant than closing and losing a long document!

“Words, once they are printed, have a life of their own.” - Carol Burnett
Speaking of documents, printing a document can sometimes be troublesome. If you’ve ever created an Applescript Studio application and tried to print the contents of your document window, you may have had some odd results. For example, if you create a document with a more than one view, Applescript Studio has a hard time figuring out which view to print. It defaults to printing the currently selected view, which may (or may not) be what you want. Furthermore, often it will print the view as it appears in the window, including the scroll bars.

The solution (as it often times is with Applescript Studio) is to override the default Cocoa built-in actions and provide your own. As you might suspect, this often means using call method, which isn’t the best solution but it gets the job done. You start by connecting a choose menu item handler to the Print menu item. Then you have to get the document view, set the printing information, and call the print operation with the printing info. Here’s an example:

on printDocument()
    local theView, theDocView
    local standardPreferences, sharedPrintInfo
    local thePrintOp
    --get the view
    set theView to view "WebView" of window "docWindow"
    --and the doc view
    set theDocView to call method "documentView" of (call method "frameView" of (call method "mainFrame" of object theView))
    --Set the params
    tell theView
        set standardPreferences to call method "standardPreferences" of class "WebPreferences"
        set sharedPrintInfo to call method "sharedPrintInfo" of class "NSPrintInfo"
        call method "setLeftMargin:" of sharedPrintInfo with parameters {0} -- margins are in points (72 = 1 inch)
        call method "setRightMargin:" of sharedPrintInfo with parameters {0}
        call method "setTopMargin:" of sharedPrintInfo with parameters {0}
        call method "setBottomMargin:" of sharedPrintInfo with parameters {0}
        call method "setOrientation:" of sharedPrintInfo with parameters {0} -- 0 for portrait, 1 for landscape
        call method "setHorizontalPagination:" of sharedPrintInfo with parameters {1} -- 1 = fit to page
        call method "setVerticalPagination:" of sharedPrintInfo with parameters {0} -- 0 = auto paginate (2 = clip to page)
        call method "setVerticallyCentered:" of sharedPrintInfo with parameters {0} -- 0 = false
        call method "setHorizontalCentered:" of sharedPrintInfo with parameters {1} -- 1 = true
        set thePrintOp to (call method "printOperationWithView:printInfo:" of class "NSPrintOperation" with parameters {theDocView, sharedPrintInfo})
        --print the view
        call method "runOperation" of thePrintOp
    end tell
end printDocument

I really must thank my friend Paul Nesfield in England, creator of Diabetes Logbook X, for this solution. I doubt seriously I would have found this myself.

Finding Yourself
Another great secret involves the Find function. If you notice, the default Applescript Studio menubar you get in Interface Builder includes the Find submenu, consisting of Find, Find Again, Find Previous, and Use Selection for Find. Unfortunately, as provided they don’t quite work; there’s still a bit of voodoo to making them find anything.

First, you must set the tag for each menu item to the proper number. Cocoa looks at the tag number rather than the menu title to determine what sort of Find operation to do. With the Inspector window open, select each menu item and look at the Attributes tab. At the bottom of the Inspector is the tag entry box. The correct values are:

1 - Find... 2 - Find Next 3 - Find Previous 4 - Use Selection for Find
OK, now we need to connect those menu items to the performFindPanelAction: action. To do this, we need to select the first item and hold down the control key and drag to the First Responder item in the MainMenu.nib window (that contains all the instances of objects like windows). When you do this, you will see that the Connections tab in the Inspector becomes active. More than likely the action tab will be selected for you - if it isn’t select it. Then click on the performFindPanelAction: action and click the Connect button at the bottom of the Inspector.

Do these steps for each menu item. If you want the “Jump to Selection” menu item to work also, connect it to the centerSelectionInVisibleArea action. This item doesn’t care what tag value it has, so you can safely leave it set to zero.

How simple is that? You have now added full Find support to your documents! A big “Thank YOU!” to Jobu on the MacScripter forums for this one.

A View To Some Frills
Sometimes all that separates a good application from a great one is the “bells and whistles,” or frills. By adding some of these handlers to your application you can give your user the comfort of a real Mac application.

A popular thing to do with a document is find the current selection. Using a text view, you can do this with the following handler that includes a text view method:

on selectedRange(aTextView)
    local theRange
    set theRange to call method "selectedRange" of aTextView
    return theRange --theRange is a list of 2 digits, the beginning character and ending character of the selection.
end selectedRange

You can use this with your own handler that then uses the range to get the selected text of the text view:

on getSelection(aTextView)
    local theContent, theRange
    local beginAt, endAt
    set theContent to content of aTextView
    set theRange to selectedRange(aTextView)
    set beginAt to ((item 1 of theRange) as integer) + 1
    set endAt to (item 2 of theRange) as integer
    if beginAt > endAt then
        return ""
        return (text beginAt thru endAt of theContent)
    end if
end getSelection

And to insert text into a text view at the current insertion point, here’s a nice simple handler:

on insertText(aTextView, theText)
    call method "insertText:" of aTextView with parameter (theText)
end insertText

No More Secrets
As you can see, the call method command gives you the ability to “steal” functions from Cocoa and add a lot more polish to your application. Now these aren’t secret any longer, so we can all go on to find some new tricks!

'Til next time, have fun. Crunch code!