Sunday, October 25, 2009

Revolution, Snow Leopard and AppleEvent handlers

As you know, Macs have always sported a rather different operating system - in fact, MacOSX is quite good at hiding its powerful UNIX underpinnings from the average user, while still offering its full power to the more advanced computer user. But tossing out the old brittle 'cooperative multi-tasking' MacOS Classic, and swapping in the modern, robust NeXtStep with a new coat of paint, required that the Apple developers came up with a whole compatibility layer, named Carbon.

And one of the things that Carbon brought with it, was the system of Apple Events - added in System 7.0, this new type of event allowed Mac applicatiosn to talk to one another. Later, these Apple Events formed the underpinning of AppleScript, the system-wide scripting mechanism that is widely used for automating processes (and in turn, underpins the Automator tasks that allow you to point-and-click automation workflows in MacOSX).

What exactly is an Apple Event? An AppleEvent has a generic Class and Id, as well as associated data - and our rev stacks can handle these events by implementing an 'appleEvent' handler in our (stack) script. Alright, but why should I care as revDeveloper? Because if you want to write a decent Mac application, you had better implement the 'core' events. These are apple events of class 'aevt' and have an id of 'oapp' (open application), 'odoc' (open document), 'pdoc' (print document) or 'quit' (exit application).

Now, the revEngine is kind enough to handle the 'oapp' and 'quit' events for us, translating them into built-in events for us to handle, So we can concentrate on 'odoc' and 'pdoc' - and in fact, we only need to bother with these if we have some sort of 'editor' application. Of course, the Quartam Reports Layout Builder is exactly such an application. So I implemented a simple handler in my stack:
on appleEvent pClass, pId
if pClass is "aevt" and pId is "odoc" then
-- get the associated data
request appleEvent data
put it into tFilePath
-- now handle opening the file somewhere else
OpenFile tFilePath
else
-- let the revEngine handle other events
pass appleEvent
end if
end appleEvent

However, with the release of Snow Leopard, the format of the associated data appears to have changed rather dramatically. In previous releases, we would get a file path that we could use directly in file processing. For instance, your 'OpenFile' handler could look something like this:
on OpenFile pFilePath
put URL ("binfile:" & pFilePath into tBinaryData
-- go ahead and parse the content of the file
end OpenFile

For Snow Leopard, the good engineers at Apple have decided to change the format and where a file path from an 'odoc' event used to look something like "/My Big Project/Layouts/My First Layout.qrl", it now looks something like "file://localhost/My%20Big%20Project/Layouts/My%20First%20Layout.qrl" - presumabl, they're paving the way for other protocls than "file://" in the future, but this change has left our apps in the dust. I'm sure they were kind enough to update the Cocoa framework to translate this automatically, but what about us who don't drink the Objective-C Kool-Aid?

Well, the '%20' substitute for the space character, clued me in that there was some sort of URL encoding going on. So after a bit of fiddling, I came up with the following workaround for this prickly problem:
on appleEvent pCLass, pId
if pClass is "aevt" and pId is "odoc" then
-- get the associated data
request appleEvent data
put NormalizedPath(it) into tFilePath
-- now handle opening the file somewhere else
OpenFile tFilePath
else
-- let the revEngine handle other events
pass appleEvent
end if
end appleEvent

function NormalizedPath pFilePath
put pFilePath into tNormalizedPath
-- workaround change in Snow Leopard
if char 1 to 7 of pFilePath is "file://" then
set the itemDelimiter to slash
delete item 1 to 3 of pFilePath -- remove "file://localhost" from front
repeat for each item pFilePart in pFilePath
put slash & urlDecode(tFilePart) after tNormalizedPath
end repeat
end if
return tNormalizedPath
end NormalizedPath

Splitting off the NormalizedPath function means I can reuse it for the 'pdoc' Apple Event, if I ever were to support that. Plus, I can now add that to my 'generic' library that I share among all my applications. Anyway, I hope this blog post is of help to some fellow revDeveloper baffled by this problem.

No comments: