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.

Monday, October 19, 2009

Repeat loops, dialog boxes and aborting

It's one of those things that happen to all revolution developers at one point: you have written a repeat loop, inserted some 'answer' dialogs to figure out the problem, only to discover you're stuck and can't abort the loop because the dialog box is in the way. The only way out is killing the process, which has as downside that you've lost all changes to your stack ince the last Save.

After you've been bitten once, you'll probably adopt the habit of inserting an escape plan in your loops. At the same time, you ought to prevent problems in your shipping code with these escape routes, but more on that later - a first escape route may look something like this:

repeat forever
add 1 to theCounter
answer theCounter
-- next block allows escape
if the cantAbort of this stack is false and the shiftKey is down then
answer "Are you sure you want to exit the loop after running" && theCounter && "times?" with "Continue" or "Exit"
if it is "Exit" then exit repeat
end if
end repeat

The cantAbort property of a stack determines whether or not you can abort in the first place (something which you really ought to turn on in standalones) and there's a checkbox on the stack inspector so it's quite convenient.

Another option is to move that block of code into a separate handler at the stack level

on CheckForAbort pCounter
if the cantAbort of this stack is false and the shiftKey is down then
answer "Are you sure you want to abort execution of" && pInfo && "?" with "Continue" or "Exit"
if it is "Exit" then exit to top
end if
end CheckForAbort

and then add a call to that handler in your loop

repeat forever
add 1 to theCounter
answer theCounter
CheckForAbort "TightLoop-" & theCounter
end repeat

The downside of the second approach is that any cleanup code that comes after the repeat won't be called. Of course you can also rely on try-throw-catch exception handling, but some people find this awkward.

So just for completion, here's how you'd allow escape with cleanup via exception handling; first change the CheckForAbort handler to

on CheckForAbort pInfo
if the cantAbort of this stack is false and the shiftKey is down then
answer "Are you sure you want to abort execution of" && pInfo && "?" with "Continue" or "Exit"
if it is "Exit" then throw "Aborting execution of" && pInfo
end if
end CheckForAbort

and then modify the script with the loop so that it resembles the following

try
repeat forever
add 1 to theCounter
answer theCounter
CheckForAbort "TightLoop-" & theCounter
end repeat
catch tError
answer error tError
finally
-- do your cleanup here
--> it will be called whether or not something throws an error
end try

Hopefully, this will be of use to you at one point or another. Make sure to check out these entries in the documentation for more information about aborting running scripts: cantAbort stack property, allowInterrupts global property, interrupt function and of course the try-throw-catch triade for exception handling. Use them wisely, and provide your users with a safer envronment to work in...

Sunday, October 4, 2009

Quartam Reports Webinar

As announced in the revUp 79 newsletter for revAfficionados, I'll be hosting a webinar on Quartam Reports, sponsored by the revSelect third-party extension program.

What can you expect to see?
- a few slides to explain how Quartam Reports can help you revDevelopers
- some examples of what you can achieve with Quartam Reports
- a brisk walkthrough creating a report for an example application
- time for questions and answers at the end

Click here to register and join us on October 6, 2009 at 7pm BST (8pm for most of Europe, 2pm Eastern for US viewers) - if you can't make it, you can always watch the recording a few days later. I'll post the download link once available.

RunRevLive'09 Slides and Example Code

Just a month ago, RunRev held their successful RunRevLive'09 conference in Edinburgh.

As I've blogged before, I had the honour of presenting on three topics:
- 'Working with Java Classes'
- 'Desktop Databases with SQLite'
- 'Basic Reports & Output'

By popular demand, I've made the slides and example code for these sessions available in the Downloads section on the quartam.com website.