Idle Interval 1.1

Techniques
Idle Interval Scripting Addition

Introduction
A: Create a new idle handler
B: Copy Idle Interval.osax into your application bundle

C: Load the internal copy on Mac OS X 10.2
D: Set the SAInternalOnly Info.plist entry
E: Get a path to the active copy of the scripting addition


Introduction   
This page describes techniques for supporting and using the Idle Interval scripting addition in your AppleScript studio application. For example, the user may not have installed the scripting addition yet; or, the application may be running on 10.2, which supports fewer mechanisms to load scripting additions than later versions of Mac OS X.

Many of these techniques depend on specific features of the Idle Interval scripting addition, and do not work for other unmodified scripting additions.


A: Create a new idle handler   
For those who are still new to the adventure of using an idle handler
, you can use the following technique. To complete these steps, you need an existing Xcode AppleScript Studio application project, but you do not need the Idle Interval scripting addition.

(1) Open your Xcode AppleScript Studio project file, and select your .applescript file in the Groups & Files section. Copy the sample idle handler below, paste it into your applescript, and save the script:

on idle theObject
     set newIdleInterval to 0 -- signal AppleScript not to change the idle interval
     try
         -- log "idle handler called"
         beep

          -- ... normal idle handling statements ...

          -- set newIdleInterval to ...  -- (optional) change the idle interval
     on error errMsg number errNum
          -- (always catch errors to guarantee our return value)
          -- log {"idle handler error", errNum, errMsg}
     end try
     return newIdleInterval
end idle

This sample idle handler returns zero explicitly, even if one of its statements causes an error. Returning zero from your idle handler signals AppleScript not to change the idle interval.

Your idle handler is not required to return zero. It can return a positive number, and this will produce the same behavior with or without the Idle Interval scripting addition. However, when you change the idle interval this way, it uses the original AppleScript Studio implementation, which does not call your notification handler, and might not accept fractional numbers.

The key point is that your idle handler should always return the number you intend, not some random value thrown out of the handler because of an error.


(2)
Disclose the contents of your application group in the Groups & Files section, then disclose the contents of its Bundle Resources build phase (see below). In this example, the project contains several application targets. Xcode creates a new project with one target.

Double-click the MainMenu.nib icon to open it:




(3) Interface Builder should launch and display the MainMenu.nib window. Click the File's Owner icon once to select it:




(4) Choose the menu item Show Info from the Tools menu. A window named "File's Owner Info" should open that looks similar to this:




(5) Select AppleScript from the popup menu:




(6) Verify the File's Owner Info window looks similar to the picture below. In particular, the Script section should contain the name of the .applescript file you modified in step 1. (This example contains multiple scripts; yours will probably contain one script.)

If the Script section is empty, then there is a problem and you need to fix it before moving on. This seems to be a rare but annoying bug in Interface Builder. There is no sure way to fix it. You might try quitting both Interface Builder and Xcode, or even logging out as a user and logging in again. Keep trying until it works.

If you can see your .applescript file, make sure its checkbox is selected:




(7) In the Event Handlers section of the window, disclose the Application section. Verify the idle checkbox contains a dash (see below). This will confirm that Interface Builder has detected, but not yet connected to, the new idle handler you pasted into your script. If the checkbox contains a check mark instead of a dash, then deselect the checkbox and look again for a dash.

If the idle checkbox is empty, then Interface Builder can not see your idle handler, and you need to fix the problem before moving on. Maybe you did not save your script in step 1. Quit Interface Builder without saving changes to MainMenu.nib. Make sure the idle handler is in the script, the script compiles correctly, and you have saved the script, then try again.




(8) If the idle checkbox contains a dash, then select it. When it contains a check mark, Interface Builder connects the idle event to the new idle handler in your script:




(9) Save the changes in the MainMenu.nib file. Quit Interface Builder.

(10) Clean, Build and Run your Xcode application target. Your computer should beep once every second when the application is running, because of the beep statement in the sample idle handler. If you do not hear a beep, review each step again.

If you hear a beep every second, congratulations! You have created an idle handler for your AppleScript Studio application. Now you can add you own statements to the handler. You can also install the Idle Interval scripting addition (or use the techniques below to add it to your application) and try changing the timing options.



B: Copy Idle Interval.osax into your application bundle   
Your finely crafted idle handler works just like you hoped. Now you want to share your application with others. How can you ensure the right version of the Idle Interval scripting addition is available wherever your application runs?

One solution is to convince others to install the scripting addition wherever they run your application. For various reasons, this does not always work. At the very least, it can be an inconvenience for them to modify their system, even if you include the scripting addition on the same disk as your application. Also, they could install a different version than the one your application expects.

A more reliable solution is to copy the Idle Interval scripting addition into a special folder of your application bundle with a Copy Files build phase, and optionally set an Info.plist entry so external copies of the scripting addition will refuse to load.

The special folder has this path:

yourAppName.app/Contents/Resources/Scripting Additions/

On Mac OS X 10.3, scripting additions in this folder will load when your application launches. Notice the space in the folder name, unlike the various ".../Library/ScriptingAdditions" folders whose names do not contain a space.

Use this technique to build your application with an internal copy of the Idle Interval scripting addition. If your application needs the internal copy on 10.2, then also follow the steps in section C: Load the internal copy on Mac OS X 10.2. If you want to guarantee only the internal copy loads, then also follow the steps in section D: Set the SAInternalOnly Info.plist entry.


(1) Decide where to keep your source copy of the Idle Interval scripting addition. You can keep it anywhere, as long as it is available when you build your application. One convenient place, shown in the examples below, is your Build Products folder. This location is especially useful if several of your projects share the same build products folder.

(2) Open your Xcode project file, and disclose the contents of your application group in the Groups & Files section of your project window (see below). In this example, one project contains several applications. Xcode creates new projects containing one application.

Click the Resources group once to show Xcode where to add a new file:




(3) Choose the menu item Add to Project... from the Project menu:




(4) A file browsing panel should open in your project window, allowing you to locate your source copy of the scripting addition. Once you have located and selected your source copy, click the Add button to continue.

(5) A different panel should open, asking you about details of the added file (see below). Review the items in the Reference Type popup, and choose the one that makes sense for your situation. In this example, Relative to Build Product makes sense. If none of them make sense or you are not sure, then select Default from the popup.

In the Add To Targets section of the panel, select the checkbox beside one target, representing the application you disclosed in step 2. Deselect any other checkboxes in the Add to Targets section, then click the Add button.




(6) Make sure the Idle Interval scripting addition appears in the Resources group (see below). If not, you need to correct the problem before you continue.




(7) Disclose the contents of the Targets group. Click once on your application target to tell Xcode where to add a new build phase:




(8) Choose the menu item New Copy Files Build Phase from the submenu New Build Phase in the Project menu. A Copy Files Info window should open that looks like this:




(9) Choose Resources from the Destination popup menu, then type "Scripting Additions" into the Subpath text field. When the window looks like the picture below, then close it.




(10) At this point, your application target should be selected in the Groups & Files section. Disclose the contents of the application target, and also disclose the contents of the Bundle Resources and Copy Files build phases. The Groups & Files should now look like this:




(11) Drag the file Idle Interval.osax from the Bundle Resources build phase down to the Copy Files build phase. Your Groups & Files should now resemble the picture below. The file Idle Interval.osax should appear in two places, the Resources group and the Copy Files build phase, and it should not appear in the Bundle Resources build phase.




(12) Clean and Build your application target. It should now run on Mac OS X 10.3, without installing the Idle Interval scripting addition on that machine.



C: Load the internal copy on Mac OS X 10.2   
The previous section, B: Copy Idle Interval.osax into your application bundle
, describes a feature of Mac OS X 10.3 that loads scripting additions from a special folder in your application bundle. However, Mac OS X 10.2 ignores the special folder and loads nothing from it.

This section describes how to load the Idle Interval scripting addition at runtime from your application bundle, if it did not already load. You do not need this technique unless your application has two requirements: (a) to run on 10.2 as well as 10.3, and (b) to load the internal copy of the Idle Interval scripting addition when necessary. The technique is safe to use on 10.3, and when using a normally installed copy of the scripting addition.

(1) Modify your application script by adding initializeIdleInterval(), a handler that can load the Idle Interval scripting addition at runtime if necessary, and notify the user when the scripting addition fails to load:

on initializeIdleInterval()
     -- log "initializeIdleInterval begins"
     set iiOK to false
     try
          -- Check if the addition loaded from an external folder, or, on 10.3 or later,
          -- from our application main bundle /Resources/Scripting Additions/ folder.
          set iiValue to get idle interval of timing options
          if class of iiValue is real then set iiOK to true
     on error errMsg number errNum
          -- log {"iii: idle interval addition not already loaded", errNum, errMsg}
          -- ignore errors; always return a boolean
     end try

     if not iiOK then try
          -- Try loading the addition from inside the current application.
          set iiBundlePath to resource path of main bundle & "/Scripting Additions/" & ¬
               "Idle Interval.osax/Contents/Resources/IdleIntervalBundle.bundle"
          set iiBundle to call method "bundleWithPath:" of class ¬
               "NSBundle" with parameter iiBundlePath
          call method "load" of iiBundle

          set iiValue to get idle interval of timing options
          if class of iiValue is real then set iiOK to true
     on error errMsg number errNum
          -- log {"iii: no internal addition or iiBundle failed to load", errNum, errMsg}
          -- ignore errors; always return a boolean
     end try

     -- log ("initializeIdleInterval returns " & (iiOK as boolean))
     return iiOK
end initializeIdleInterval


(2) Modify your launched handler to call initializeIdleInterval() and quit if it returns false, like the example below. If you have no launched handler, then you can use will become active or activated. However, it is possible for an AppleScript Studio application to launch directly into the background without calling will become active, will resign active, activated, or resigned active. The launched handler is the earliest, most reliable place to call initializeIdleInterval().

on launched theObject
     try
          -- ... other statements ...

          -- Idle interval features do not exist until will become active.
          -- However, this can be called here, to prepare for later.
          if not initializeIdleInterval() then ¬
               error "Please install the Idle Interval scripting addition."

          -- ... other statements ...
     on error errMsg
          display dialog errMsg & return & return & ¬
               "(This dialog will close in 60 seconds.)" buttons {"Quit"} ¬
               default button "Quit" giving up after 60
     end try
end launched



D: Set the SAInternalOnly Info.plist entry   
The internal copy of the scripting addition is necessary whenever the user does not install an external copy. However, there may be situations when your application depends on a feature in a specific version of the scripting addition. If the user does install the scripting addition in one of the "...Library/ScriptingAdditions/" folders, then merely copying the right version of the file into your application bundle is not sufficient to ensure it will be the active copy.

After you complete section B: Copy Idle Interval.osax into your application bundle
, (and optionally, section C: Load the internal copy on Mac OS X 10.2), then use the following technique to guarantee the internal copy is always the active copy, even in the presence of one or more external copies. The strategy is to add an Info.plist entry to discourage external copies of the Idle Interval scripting addition from loading.

Currently, only the Idle Interval scripting addition responds to this technique. However, the Info.plist entry is an array, designed to support multiple scripting additions when needed.

(1) Open your Xcode project file, and disclose the contents of the Targets group. Click your application target once to select it (see below). You should then see the target editor details in the adjacent section of the window.




(2) In the target editor, click Expert View under the section Info.plist Entries:




(3) Look for the Info.Plist Entries table displayed in the window. It should look similar to the picture below, except some of the values will be different in your project:




(4) Click the New Sibling button to create a new entry in the table.




(5) Type "SAInternalOnly" as the name of the new entry, and press the return key:




(6) Change the Class of the new SAInternalOnly entry to Array:




(7) Click the SAInternalOnly array entry once
to select it, and disclose its contents. (Although it has no contents yet, you can still click the disclosure triangle to the left of the name.) Disclosing its contents causes the New Sibling button to change to New Child:




(8) Click the New Child button to append a new item to the SAInternalOnly array:




(9) The new SAInternalOnly array item should have Class String. Type "Idle Interval" as the value of the new string, then click the left end of its row in the table, to stop editing the item:




(10) Clean and Build your application target. When you launch it, the internal copy of the Idle Interval scripting addition always loads instead of external copies.



E: Get a path to the active copy of the scripting addition   
Previous techniques describe ways of loading the Idle Interval scripting addition from inside your application. This technique allows you to get (and display) the string path path to the active core bundle, which contains the path to the active Idle Interval.osax file. This way, you can prove which copy is active, instead of guessing.

(1) Paste this into your AppleScript Studio application script, in a handler that executes sometime after your application loads the scripting addition:

-- get the path of the core bundle currently implementing the Idle Interval suite
try
     set scriptSuiteRegistry to call method ¬
          "sharedScriptSuiteRegistry" of class "NSScriptSuiteRegistry"
     set iiCoreBundle to call method "bundleForSuite:" of scriptSuiteRegistry ¬
          with parameter "IdleInterval"
     set iiCurrentCorePath to call method "bundlePath" of iiCoreBundle
on error
     set iiCurrentCorePath to "(error: no bundle found for IdleInterval suite)"
end try

-- display the path
display alert "Idle Interval core bundle path:" message ¬
     iiCurrentCorePath default button "OK"



           <----- BACK: Features                                                                   NEXT: FAQ ----->




Thursday, 07-Aug-2008 02:07:40 MST

© 2004-2007 Stuart B. Russell. All Rights Reserved.
Trademarked terms are the property of their respective owners.