Monday, September 27, 2010

ApplescriptObjC tutorial: the most basic tutorial

I wrote a collection of Applescripts to automate data entry and other processes. These scripts however don't have a good place to live. For example, how should I launch them all? Double click on their icon? I now have about 10 scripts and I need a way to launch them. I decided to go the route of AppleScriptObjC. This is a brand new platform Apple designed to replace Applescript Studio. It is essentially applescript bindings to the cocoa library. The only reason this is useful is because the XCode Interface Builder (IB) allows you to design your own window by drag and drop of buttons, etc. Each button can correspond to launching a script. You can include text fields to gather user intput, and you can even implement progress bars, etc... But lets not get ahead of ourselves. The problem with ApplescriptObjC is that it requires a basic understanding of Cocoa. But, I believe that is only because nobody on the net is currently talking about it from the scripter's perspective.

If you start out as someone wanting to write automation routines, then why would you ever consider writing or learning C libraries? Nobody is doing that this day and age. Its bad enough you have to use applescript in the first place.

This tutorial is for those of you who don't want to program C libraries, but want to write your automations and hook them into a GUI you build using drag and drop. I'll start by opening a new project and creating a simple program that has 1 button and when you click it it runs your script.


Phase 1: Writing the code.
  1. Download and install Apple's XCode (requires snow leopard 10.6 or better)
  2. Start a new project by selecting "Cocoa Applescript Application" and name it "test"
  3. Once your project is created and in front of you look on the left side for "Classes" go into it and select "testAppDelegate".  This is simply a file containing all your applescripts. You can safely ignore everything else for now.
  4. Notice there is a script in there: "script testAppDelegate" this is the script where you will setup functions that will run when you click buttons in your program. Notice the first line is: "property parent : "NSObject"". This is inheritance. Everything in this script inherits all the functions available in the cocoa class NSObject. Fortunately we don't care what this means or does as we just want to setup a GUI for our collection of scripts. Just keep in mind that everything you implement needs to go after this for cocoa to work properly.
  5. Create a new function call it "ButtonClicked" and SAVE:
 on ButtonClicked_(sender)
display dialog "button was clicked"
end ButtonClicked_
You need to have the underscore at the end of the function name and the parameter must be sender. This informs the Cocoa libraries that it can accept connections from buttons you will later create in the Interface Builder (IB)

Phase 2: Designing the window and connecting buttons.
  1. Next on the left side under resources: double click on the file MainMenu.xib This will open up the interface builder and start you out with a default empty window.
  2. If the library pane isn't already opened up, click on "Tools>Library" and the window should open. Scroll down until you find a button and drag it into your main window.
  3. Type whatever text you want into the button to label it
  4. Control-Drag the button to the object "Test App Delegate" in the MainMenu.xib object browser. It should create a line between the button and the App Delegate object  (your script)
  5. When you let go of the mouse click, it should popup a context HUD menu. Select "ButtonClicked:". If it is not an option go back to the app delegate script and ensure you have it saved and ensure that it is setup correctly with the underscore and parameter "sender" as described above. Notice they use a : instead of _ in the name of your function that is because Cocoa is converting the functionname syntax from applescript to C. Don't worry about that it is supposed to be that way.
  6. You are now fully connected. SAVE from within MainMenu.xib's object browser and go back to XCode and hit the hammer button to compile and run.
A couple of notes: notice that when you click the button, it doesn't become available again until the script finishes running. So if you have a script that requires lots of time to run and you want to continue to use your application you might consider forking it off using a shell script call to osascript. I have another post that describes exactly how to do that here

forking a thread in AppleScript...

I searched everywhere to learn how to fork a background thread or process using Applescript. The best I could find is to use "do shell script" to start an osascript background process to run the second script. This works pretty well so long as your script works properly under osascript.

In my particular case, I was using Applescriptobjc to write a GUI for my collection of applescripts that assist data entry in a proprietary database I use at work.  Then I wanted a background process to control the visibility of this application depending on whether or not that program is the active window. The script then moves the position of the window to the bottom of that application's window.

The first option was to just have a separate applescript that would run separately. But now that I can fork a thread from within the main program, there is no need to do this. Here's the code I used:

    on DockUtility()
        set pathname to "/Applications/lightspeedhelper/"
        set scriptname to pathname & "StickToLightSpeed.scpt"
        set logfile to pathname & "StickToLightSpeed.log"
        set bgPID to ""
       
        do shell script "osascript " & scriptname & " &> " & logfile & " & echo $!" -- redirect output and send as bg thread
        set bgPID to the result
    end DockUtility

the shell script evaluates to:

osascript scriptname.scpt &> logfile.log & echo $!

This will launch osascript from a unix shell, redirect standard output and standard err to the log file (that is what you have to do for it to launch in the background) and the ampersand at the end means set it to launch in the background. The echo $! is a shell command that returns the process ID (PID) of this process. It is useful sometimes to have the bgPID there so you can later kill the process or check if it is active.