Previous Page
Next Page

27.1. Applets

An applet is a compiled script wrapped up in a simple standalone application shell (see "Applet and Droplet" in Chapter 3). To make a script into an applet, save it from a script editor application as an application instead of as a compiled script. You select this option in the Save As dialog. When you launch the resulting application (by double-clicking it in the Finder, for example), the script runs.

It is also possible to save a script as an application bundle. From the outside, this looks and works like an applet. Because it's a bundle, though, you can do things with it that you can't do with an old-style applet, such as storing extra resources inside it; for an example, see "Persistence," later in this chapter. Also, an application bundle can call scripting additions contained within itself; see "Loading Scripting Additions" in Chapter 21. Keep in mind that this format is not compatible with systems earlier than Panther.

For applet formats, and for special applet behaviors when an application is missing as an applet starts up, see "Applet and Droplet" and "Application Missing When an Applet Launches" in Chapter 3, and "Using Terms From" in Chapter 19. Persistence works in applets; see "Persistence of Top-Level Entities" in Chapter 8. For the behavior of an applet when a runtime error occurs, see "Errors" in Chapter 19. When an applet runs, no decompilation takes place; for one way this can affect the behavior of your script, see "Raw Four-Letter Codes" in Chapter 20.

27.1.1. Applet Options

When you elect to save a script as an applet, you are given some options that affect how the applet will behave:


Stay Open

The normal behavior of an applet when started up is to run its script and then automatically quit . A stay-open applet does not automatically quit; it just sits there running, like any application. An applet has some built-in menus , and in a stay-open applet the user has time to access them; they include a Quit menu item, which the user can choose to quit the applet. If stay-open applet has already run its script, what's the point of its staying open? For the answer, see "Applet Event Handlers," later in this section.


Startup Screen

(Also called Show Startup or Show Startup Screen; in older versions of Script Editor this option was reversed and was called Never Show Startup Screen.) The startup screen , if it is to be shown, is a kind of introductory splash screen displaying the script's description when the applet is started up. In a script editor application, there is a text view into which a script's description may be entered. The description is styled text, and the styling is maintained in the startup screen dialog. The splash screen also offers the choice to run the applet's script or to quit without running it.

In a stay-open applet, where the user has time to access the applet's menus, the user can toggle the startup screen option by choosing File Use Startup Screen. Even in an ordinary applet, the user can compel the startup screen to appear by holding down the Control key as the applet starts up.

27.1.2. Editing an Applet

A script saved as an applet is normally still legible and editable. The only way to prevent this is to save the applet as Run Only. Keep in mind that this means even you can't edit the applet's script; if you have no other copy of the script, you lose all ability to edit the applet's script, forever.

Let's presume the applet is not run-only. How can its script be edited? Not by double-clicking the applet from the Finder, because that runs the applet. However, a script editor application can still open it (through its Open dialog, for example). In fact, you can even keep an applet script open for editing in a script editor application, save it without closing it, and then double-click it in the Finder to run it, as a way of testing while developing. But, for obvious reasons, you can't save an applet's script into the applet while the applet is actually runningwell, you actually can, but the changed functionality won't be available until you quit the applet and start it up again.

Another way to edit an applet is to choose its Edit Edit Script menu item. In a stay-open applet, that menu item can be chosen whenever the applet is idle. In non-stay-open applet, the menus are visible while the startup screen is displayed; thus, the user can start up the applet while holding down the Control key, to force the display of the startup screen, and then choose Edit Edit Script.

27.1.3. Applet Event Handlers

An applet script may contain certain event handlers (see "Event Handlers" in Chapter 9) which, if present, will be called automatically at specific moments in the lifetime of the applet:


run

The run handler , whether implicit or explicit (see "The Run Handler" in Chapter 9), is called when the applet is started up, either by the user opening it from the Finder or by a script targeting it. (To start up an applet without calling its run handler, tell it to launch.)


reopen

A reopen event handler, if present, is called when the already running applet is summoned to the front by such means as being double-clicked in the Finder or having its icon clicked in the Dock. Merely switching among applications with -Tab, or telling the applet to activate, does not send a reopen event.


idle

An idle event handler, if present, is called as soon as the run handler finishes executing, and then again periodically while a stay-open applet is running. The value returned by the idle handler is a real number, representing how many seconds later the idle handler should be called again. A return value of 0, or any other value that can't be coerced to a positive real, is treated as 30.


quit

A quit event handler, if present, is called when the applet is about to quit. If it is a stay-open applet, this might be because the user has chosen its Quit menu item; if not, it might be because the applet has been started up and its run handler has finished executing. If the quit handler wishes to permit the applet to quit, it must give the continue quit command.

An applet having a quit handler that does not give the continue quit command will appear to the user to be impossible to quit (except by force-quitting).


So, for example, here's an annoying little stay-open applet:

on run
    display dialog "Howdy!"
end run
on reopen
    display dialog "Get to Work!"
end reopen
on quit
    display dialog "Farewell!"
    continue quit
end quit
on idle
    beep
    activate
    display dialog "Get to Work!"
    return 1 * minutes
end idle

An applet is scriptable with respect to its event handlers; that is, you can tell an applet to run, reopen, idle, or quit, and it will execute the respective handler if it has one. If you tell an applet to quit and it has no quit handler, it simply quits. If you tell an applet to idle and it has no idle handler , or to reopen and it has no reopen handler, nothing happens (but the calling script may receive an error).

If an applet is not running, do not tell it to run or you'll confuse yourself. The run handler will be called twiceonce because you targeted the applet, and again because you told it to runand the idle handler will be called as well. To prevent this, tell the nonrunning applet to activate (the run handler will be called once, and the idle handler will be called).

Alternatively, you can tell the nonrunning applet to launch. In that case, neither the run handler nor the idle handler will be called, and no other event handler is called either; if you immediately tell the applet to quit, not even its quit handler is called. An applet launched in this way "comes to life" only if you send it some further Apple event (for example, tell it to launch and then tell it to run).

An idle handler should not be treated as ensuring a precise measure of time; the time interval returned is merely a request not to be called until after that interval has elapsed. (I am not entirely clear on what the time interval is measured from; experiments returning 0 seemed to suggest that it was measured from when the idle handler was last called, not from when it last returned, but this didn't seem to be true for other values.) If your goal is to run a script at certain times or intervals, you might be happier using a timer utility to handle this for you (see "Timers, Hooks, Attachability, Observability" in Chapter 26).

A question arises of how to interrupt a time-consuming applet. (This problem, and its solution, were suggested to me by Paul Berkowitz.) Suppose the run handler takes a long time and the user wishes to stop it and quit. The user can press Command-. (period), which raises an error (-128, "User canceled") that stops the run handler dead in its tracks, but the user might not know this. The Quit menu item doesn't work; if the user chooses it, the applet doesn't quitif it has a quit handler, the quit handler is called, but when the quit handler says continue quit, the applet goes right back to executing the run handler where it left off! (I regard this as a bug.) There is also the question of how you can make sure that any required clean-up actions are performed as the applet quits. The best strategy is probably something like this:

global shouldQuit
global didCleanup
on run
    set shouldQuit to false
    set didCleanup to false
    try
        -- lengthy operation goes here
        repeat with x from 1 to 10
            if shouldQuit then error
            say (x as string)
            delay 5
        end repeat
    on error
        tell me to quit
    end try
end run
on quit
    if not didCleanup then
        -- cleanup operation goes here
        say "cleaning up"
        set didCleanup to true
    end if
    set shouldQuit to true
    continue quit
end quit

When the user chooses Quit from the menu bar, our quit handler is called, but when we say continue quit we will merely resume the run handler, so we also set a global indicating that the user is trying to quit. The resumed run handler notices this and deliberately errors out, and we catch the error and call our own quit handler, and quit in good order. We would perform our cleanup operations twice in this case, but that is prevented by another global. It's all very ingenious, but it's messy, and ultimately not very satisfactory. If the user chooses Quit from the applet's Dock menu, our applet's quit menu handler isn't called (another bug), and if the user force-quits our applet then of course no cleanup is performed.

27.1.4. Droplets

A droplet is an applet onto whose icon the user can drop Finder items (files and folders). Internally, it is simply an applet with an open event handler:


open

An open event handler, if present, will be called when items are dropped in the Finder onto the droplet's icon. It should take one parameter, through which your code will receive a list of aliases to the items dropped.

If a droplet is started up by double-clicking it from the Finder, then its run handler is executed and its open handler is not. But if it is started up by dropping items on it in the Finder, then it's the other way around: its open handler is executed and its run handler is not. Once a droplet is running (assuming it is a stay-open droplet), the open handler can be executed by dropping items onto the droplet's icon in the Finder. The open handler is also scriptable, using the open command, whose parameter should be a list of aliases.

In this simple example , the droplet reports how many folders were dropped on its icon:

on open what
    set total to 0
    repeat with f in what
        if folder of (info for f size no) then set total to total + 1
    end repeat
    display dialog (total as string) & " folder(s)"
end open

27.1.5. Persistence

Persistence of top-level entities (see Chapter 8) works in an applet. The script is resaved when the applet quits, maintaining the state of its top-level environment.

So, for example, the following modification to the previous example would cause an applet to report the count of folders that had ever been dropped on it, not just the count of folders dropped on it at this moment:

property total : 0
on open what
    repeat with f in what
        if folder of (info for f size no) then set total to total + 1
    end repeat
    display dialog (total as string) & " folder(s)"
end open

On the other hand, this persistence naturally ends as soon as the applet's script is edited. If you're still developing an applet, or are likely to edit it further for any reason, you might like a way to store data persistently with no chance of losing it. The application bundle format supplies a solution. An application bundle looks, and behaves in the Finder, just like an applet , but is in reality a folder. We can perform persistent data storage in a separate file inside the bundle; the user won't see this separate file, and its data will persist even when we edit the applet's main script.

To illustrate, let's return to the example in "Data Storage" in Chapter 8. This code is just the same as in that example, except that we now assume we are an application bundle, and the opening lines have been changed to store the data inside the bundle:

set thePath to (path to resource "applet.icns") as string
set text item delimiters to ":"
set thePath to ((text items 1 thru -2 of thePath) as string) & ":myPrefs.scpt"
script myPrefs
    property favoriteColor : ""
end script
try
    set myPrefs to load script file thePath
on error
    set favoriteColor of myPrefs to text returned of 
(display dialog "Favorite Color:" default answer
"" buttons {"OK"} default button "OK") store script myPrefs in file thePath replacing yes end try display dialog "Your favorite color is " & favoriteColor of myPrefs

The first three lines of the script are a rather elaborate song-and-dance to obtain the pathname of the bundle's Resources directory. We could hardcode the path from the top of the bundle, like this:

set thePath to (path to me as string) & "Contents:Resources:myPrefs.scpt"

Instead, we use the path to resource command. But this command has an odd functionality hole: we can obtain the pathname of a resource file inside the Resources directory, but not the pathname of the directory itself. So we start with a resource file we know is present and work our way up the folder hierarchy and back down again.

27.1.6. Applet Scriptability

We have seen already that an applet is scriptable with respect to its event handlers: you can tell an applet to run, open, reopen, idle, or quit, and the corresponding event handler will be called. An applet is scriptable also with respect to user handlers. You call them like ordinary handlers. For example, suppose we have a stay-open applet howdy whose script goes like this:

on sayHowdy(toWhom)
    activate
    display dialog "Howdy, " & toWhom
end sayHowdy

Then we can say in another script:

tell application "howdy"
    sayHowdy("Matt")
end tell

The value is returned as one would expect; here, the calling script receives the value {button returned:"OK"} if user presses the OK button.

The reason this is possible is that AppleScript's mechanism for calling a user handler in a script object is extended to work even when you're talking to an applet . The call is translated into a special event, the Call·subroutine command ('ascr\psbr'). This is a sort of meta-command; its parameters are the name of the handler you're calling and the parameters you're passing. At the other end, the target applet unpacks the handler call and performs it within its script. Thus it's just as if you said sayHowdy("Matt") from inside the applet's script. If the script defines such a handler, the result comes back as the reply. If it doesn't, you get a "Can't continue" error message. (This error message is familiar from when you accidentally use this same mechanism to send a user handler call to an ordinary scriptable application, which, as we have seen in Chapter 11 and elsewhere, is all too easy to do.)

An applet is also scriptable with respect to its other top-level entities. If an applet has a top-level property x, you can get and set its x. And if an applet defines a top-level script object s, you can refer to s, and you can get and set its properties.

In short, an applet behaves like a script object, whose top-level entities you can access in the normal way (see "Top-Level Entities" in Chapter 8). You might think of it as a stored script object that you can target without loading it, and that is automatically persistent without your storing it.

Of course, this means that when you're scripting an applet, no special dictionary is involved. An applet doesn't have a dictionary, and it doesn't need one, any more than a script object needs a dictionary. Therefore an applet has no way to publish information about what top-level entities it contains. The user who wishes to script the applet must know in some other way how to do so (by reading the applet's script, or through some other documentation).


Previous Page
Next Page