Tuesday, January 17, 2017

#1 2001-10-02 06:22:34 pm

Bill Cheeseman
Administrator
Registered: 2006-06-29
Posts: 158

AppleScript Gotchas, Exceptions to the rules, explained...

How to use the reopen event in a stay-open application

When a user runs (or opens) a script application—for example, by double-clicking on its icon, or selecting its icon and choosing the Open command from the File menu—the Finder calls the applet's run handler and executes it. If the applet has no explicit run handler, any code at the root level of the script is run as an "implicit" run handler.

Script writers sometimes assume that the run handler will be called every time an applet is run in this manner. Surprise! The assumption is not correct in the case of stay-open applications that are already running. When a running stay-open applet is double-clicked, nothing happens. See the Rerunning Applets for the solution.

Offline

 

#2 2001-10-02 06:23:39 pm

Bill Cheeseman
Administrator
Registered: 2006-06-29
Posts: 158

Re: AppleScript Gotchas, Exceptions to the rules, explained...

How to avoid errors when using a loop variable

When looping through a list in a repeat with block using the with form, be careful how you test the loop variable. The following code snippet looks like it should return the value 2, but it doesn't because the test for equality with 2 is never satisfied:

Applescript:


repeat with i in {1, 2, 3}
if i is 2 then
exit repeat
end if
end repeat
i --> item 3 of {1, 2, 3}

As you see from the final value of i, the snippet looped all the way through the list and exited only when the list was exhausted. To get to the bottom of the problem, try this:

Applescript:


repeat with i in {1, 2, 3}
class of i --> integer
i --> item 1 of {1, 2, 3}, item 2 of {1, 2, 3}, etc.
if i is 2 then --it never is 2!
exit repeat
end if
end repeat
i --> item 3 of {1, 2, 3}

Single-stepping through this snippet using an advance script editor, we see that the class property of each list element is reported as integer, which demonstrates that the loop variable is evaluated to its contents when determining its class. However, the loop variable is not evaluated to its contents when performing the equality test. So, to be safe, you should always test the contents of a loop variable, not the loop variable itself, like so:

Applescript:


repeat with i in {1, 2, 3}
class of (contents of i) -->integer
contents of i -->1, 2, etc.
if contents of i is 2 then
exit repeat
end if
end repeat
contents of i -->2

Or, in simpler form:

Applescript:


repeat with i in {1, 2, 3}
if contents of i is 2 then
exit repeat
end if
end repeat
contents of i --2

Or, in the safest form (from the viewpoint of a forgetful scripter), always reset the loop variable to its contents before attempting to use its value for any purpose:

Applescript:


repeat with i in {1, 2, 3}
set i to contents of i
if i is 2 then
exit repeat
end if
end repeat
i -->2

You might think this is a bug in AppleScript, but it isn't. A loop variable in the with form of the repeat loop is a reference to an object, not the value of the object. Like many AppleScript gotchas, this one is actually explained in the AppleScript Language Guide: "To get the value of an item in the list, you must use the contents of operator" (emphasis added). Moral: read the manual!—not once, but twice, thrice, or whatever it takes.

Offline

 

#3 2001-10-02 06:09:56 pm

Bill Cheeseman
Administrator
Registered: 2006-06-29
Posts: 158

Re: AppleScript Gotchas, Exceptions to the rules, explained...

How to deselect selected Finder items from a script

It's easy to deselect selected icons in the Finder: just click in an empty area of the desktop. But how do you do that from a script?

This question has been asked on the AppleScript mailing lists at least once a year since the Finder became scriptable many years ago. People seem to have trouble thinking about this question because they're locked into the Graphical User Interface mindset. They're trying to find a way to make a script click on the desktop.

The trick is to think like a script. The Finder's selection property returns a list of Finder items. If nothing is selected in the Finder, the selection property returns an empty list. So, if you want to deselect selected icons, you want to create an empty selection, right? So....

Apple's official answer appeared on July 11, 1997 in the Technical Q&A's section of what was then called the Developer World site, in a note entitled "IC 04 - Deselecting Icons in the Finder." Since it's no longer available, here's the relevant part of the text:

"Q Is there any way to programmatically deselect icons that were previously selected in the Finder?

"A Yes, actually, there are a few different methods you can use...:

"1.You can do this through AppleScript by doing the following:

Applescript:


ignoring application responses
tell application "Finder" to set selection to {}
end ignoring

".... On the other hand, if you only want to deselect the icon for a particular item, you would need to ask the Finder for the selection, walk through the list of selected items, remove your item, then set the selection to the resulting list...."

The script fragment given in that answer works without the ignoring application responses wrapper. I surmise that it was desirable to include it in 1997 for enhanced speed, but that doesn't seem to be necessary these days.


Filed under: Finder

Offline

 

#4 2001-10-02 06:07:34 pm

Bill Cheeseman
Administrator
Registered: 2006-06-29
Posts: 158

Re: AppleScript Gotchas, Exceptions to the rules, explained...

How and when to use the with timeout statement

AppleScript's handling of timeouts keeps surprising people. Here are the rules.

First, the with timeout statement is not a "pause" or "wait" command, as beginners often mistakenly assume. If you want to know how to implement a true pause in AppleScript, turn to the Hurry Up and Wait gotcha. The with timeout statement doesn't suspend the running of a script for the designated number of seconds while something else is happening. Instead, it tells the system the maximum amount of time you are willing to allow for some operation to complete. It's a "deadline." If the operation completes in less time, the script resumes its work without waiting for the remaining time to elapse. It is a sort of "fail safe" mechanism for applications that have gone awry and fail to return a timely value. If you don't specify another length of time, the system imposes a one-minute time limit, and a script will quit with a timeout error if the application has not completed its action by the time the limit expires. It is frequently necessary to set much longer values for time-consuming actions to be performed by a targeted application, which is the purpose of the with timeout statement.

This can look as if a pause or wait command is at work, because the script will in fact normally do nothing until the targeted application completes its work and returns a value. However, this delay is an integral feature of AppleScript and has nothing to do with the use of a with timeout statement. The delay is simply AppleScript waiting for the application's return value, and it happens whether or not you use a with timeout statement.

In lieu of a with timeout statement, it may sometimes be reasonable to enclose commands to an application in an ignoring application responses statement. If your script doesn't need a return value but merely wants to set some process in action, this will permit the script to do so and to proceed immediately with the rest of its work, even while the application simultaneously carries out the command your script just sent to it. (Some application commands let a script continue immediately even though they have not yet returned a value and even though no ignoring application responses statement was issued. This can cause enormous difficulty and in the old days it led to frequent demands from the scripting community for a good "pause" command (which was eventually provided in the form of the delay command). The best technique to use in these circumstances is an idle handler, as discussed in the Hurry Up and Wait gotcha.)

Second, with timeout applies only to applications that are targeted by a script, not to the application that is running the script (such as a script editor), nor to the AppleScript environment itself. In other words, your scripts can take as long as they want to accomplish whatever they're supposed to do by themselves. It is only once they tell an application to do something that the timeout clock will start running down.

Therefore, with timeout statements should enclose commands that are sent to applications; they never need to be used with commands that are pure AppleScript. This requires some definition. Commands sent to applications include scripting addition commands that are targeted at application objects. Thus, for example, activate application "Excel" and tell application "Excel" to activate could meaningfully be enclosed in a with timeout block on a slow machine, but it would make no sense to enclose a series of complex AppleScript arithmetic or string operations in a with timeout block.

A good example of this principle involves the proper use of the standard Apple display dialog command in the Standard Additions scripting addition. If you call display dialog within a tell block targeted at an application, it will work fine—except that any user who goes on a coffee break lasting more than two minutes will receive a timeout error after returning to click the OK button. Moral: either enclose the call to display dialog in a with timeout block lasting an hour or 24 hours, or move it out of the application tell block. Calling it from a separate subroutine is a good way to do this.

Third, the specified timeout applies to each command within the with timeout block, individually. That is, putting a short-duration application command into a repeat loop will not trigger a timeout after many iterations. Either the command takes too long, in which case it will time out on the first iteration, or it comes in under the wire, in which case you can repeat it as many times as you like without risking a timeout.

Note that it is also possible to set a timeout of less than the default two minutes. This might be useful if, for example, you know the targeted application should complete a command within a few seconds and any longer delay must represent an error condition.

One last point. You will sometimes see people write that with timeout of -1 seconds lets an application take as long as it wants to complete execution; i.e., that the script will never time out. I don't know where this idea came from, but it isn't mentioned in the manual or any of my AppleScript books, and it doesn't work. In my scripts, if an application command times out with a timeout of 1 second, it also times out with a timeout of -1 seconds. (There is in fact a kNoTimeOut constant for use by application developers, but its value is -2; -1 is the value of the kAEDefaultTimeout constant, which specifies the default timeout value of two minutes. See Inside Macintosh, "Interapplication Communication," chapters 4 and 5. These values have no use in scripts.)


Filed under: excel

Offline

 

#5 2001-10-02 06:01:04 pm

Bill Cheeseman
Administrator
Registered: 2006-06-29
Posts: 158

Re: AppleScript Gotchas, Exceptions to the rules, explained...

How to wait faster for something to happen

The quickest way to write a simple routine to make a script pause while it waits for something to happen is to place a test routine inside a repeat loop. You used to see this advice on the AppleScript mailing lists all the time. The script will repeatedly test for the awaited condition, doing nothing else until the condition appears.

But DON'T DO IT THIS WAY! Not only will your script do nothing else while it is waiting, but your entire system will bog down and do practically nothing, too. If you don't believe me, try writing a script that opens a large digital photo and then waits in a repeat loop until the photo is open. The photo will take an agonizingly long time to open, far longer than if you opened it by double-clicking on its icon. The same thing happens if you use a repeat loop to wait for a PPP connection to open, or for any slow process to complete. The reason is simply this: a repeat loop, like any other normal code, monopolizes the computer's processor while it runs, depriving other pending processes of sufficient access to the CPU.

This problem is considerably ameliorated in Mac OS X, where other processes always get their fair shares of the CPU's time. However, your repeat loop will still convince the system that your process needs time, and it will slow other processes down.

The correct way to implement a pause in AppleScript is to use the AppleScript idle handler. It is harder to code, and it requires a conceptual shift in your scripting frame of reference, but it is well worth the effort to learn how to do it. You can specify in the idle handler how long the system should spend on other activities before returning to your test routine and, by golly, the system will run other processes at full speed in between scheduled calls to your idle handler. Even a one-second idle interval will work wonders, because computers can do an immense amount of processing in one second.

This advice used to be correct even when you simply wanted to pause for a specified duration, rather than while waiting for some specified condition to become true. However, the Mac OS now includes a command in the Standard Additions scripting addition, delay, which is an easy way to implement a pause for a specified number of seconds in AppleScript. It causes your script to pause for a designated period while letting the rest of your system run at full speed. The idle handler is still, however, the best way to implement more complex pauses.

For an in-depth discussion of strategies for using AppleScript's idle handler, with examples, read the Idle Thoughts tip. You might also find it useful to glance at the Time's Up! gotcha.

Offline

 

#6 2001-10-02 06:02:51 pm

Bill Cheeseman
Administrator
Registered: 2006-06-29
Posts: 158

Re: AppleScript Gotchas, Exceptions to the rules, explained...

How to compare lists that contain sublists

Comparing two lists is a simple task, well documented in the AppleScript Language Guide. However, the simplicity breaks down when the lists contain sublists, and the Language Guide doesn't tell you about this special case. Here, we explain how to compare lists of lists without error. In essence, one must remember that two lists containing sublists can properly be compared only if the outer list brackets are represented on both sides of the comparison operator. Let's see what this means.

First, ordinary lists: The Language Guide explains that the Equal and Is Not Equal To operators report that two lists are equal if each item in the list to the left of the operator evaluates to the same value as the item in the corresponding position in the list to the right of the operator. The example given is:

Applescript:


{(1 + 1), (4 > 3)} is equal to {2, true} --> true

The Guide explains that the Contains and Is Contained By operators report that one list is in another if it is a sublist of the other; that is, if its items not only evaluate to the same values but appear in the same order in both lists. An example is:

Applescript:


{"this", "is", 1 + 1, "cool"} contains {"is", 2} --> true

If you want to know whether a list contains a single item, you can optionally omit the list brackets, because AppleScript will coerce the single item to a list containing that item before making the comparison. For example,

Applescript:


{"this", "is", 1 + 1, "cool"} contains {2} --> true

is equivalent to:

Applescript:


{"this", "is", 1 + 1, "cool"} contains 2 --> true

The latter is the more natural usage to English speakers, but, as we shall see, this will lead us into a fatal error when we encounter lists containing sublists.
What happens when you apply the comparison rules to lists of lists? Take the first example, above, and change the first item in the list on the left, "(1 + 1)", to a sublist, "{1 + 1}", so that the comparison reads as follows:

Applescript:


{{1 + 1}, (4 > 3)} is equal to {2, true} --> false

The comparison now returns false. To make it true, the first item in the list on the right must also be a list, like so:

Applescript:


{{1 + 1}, (4 > 3)} is equal to {{2}, true} --> true

This much may seem obvious, since {1 + 1} evaluates to {2}, not to 2. AppleScript is unable to coerce items in multi-item lists the way it can coerce between single items and single-item lists, as is more or less documented in the Language Guide.

The surprise comes with the containment comparison operators. You would expect this comparison to be false, and it is, for the reason described above:

Applescript:


{{1 + 1}, (4 > 3)} contains 2 --> false

But wouldn't you expect this comparison to return true?

Applescript:


{{1 + 1}, (4 > 3)} contains {2} --> false

Well, it doesn't. Instead, you have to use double list brackets on the right, like so:

Applescript:


{{1 + 1}, (4 > 3)} contains {{2}} --> true

Why? It appears that, as a general rule, AppleScript requires the outer list brackets to be represented on both sides of the operator when there is a sublist within the lists, even when the operator is Contains. AppleScript will not coerce {2} to {{2}}. Thus,

Applescript:


{{1 + 1}, (4 > 3)} contains {2} --> false

is not true because the first item in the list on the left is a sublist within a list, whereas the matter to the right of the operator is a simple list, namely, the number 2 in a list, and it cannot be coerced to a sublist within a list. The sense of this is more obvious when you use the Equals operator, where:

Applescript:


{{1 + 1}} is equal to {2} --> false

returns false, as an English speaker would expect, while:

Applescript:


{{1 + 1}} is equal to {{2}} --> true

returns true as expected. The confusion is caused solely by the fact that the everyday English term, "contains", implies that the matter to the right of the operator should be written as a subset of the matter to the left, that is, without the outer list brackets, while the same AppleScript operator counter-intuitively requires the outer list brackets on both sides of the comparison with both Equal and Contains. This is a case where the much-vaunted "plain English" quality of AppleScript breaks down.

Just to make this all the more puzzling, note that using the "as list" coercion cannot be substituted for the outer list brackets. If the last example is written in this seemingly equivalent form, it does not return true:

Applescript:


{{1 + 1}} is equal to ({2} as list) --> false

Apparently, AppleScript sees that {2} is already a list, so the "as list" coercion does nothing. However, this obviously does return true:

Applescript:


{{1 + 1}} is equal to {2 as list} --> true

You understand why, now, don't you?

All of this has real-world consequences when you are trying to program comparisons using variables, where the variables might involve lists of lists. I discovered the principles discussed here while trying to compare colors of style runs in the Style application by Marco Piovanelli, for my Script2HTML script application. Style returns certain colors as constants, such as red, green and blue. However, it returns colors in the rest of the spectrum as RGB color values, which are lists of three integers showing the intensities of the red, green and blue components of the color, such as {13943, 19384, 64953}. You can't know in advance whether your script will encounter a document containing colors in the one form or the other. The following code snippet works correctly if the text in document 1 happens to use colors for which Style supplies constants and you want to ascertain whether the document contains one of them:

Applescript:


tell document 1 of application "Style"
set theColors to color of every style run -->{red, black}
set myColor to red
theColors contains myColor --> true
end tell

But the same script fails miserably if the colors of interest are represented as RGB color values —and I can tell you from personal experience that the syntax error is very hard to find if you aren't aware of this gotcha:

Applescript:


tell document 1 of application "Style"
set theColors to color of every style run -->{{27202, 56683, 56143}, black}
set myColor to {27202, 56683, 56143}
theColors contains myColor --> false
end tell

Adding the outer list brackets to the right of the comparison operator makes it work correctly:

Applescript:


tell document 1 of application "Style"
set theColors to color of every style run -->{{27202, 56683, 56143}, black}
set myColor to {27202, 56683, 56143}
theColors contains {myColor} --> true
end tell

The outer brackets also work with the constant, so we have a general solution:

Applescript:


tell document 1 of application "Style"
set theColors to color of every style run -->{{27202, 56683, 56143}, black}
set myColor to black
theColors contains {myColor} --> true
end tell

The moral? To be safe with all list comparisons, always make sure the outer brackets are represented on both sides of the operator just in case your list may include a sublist or two.

Offline

 

#7 2001-10-02 05:56:22 pm

Bill Cheeseman
Administrator
Registered: 2006-06-29
Posts: 158

Re: AppleScript Gotchas, Exceptions to the rules, explained...

AppleScript Gotchas

Exceptions to the rules, explained so you won't have to stay up all night tracking down those hard-to-find syntax errors.

AppleScript, like any programming language, is full of little twists and turns that defy intuition. In fact, some would say that AppleScript provides an embarrassingly generous assortment of them. You could swear your code is correct, because it looks correct and it is consistent with other syntactical constructs that work just fine. But there's some little exception to the usual rule tucked away in the manual—which you read from cover to cover just yesterday, right?—or maybe there's even (ack!) a bug in AppleScript.

Offline

 

#8 2001-10-02 05:58:30 pm

Bill Cheeseman
Administrator
Registered: 2006-06-29
Posts: 158

Re: AppleScript Gotchas, Exceptions to the rules, explained...

Why variables sometimes don't work when direct calls to an application's properties do, and vice versa

You may sometimes be stumped by the discovery that you can use an application property in a script, but when you try to assign the property to a variable and use the variable, the script stops working. You always try to write efficient code, of course. You probably heard somewhere that good AppleScript coding practice teaches you to assign an application's property to a variable if you plan to use it more than once, in order to avoid sending slow Apple events to the application repeatedly. But the plain truth is that sometimes you must call the application's property every time you need it. Don't carry this advice too far, however, because sometimes you cannot use an application's property directly and must assign it to a variable. Confused? This conundrum crops up in many applications, but let's use the Finder in the classic Mac OS as an exemplar.

These examples won't work in the Finder in Mac OS X 10.4 (Tiger), because the Finder's selection property is implemented differently than it was in the classic Mac OS.

To try out these examples in the classic Mac OS, be sure to select one or more items in a Finder window before running each example script, because some of them will deselect the current selection behind your back.

Sometimes you must not put the result of a Finder statement into a variable. The variable is unusable in these cases, because you must use the Finder property directly. This doesn't run:

Applescript:


tell application "Finder"
set theSelection to selection
get container of theSelection
end tell

Neither does this:

Applescript:


tell application "Finder"
get selection
get container of result
end tell

But this does run, because it gets the container property of the selection directly:

Applescript:


tell application "Finder"
get container of selection
end tell

And, if you know only one item is selected, you can get the first item of the variable considered as a list. This is not a general solution, but it hints at what is going on:

Applescript:


tell application "Finder"
set theSelection to selection
get container of item 1 of theSelection
end tell

Contrariwise, sometimes you must put the result of a Finder statement into a variable. The variable is essential in these cases, and you cannot use the Finder property directly. This doesn't run:

Applescript:


tell application "Finder"
select container of selection
end tell

This does run:

Applescript:


tell application "Finder"
set theContainer to container of selection
select theContainer
end tell

This is equivalent to using the result, or using get in line in parentheses. This also runs:

Applescript:


tell application "Finder"
get container of selection
select result
end tell

And so does this:

Applescript:


tell application "Finder"
select (get container of selection)
end tell

And explicitly coercing container of selection to an AppleScript list also runs, which is another hint:

Applescript:


tell application "Finder"
select container of selection as list
end tell

This all has to do with type coercions. You have to be alert to the class of the property or variable you are using. Sometimes you want to know whether a variable to which an application property is assigned retains the property's class. Other times, you want to know what class is expected by the command you are using.

In the first example, putting the Finder's selection property into a variable turns the variable into an AppleScript list. The Finder can't get the container -- or any other property -- of a list except by using a slow repeat loop to get the container of each element in the list, one at a time. Using the Finder's selection directly, however, allows the Finder to work either with the selected Finder item or, in the case of a multiple selection, a group of Finder items all at once. In the case of a multiple selection, the Finder knows how to extract the container -- or any other property -- of each element in the selection rapidly, whereas it cannot do so once the selection has been turned into an AppleScript list by assigning it to a variable. It is very important to know this about the Finder, because using the Finder's power directly, rather than in an AppleScript repeat loop, can make for substantial speed improvements and, often, simpler code, especially if you make use of the filter reference form (whose clauses).

In the second example, the same phenomenon is at work, but in reverse. The statement, container of selection, is a Finder construct. Ironically, the Finder's select command can only operate on an AppleScript [/b]list[/b], so container of selection must be converted to a list by assigning it to a variable (or using get or the result) or by explicitly coercing it with as list.

In general, when I write a Finder statement that looks like it should work, but it doesn't, I try the opposite approach. That is, if using the Finder statement directly doesn't work, I try putting it into a variable or using get or the result, or I use as list or whatever coercion may be required. If using a variable doesn't work, I try using the Finder statement directly.

I have run into the same phenomenon in other applications, and I use the same guidelines with them. Because it leads to confusion, I consider this problem to be a bug in an application's scripting support wherever I encounter the problem. After all, wouldn't you expect the Finder to know how to select one of its own items?


Filed under: Finder

Offline

 

#9 2001-10-02 05:59:48 pm

Bill Cheeseman
Administrator
Registered: 2006-06-29
Posts: 158

Re: AppleScript Gotchas, Exceptions to the rules, explained...

How to script the Macintosh desktop

If you think that scripting disks and folders is a challenge to your sanity, just try scripting the Desktop folder—if there is such a thing. There is at least the illusion of a Desktop folder, or is it an hallucination? Whatever it is, there are two ways to script it—the Finder and the info for command in the Standard Additions scripting addition—and they are not consistent in all respects.

This note was originally written in 1998 to explore some insights reported that year on one of the AppleScript mailing lists by Ed Stockly. It described inconsistencies in the treatment of the Desktop by the Finder and the info for command, way back in the days of Finder 8.1. Most of the problems noted then have long since been fixed but, surprisingly, some of them persist in Mac OS X 10.4.6, released the day this note is being revised—only three days after April Fool's Day in 2006. Let's explore how the Desktop has fared since 1998.

In Finder 8.1, getting the desktop property returned desktop of application "Finder", a reference to an object of class desktop-object. It still does today, in Finder 10.4.4. In the old days, the desktop was a special kind of container having many, but not all, of the properties of the Container and Item classes. If you still have the original 1994 edition of the AppleScript Finder Guide sitting around, you can read about its special status at pp. 20-21, 31 and 56-57. It remains true today that the desktop-object doesn't respond to all of the properties of a container or an item, although the Finder's dictionary claims that it inherits from them. For example, you'll get an error if you try to get the expandable property or the index property of the Desktop.

This was all more or less consistent with the fact that, in Finder 8.1, there existed something that appeared to be an invisible folder called "Desktop Folder". You saw it, for example, if you mounted a remote computer using file sharing, where you could open it to see its contents. The info for command even identified it as a folder with the name "Desktop Folder". In Mac OS X, of course, it is readily visible as a folder in the GUI. Just open your home folder in the Finder, and there it is, plain as day. Select it and choose Get Info from the File menu, and you'll see that its kind is reported as "Folder." It remains somewhat ambivalent, even today, about whether it is really a folder, however. If a script gets the Desktop's kind property, it returns Desktop, not folder. Over time, the word "Folder" has even disappeared from its name; its name is now simply "Desktop." But it does seem to be a folder, as the Get Info window insists.

Since you can think of the desktop as a folder, you might suppose that it has a window in which you can see the icons of the items it contains. Sure enough, there is a class desktop window in today's Finder, which inherits from class Finder window and class window. That wasn't true in Finder 8.1, where the AppleScript Finder Guide noted, at pp. 22-23, that the desktop's window was not considered to be part of the window class. For example, the Finder could not get the desktop window's bounds or position property in Finder 8.1. This was true, although you could refer to container window of desktop and window of desktop. Both of those properties returned content space of desktop in Finder 8.1. Since that class no longer exists, they return window of desktop of application "Finder" in Finder 10.4.4. To compound the confusion, even in the days of Finder 8.1 you could get the bounds of the Desktop by ignoring the Finder and getting the folder window property of the info for command in the Standard Additions scripting addition. Today, even the Finder allows you to get the Desktop's bounds and position properties. However, the Finder remains ambivalent about whether it is a window like other windows. If you ask the Finder to get every window, the returned list does not include the Desktop's window, even though that is the one window that is always open! Its special status as the only always-open window is reflected in the fact that it always has an id of 1.

Despite these inconsistencies, the Desktop is much easier to handle in Mac OS X 10.4.6. Here are the specific bugs identified by Ed Stockly in the treatment of the desktop in Finder 8.1, which seriously interfered with scripting of the desktop, reconsidered in light of today's Finder:

(1) Getting name of desktop in Finder 8.1 returned "Desktop". This is what we call the desktop in conversation, to be sure, but it was not consistent with what was returned when you got the name of any other container; namely, its full name without a trailing colon. The full name of the Desktop Folder in the GUI in Finder 8.1 was "Desktop Folder", just as it was in the name property of the desktop in the info for command. As a result of this discrepancy, name of desktop could not be used in Finder scripts, as the name of a disk or folder could, by simply appending a colon. Coercing path to desktop to a string correctly returned "<name of startup disk>: Desktop Folder:", not only with the trailing colon but including the word "Folder". Therefore, a workaround was to extract the name from this string using the standard (if somewhat awkward) method of changing AppleScript's text item delimiters to ":" and getting the last text item of path to desktop as string (after removing the trailing colon). An easier workaround was to get the name property from the info for command. It would have been easier still if name of desktop had correctly returned "Desktop Folder".

How does Finder 10.4.4 compare? Getting name of desktop still returns "Desktop", but the problems this posed in the past have been eliminated because that really is the name of the Desktop today. The info for command has fallen into step, returning "Desktop" in its name property. In Mac OS X 10.4.6, path to desktop returns an alias which, when coerced to a string, correctly gives the path to "Desktop:" with the ending colon. Everything works as it should.

(2) Similarly, in Finder 8.1, coercing the desktop property to an alias returned alias "<startup disk>:Desktop" without the word "Folder" or a trailing colon. This was a spurious reference and could not be used. For example, get name of (desktop as alias) generated error number 32768029, the spurious error number that was improperly returned by orphaned aliases and aliases to the desktop folder in Mac OS 8.0. The workaround was to get (desktop as string) as alias, because desktop as string returned the correct "<name of startup disk>:Desktop Folder:". It would have been easier if desktop as alias had correctly returned alias "<name of startup disk>:Desktop Folder:".

How does Finder 10.4.4 compare? Getting desktop as alias correctly returns an alias with a path string ending in "Desktop:". Everything works as it should.

(3) In Finder 8.1, getting container of desktop returned «class ****» "Finder". This was clearly spurious, as the desktop had no container in the classic Mac OS.

How does Finder 10.4.4 compare? You can guess. The Desktop folder is now contained in your home folder, and getting container of desktop correctly returns a Finder reference to your home folder.

All is not roses, however. In Finder 10.4.4, the bounds of desktop on my PowerBook are {-33, -33, 31, 31}. Whatever that means, its bigger than the folder window property returned by the info for command, {0, 0, 0, 0}.

Offline

 

#10 2001-10-02 06:05:58 pm

Bill Cheeseman
Administrator
Registered: 2006-06-29
Posts: 158

Re: AppleScript Gotchas, Exceptions to the rules, explained...

How to work with the range reference form

Quick, what is the result of each of these statements?

words from paragraph 2 to paragraph 4
words of paragraphs 2 thru 4
text from paragraph 2 to paragraph 4
text of paragraphs from 2 to 4

They're all different, and keeping them straight can give you a lot of power.

The AppleScript Language Guide describes the "range" reference form as returning a container in the form of a list of the individual objects within the specified range. Examples are given in two different forms, words 12 thru 24, which specifies the range by using a pair of indices, and words from paragraph 3 to paragraph 5, which specifies the range by using a pair of "boundary objects" (paragraph 3 and paragraph 5, in the example). You can substitute beginning and end for the first and second boundary references, respectively, when you use the boundary object form. When specifying a range, thru (or its synonym, through) is proper when you use simple integer indices (if you write words from 12 to 24, the compiler will transform it to the canonical form, words 12 thru 24), whereas from is proper when you use boundary objects. All of these cases return a simple list of individual items (words, in the examples).

The statement words of paragraphs 2 thru 4 looks similar, but it is not a simple range. Instead, it is a compound statement calling for a property of a simple range. It may be best understood by adding parentheses, although they are not syntactically necessary: words of (paragraphs 2 thru 4). Here, paragraphs 2 thru 4 is a container consisting of an indexed range of paragraphs, and words (a synonym for every word) is a property of that container. The range within this compound statement, paragraphs 1 thru 2, yields, like any range, a simple list consisting of the two paragraphs. In this case, each item in the list is a string consisting of the complete text of the corresponding paragraph. When you then ask for the words of that list (or every word of it), you end up with a complex, nested list, where the two paragraph strings in the original container have been turned into two lists containing the individual words in the respective paragraphs, nested within the original outer list.

An interesting but confusing instance of these principles involves the text property of most text objects, including all AppleScript strings. Whereas the range, words from paragraph 2 to paragraph 4, returns a list of words, text from paragraph 2 to paragraph returns a single, long string. While this may feel wrong, it is really very simple and does not involve any inconsistency. The text property is considered by AppleScript, oddly, to be a plural property, not a plural synonym for every word. There can be only one text of any text object, and it is improper to say every text. The statement text from paragraph 2 to paragraph 4 calls for a simple range using the boundary object form, and the result is a single string because there is only one text in the range. This statement, like any statement that returns a list that happens to contain only one item, could just as well have returned the string enclosed in list brackets, on the model of the syntactically identical words from paragraph 2 to paragraph 4. And many people think it should. The reason it does not has nothing to do with the syntax of ranges, but is only because AppleScript commonly coerces any list result that contains a single item into that single item without the list brackets.

This is all wonderfully precise and horribly confusing, both at the same time. To make matters worse, there are scriptable text editors that blur these distinctions. It's hard to blame their developers, because Apple's Language Guide itself got one of its examples wrong, at the top of page 139 in the original 1993 edition, requiring a correction in an Addendum. The erroneous example claimed that text of words 1 thru 4 returns a single string, when in fact it returns a list consisting of each word in the range. If you've been paying close attention, you know why. Because -- obviously!?! -- this statement does not call for a simple range but for the text property of a range. It is no different than, say, font of words 1 thru 4, which returns a list consisting of 4 font identifiers. The giveaway is the term of, which does not appear in either form of a simple range reference like text from word 1 to word 4. The statement text of words 1 thru 4 is best understood as text of (words 1 thru 4). The range in parentheses returns a list of individual words, and the text property of that list returns a list of each of those words as text. Since the text of each word is the word itself, the results are identical.

Offline

 

Board footer

Powered by FluxBB

[ Generated in 0.078 seconds, 8 queries executed ]

RSS (new topics) RSS (active topics)