Collection having a count but unable to access items - applescript

I'm trying to automate some things MS PowerPoint through AppleScript. I'd like to fetch the (shape) selection of the active window. Reading the dictionary, my guess was:
tell application "Microsoft PowerPoint"
set sel to shape range of selection of active window
count of sel's shapes -- returns 2 for specific case
-- class of sel's shapes -- throws a compilation error "object you are trying to access does not exist"
set i to item 1 of sel's shapes -- i not set but this line does not throw an error
i -- error: the variable i is not defined
end tell
with comments indicating what happens when a specific line is run. Interestingly, sel's shapes does have a count, but I can't fetch any item from it. My first instinct was that sel's shapes must be of some other datatype, but class sel's shapes also throws an error, complaining that sel's shapes does not exist.
Q: what's going on here? How can count of be defined (and work!) whilst item 1 of and class of are not?

It turns out that this is due to the way reference forms are handled in AppleScript (https://developer.apple.com/library/archive/documentation/AppleScript/Conceptual/AppleScriptLangGuide/reference/ASLR_reference_forms.html#//apple_ref/doc/uid/TP40000983-CH4g-120522).
In this case, the "shape range" does not contain any "items". It only contains "shapes":
set s to shape 1 of shape range of selection of active window
works just fine:
s's left position -- Returns an actual value
Lesson learned: take care to use the correct class of item that you are trying to get from a collection.

Related

Deselecting a file from a selection in Finder via AppleScript

I've been searching everywhere for around 6 hours to find a way to deselect file items from a previously selected array of items in Finder via AppleScript via Automator with a Quick Action. I've seen deselect all items, I've seen add items to a selection/list for process, but not remove items from a selection...
The goal: I usually have to process a lot of images and get them approved by supervisors before providing them on our servers for use. All of which come in a set of a transparent PNG and an opaque JPG. I only need to upload the JPG files over for approval. So, let's say I have 30 images. 15 of which are JPGs, 15 of which are PNGs. I would love to be able to select all 30 images, and deselect the 15 of which are PNGs (via the script), left with a selection of all the JPGs.
There are other ways to achieve this, but it would be exceptionally helpful if I could just select the images, run the script, and then move them over for upload (website upload). That appears to be the fastest (by hand) way to go about achieving the end result.
What I know: The selection property. Through my research, I've seen the selection property called in a small amount of different ways, but am unsure how to properly manipulate it. I've tried converting the selection to a string and splitting that by detecting a word to split from (like "Macintosh HD") allowing them to be manipulated in a list, and then potentially removing them from a selection...? I'm also very unsure how to just properly manipulate a property as-is...
I've also attempted to just use the given files via input through Automator AppleScript. But alas, that still leads to knowing how to remove/deselect a file from the current selection.
Anybody know how to achieve this?
I appreciate all the time spend and information in advance. Thank you.
There are two approaches you can use for this, one in pure AppleScript, the other using Automator actions.
The pure AppleScript solution is take the selection as a list of files, copy the JPG files to another list, and set the selection to that new list, like so:
set new_selection_list to {}
tell application "Finder"
set selection_list to selection
repeat with this_item in selection_list
if name extension of this_item is "jpg" then
copy this_item to end of new_selection_list
end if
end repeat
set selection to new_selection_list
end tell
Of course, if you're passing these files in through the input variable in automator, you'd use that rather than testing the selection again, but the principle still stands.
Alternately, you can set up your workflow with a Filter Finder Items action, like so:
I'd recommend the second approach. It's likely significantly faster, and takes less programming.
Although not really deselecting items per se, the Finder has a reveal command that will show the specified items. One approach would be to edit the list of selected items, and then reveal the result.
Note that the sorting of selected items varies depending on whether it is from an Automator action (sorted by name) or a Finder tell statement (the order selected), and items mixed from multiple folders (as can happen in an Automator workflow), will result in multiple Finder windows.
The following Automator Quick Action example will remove the first two items from the input items and reveal the rest of the items in the Finder window:
Workflow receives current files or folders in Finder.app
Run AppleScript:
on run {input, parameters}
set newSelection to input
if (count input) > 2 then -- guard for following statement(s)
set newSelection to items 3 thru -1 of input -- or whatever keep/remove criteria
end if
tell application "Finder" to reveal newSelection
return newSelection
end run

AppleScript - Get the Bounds of Every open Window

I have been playing all day with getting this down. The goal being to generate an AppleScript which generates yet more AppleScript. I'll explain in more detail.
THE DESIRED END RESULT: After arranging your windows how you like them follow up with launching this script. This will copy to your clipboard the necessary script to automatically launch, position, and resize the application windows to the current configuration. This would be so that I could send the script to other people who could then, upon launching this script, design their own custom layouts which could then be either pasted into Script Editor or possibly made into a service and bound to a hotkey using Automator.
WHAT I'M CURRENTLY TRYING TO OVERCOME: I can't seem to get it to list the bounds for each window. I am currently running this script.
tell application "System Events"
set openApps to name of every process whose background only is false
repeat with theItem in openApps
set checkApp to theItem
tell application checkApp to get the bounds of the front window
end repeat
end tell
This spits out the following error every time without exception:
error "System Events got an error: Can’t get application \"Finder\"." number -1728 from application "Finder"
I'm not asking that someone solve the entire problem for me. Though any advice on the matter is always appreciated. The current hurtle is just to get the bounds of each window set to variables for use elsewhere in the script.
This answer focusses on the issue stated under What I'm Currently Trying To Overcome. I've interpreted The Desired End Result to be background information that provides context to your immediately-pressing issue (and this is really interesting/useful to provide, so thank you).
TL;DR
tell application "System Events"
set _P to a reference to (processes whose background only = false)
set _W to a reference to windows of _P
[_P's name, _W's size, _W's position]
end tell
This will get you the list of size and position properties for each application process. Below is a rather verbose deconstruction of where and why your script went wrong; followed by a proposed solution after considering other equally viable solutions before settling on the base code above. I will try and trim the wordiness of this answer another day when I'm a bit less tired, but for now, I hope the deeper insight helps.
The Issues
▸ Starting with the specific error your script is throwing out, it's necessary to point out that, generally speaking, tell application blocks don't often nor should rarely need to be nested. You opened a tell block to target System Events, which was necessary to get the process names; that's the point when you should have either closed the tell block, or used a simple tell command on a single line:
tell application "System Events" to set openApps to the name of every process...
(no need for end tell in this case).
However, as your tell block remains opens, the commands that come next are also directed to System Events. The first application process that your script evidently finds belongs to Finder, and when your script (inside the repeat loop) is instructed to tell application "Finder" (by way of the checkApp variable), the error is thrown because you've actually told System Events to tell Finder to do something, and System Events has no understanding of how to do interact with an application object.
▸ This leads us onto the following line, with which are a couple of problems pertinent to your script (plus a more general noteworthy aside† about which I have left a footnote):
tell application checkApp to get the bounds of the front window
This line will only work for applications that are (Apple)scriptable. Not all applications can be controlled by AppleScript—it's a feature app makers choose to implement (or choose not to, as is ever more frequently the case) when developing their software for macOS.
Those that are scriptable will (if they follow Apple's guidelines) have defined window objects that each contain a bounds property. Those that aren't scriptable won't have either of these. That's when another error will be thrown.
Another "minor" issue is that not all of the processes which are background only necessarily have windows, and thus a front window. Finder is never background only, but sometimes has no open windows. Therefore, even once the error you're getting has been fixed, this is the next error that crops up if there are no open Finder windows.
A Solution
Although you can't obtain a bounds property of a window belonging to a non-scriptable application, System Events can retrieve some properties that belong to objects of an application process. This is independent of whether an application to which the process belongs it itself scriptable or not, because System Events is the application we are targeting, which is scriptable, and happens to have access to similar information pertaining to each process's window objects (NB. see the footnote below, but the window object belonging to a process is not the same as the window object belonging to an application, so cannot be used interchangeably, and nor can their properties).
Although there is no bounds property for the window objects owned by processes of System Events, there are two other properties, which, together, are equivalent to bounds: position and size. The position gives the {X, Y} coordinate of the top-left corner of the window relative to the top-left corner of the screen (which is defined in this context as being the origin at {0, 0}); the size gives the {X, Y} pair of dimensions that represent the window's width and height, respectively.
Thus, given an hypothetical bounds property value of {𝑎, 𝑏, 𝑐, 𝑑} for a specific window, the relationship to size: {𝑥, 𝑦} and position: {𝑤, ℎ} can be expressed thus:
{𝑎, 𝑏, 𝑐, 𝑑} = {𝑥, 𝑦, 𝑥 + 𝑤, 𝑦 + ℎ}
The other consideration is getting a list of processes that actually have windows. There are various ways to go about doing this, each with advantages and disadvantages, which include brevity of code, execution time, and accuracy of the retrieved list of windows.
Your original method of retrieving a process list discriminated by background only is one of the fastest and there are only a few situations where false negatives lead to omissions from the list (namely, menubar applications that register as background only yet clearly have a window; the Instagram app Flume is an example).
You can instead discriminate by the visible property, which is just as fast, but I feel less apt in situations where an application is hidden and would need to be unhidden before recording its window properties; or, again, some menubar apps that register as background only, not visible, yet clearly are visible_ with a window in the foreground.
The method that is most reliable in retrieving all windows in any circumstance is, sadly, quite slow, but does produce a list a that's easy to work with and doesn't need further processing.
In our current situation, however, I think it's sensible to choose the option that gives speed and will work for most applications, which is your background only filter. As the list produced from this yields some false positives (Finder, for example), we need to process the list a bit before it's reliable to utilise.
Here's the code to retrieve a nested list containing a) a list of named processes; b) a list of window sizes for each of the named processes; and c) a list of window positions for each of the named processes:
tell application "System Events"
set _P to a reference to (processes whose background only = false)
set _W to a reference to windows of _P
[_P's name, _W's size, _W's position]
end tell
If you close your Finder windows, you'll see it still appears in the first list by name, but the second and third lists have an empty list {} where its windows' sizes and positions would otherwise be. So just be sure to do some checking before you try and reference the items of each sublist.
Compare and contrast it with this slower, but more accurate solution:
tell application "System Events"
set _P to a reference to (processes whose class of window 1 is window)
set _W to a reference to windows of _P
[_P's name, _W's size, _W's position]
end tell
It takes twenty times as long to run on my system, yielding an albeit solution that can identify menu bar apps, regular apps, and hidden apps that have windows, which might end up being essential for your ultimate goal. But if you work most often with the more regular apps, then it's fairly clear which method is more suitable.
†The more generalised, potential problem—that doesn't really apply to your script as it stands, but is useful to know for future scripts if you attempt to use a similar technique—is the use of a variable as a means of referencing an application that has an undetermined name at compile time (before the script is run).
bounds is a fairly ubiquitous property of all scriptable applications's windows, which is why you (almost) get away this technique here. However, if you picked a property or object class that Script Editor specifically does not contain, AppleScript won't recognise the terminology and assume it's simply a variable name. In order for application-specific terminology to be recognised, a reference to that specific application needs to be made in some form, either by way of a tell application "Finder" to... or enclosing the relevant lines inside a using terms from application "Finder" block.
A good rule-of-thumb is that applications generally need to be known and specified at compile time in order to receive AppleScript commands. There's no easy way to cater for varying options without using an if...then...else if... series of conditional blocks for each possible application.
This is a source of frustration particularly when it comes to applications that are seemingly similar in nature and, moreover, have a similar AppleScript dictionary, yet still don't share their terminology with one another for general use. I'm thinking specifically of Safari and Chrome, both of which have objects referred to as tabs, making it easy to forget that a Safari tab is still a different class of object to a Chrome tab, and any attempt to write generalised code to script either or both will meet with failure.

Automator - AppleScript recording and changing into values

I'm recording an Automator Script to help me scrape an URL that I use to get information from a week at a time. I would like to specific value to be a vaiable.
When I have recorded the script I would like to have the specific values (i.e. 2018-01-02) be able to change into a variable that can be changed within the script (i.e. a day at a time to 2018-01-03). I can not change the date to a variable in Automator. If I copy the script to a text document I can not get it back into Automator.
Is there a way to get this done?
The script as text looks like this:
-- Click the “2018-01-02” button.
delay 2.631525
set timeoutSeconds to 2.000000
set uiScript to "click UI Element \"2018-01-03\” of group 7 of UI Element 1 of scroll area 1 of group 23 of UI Element 1 of scroll area 1 of group 1 of group 1 of tab group 1 of splitter group 1 of window \"Historical Data Feed\" of application process \"Safari\""
my doWithTimeout( uiScript, timeoutSeconds )
in Automator it looks like:
Click the "2018-01-02" button
#Matts gave a fine answer about using variables in Automator. But, having discussed the nature of your objective in more depth, it's clear that knowing how to reference variables in Automator is not at all the issue, and won't help you.
In fact, don't even bother with Automator. It's not going to do what you want it to. The whole endeavour of trying to automate physical interaction with Safari means it's going to involve a fair bit of AppleScripting, and it's the worst kind—GUI (graphical user interface) scripting.
Come out of Automator and use Script Editor. Make sure Script Editor has relevant accessibility permissions in order for GUI scripting to work. You can do this by going to System Preferences > Security & Privacy > Privacy > Accessibility, unlocking the padlock in the bottom left of the window, and then finding Script Editor in the list of apps allowed to control your computer. Make sure it's checked.
Briefly, GUI scripting is pretty much doing just that, in a nutshell: controlling your computer, in a way that allows it to issue mouse clicks and press buttons or menu items as if the user were there physically doing it themselves.
But it's a hell of a nightmare figuring out how to reference objects in the GUI hierarchy of on-screen elements that can be pressed or clicked.
I think your snippet of Automator-manufactured code was misleading you:
click UI Element "2017-01-03” of group 7 of UI Element 1 of scroll area 1 of ¬
group 23 of UI Element 1 of scroll area 1 of group 1 of group 1 of tab group 1 ¬
of splitter group 1 of window "..." of application process "Safari"
I think you saw UI Element "2017-01-03" and deduced that one must be able to access date-specific data by substituting the appropriate date string in for the one that's there currently. Hence, why you asked about using variables in Automator. But it wouldn't have worked. That date string just reports the name of the element (which happens to be something called a static text element) that contained a piece of text with a date in it, but not one that was editable. Therefore, referencing UI Element "2017-01-02" would just throw an error, because no such element exists.
So here's what I've come up with so far. It's by no means a full package: I haven't written the script that will achieve your objective straight out of the box, but I'm definitely providing you with all the necessary components for you to be able to piece together a finished product. Believe me, I did the most grueling part of it, which a painstaking search to obtain all the specific object references for every button you will want to be able to click within the GUI hierarchy.
First, an important note: I didn't use the URL you posted in the comments to do my GUI inspection. The webpage was far too busy and convoluted, which would have made the job much harder. But luckily, they had a link to a page that contains only the widget with which we'll be interacting. But, because the GUI makeup of the page is different (the widget is identical to the one your original link, it's just the rest of the page that's different), none of the code I've written will work with your original link. So you'll have to use the URL that I've provided in the script, which is https://www.dukascopy.com/plugins/fxMarketWatch/detach.php?id=ct23.
Referencing the GUI objects
Declare some property variables, which you can change according to what month and year you want to retrieve the datasets from. You can also specify your instrument, by either the short abbreviation code, or its full description:
property |URL| : "https://www.dukascopy.com/plugins/fxMarketWatch/detach.php?id=ct23"
property Instrument : {|short|:"AAPL.US/USD", |description|:"APPLE INC"}
property |month| : "December"
property |year| : "2017"
This next part is where System Events and the Safari process are employed, which is the root of the GUI scripting commands. I also lumped in with it a reference to all the elements that were common to all ones we'll be interacting with. Otherwise, you'd have to write them out over and over, whereas it's easier to just reference them once as a group of UI elements that form the basis of a tell... block:
tell application "System Events" to ¬
tell application process "Safari" to ¬
tell (window named |URL|) to ¬
tell UI element 1 of scroll area 1 of group 1 of ¬
UI element 1 of scroll area 1 of group 1 of ¬
group 1 of tab group 1 of splitter group 1
.
.
(* The rest of our code goes
inside this tell block *)
.
.
end tell
Note also that the commands are being sent to the window that's named the same as the URL of the webpage. So this script assumes that the webpage has already been loaded up in Safari—it doesn't load it for you (although that wouldn't be difficult to implement).
Also make sure you log in after you load the page. If you don't, clicking the download button brings up the login box, which will interrupt the whole script and throw an error. But, once you're logged in and click the box to "stay logged in", from then on it will proceed to the download options by itself.
The next part is the meat of the endeavour. This is where I define variables, each of which reference a GUI object that you will want to be able to issue clicks and presses to from the script.
These three form the main components for each row of available instruments that you can select:
set Instruments to a reference to groups of group 1 of group 1 of group 3
--> All Instruments (410 of them)
set Instrument_Abbreviations to a reference to static texts of group 1 of group 2 of Instruments
--> The short text code for each instrument
set Instrument_Descriptions to a reference to static texts of group 2 of Instruments
--> The long text for each instrument
This one is the pair of elements for selecting the timezone, local or GMT:
set LOCAL_GMT to a reference to checkboxes of group 1 of group 7 of group 4
This next block of elements took some work to navigate through. These are the ones responsible for selecting the date. Pressing the date button allows a calendar pop-up to appear on-screen, in which you choose the year, month and day. This will also form the core of what will become your program loop, as you use the variables to cycle through the days of a month to retrieve the data files for all of, say, December.
The thing about menus and objects such as this calendar that "pop up" is that they don't physically exist until the moment they are created and appear on our screens. Therefore, there's potential in GUI scripting for errors to get thrown when the script tries to reference an object that will be there at some future point, but is not currently there and doesn't exist yet. Using a reference to is one way to cater for this problem, and I've done this for most of these variable declarations.
set Date_button to a reference to button 1 of group 3 of group 4
--> The date button
set Calendar_year_button to a reference to button 2 of UI element 2 of row 1 of table 1 of group 5
--> The button that pops up the year menu (as shown)
set Calendar_year_list_items to a reference to static texts of groups of group 1 of group 1 of UI element 1 of row 1 of table 1 of group 5
--> The year menu
set Calendar_month_button to a reference to button 2 of UI element 1 of row 1 of table 1 of group 5
--> The button that pops up the month menu
set Calendar_month_list_items to a reference to static texts of groups of group 1 of group 1 of UI element 1 of row 1 of table 1 of group 5
--> The month menu
set Calendar_days to a reference to static texts of UI elements 2 thru -1 of rows 3 thru -2 of table 1 of group 5
--> All the day text objects visible at any one time
--> 6 rows x 7 days = 42 text objects
The next three variables are for clicking the "Tick" button, which brings up a pop-up menu. Last in this group are the pair of BID/ASK buttons.
set Tick_button to a reference to static text 1 of group 1 of group 1 of list 2 of group 1 of group 4
--> The tick button
set Tick_menu_items to a reference to menu items of list 1
--> The pop-up menu items as shown below
set BID_ASK to a reference to checkboxes of group 1 of group 2 of group 4
--> The "BID" and "ASK" buttons
Next is the download button:
set Download_button to a reference to button "Download" of group 8 of group 4
Now, when the download button is clicked, the GUI goes under a reconstruction in this bottom section. Firstly, a progress bar is shown in place of all the buttons that temporarily vanish:
I ignored these elements as I didn't see any need to have access to the cancel button in such a short space of time.
Then another reconstruction and the "Save As CVS" and "Reset" buttons come into existence. We need both:
set Reset_button to a reference to button "Reset" of group 2 of group 4
set Save_button to a reference to button "Save as .csv" of group 2 of group 2 of group 4
And now we have all the GUI objects we need access to, stored by reference in a set of variables that we (by which I mean you) can make use of to carry out the actual process of automation.
Performing Some Actions
Some of the variables contain references to elements that can perform actions straight out of the box, e.g. click Date_button. However, others will throw an error or not perform quite as you expect them to. This is because some of the variables contain a reference to a single object, such as Date_button, whilst others contains references to several different objects or groups of objects, in a nested list, such as Calendar_year_list_items.
Before utilising these nested lists, we have to flatten them into a single list. The following function will do this, and can be added at the very end of the script (after the end tell:
on flattened(L) -- recurse nested lists and return a one-dimensional list
if class of L is not list then
return [L]
else if length of L is 0 then
return L
else
return flattened(the first item of L) & ¬
flattened(the rest of L)
end if
end flattened
Now we have everything we need to start getting these objects to perform some actions. For this, I think it's best to write another function. It can just before the on flattened function declaration:
on Download_CSV_Data for _instrument from _date
local _instrument, _date
set [_day, _month, _year] to [day, month, year] of date _date
global Instruments, Instrument_Abbreviations, Instrument_Descriptions, LOCAL_GMT, ¬
Date_button, Calendar_year_button, Calendar_year_list_items, Calendar_month_button, ¬
Calendar_month_list_items, Calendar_days, Tick_button, Tick_menu_items, ¬
BID_ASK, Download_button, Reset_button, Save_button
tell application "System Events"
click 1st item of my flattened(Instrument_Abbreviations whose name is _instrument)
-- click 1st item of my flattened(Instrument_Descriptions whose name is Instrument's |description|)
click Tick_button
click (Tick_menu_items whose name is "Second")
click (BID_ASK whose name is "ASK")
click (LOCAL_GMT whose name is "GMT")
click Date_button
click Calendar_year_button
click 1st item of my flattened(Calendar_year_list_items whose name is _year)
click Calendar_month_button
click 1st item of my flattened(Calendar_month_list_items whose name is (_month as text))
click 1st item of my flattened(Calendar_days whose name is _day)
click Download_button
repeat while not (exists Reset_button)
delay 1
end repeat
click Save_button
click Reset_button
end tell
end Download_CSV_Data
In order to download a data file pertaining to a specific date, all you have to do is call that function. You would place that function call between the first end tell statement (after the variable declarations), ad before the first function declaration, so your script will look something like this:
property |URL| : "https://www.dukascopy.com/plugins/fxMarketWatch/detach.php?id=ct23"
property Instrument : {|short|:"AAPL.US/USD", |description|:"APPLE INC"}
property |month| : "December"
property |year| : "2017"
-- GUI object variable declarations
tell application "System Events" to ¬
tell application process "Safari" to ¬
tell (window named |URL|) to ¬
tell UI element 1 of scroll area 1 of group 1 of ¬
UI element 1 of scroll area 1 of group 1 of ¬
group 1 of tab group 1 of splitter group 1
.
.
.
end tell
(*** Insert function calls here ***)
Download_CSV_Data for Instrument's |short| from "17/12/2017"
on Download_CSV_Data for _instrument from _date
.
.
.
end Download_CSV_Data
on flattened(L) -- recurse nested lists and return a one-dimensional list
.
.
.
end flattened
If you're in America, you might have you switch the month and day around in the short date. So where I've written Download_CSV_Data for Instrument's |short| from "17/12/2017", you might have to write Download_CSV_Data for Instrument's |short| from "12/17/2017" (use whatever short-date format your computer system uses).
You can set variables in Automator, by using the "run applescript" command. Search for the command by typing "applescript" into the search box in automator. Then you can set the variable within the script. Here's an example of how it could be done:
set dateVar to "2018-01-02"
-- Click the “2018-01-02” button.
delay 2.631525
set timeoutSeconds to 2.0
set uiScript to "click UI Element \"" & dateVar & "\" of group 7 of UI Element 1 of scroll area 1 of group 23 of UI Element 1 of scroll area 1 of group 1 of group 1 of tab group 1 of splitter group 1 of window \"Historical Data Feed\" of application process \"Safari\""
my doWithTimeout(uiScript, timeoutSeconds)
You could also use other methods of setting the date. This article has a lot of information about using dates with Applescript https://macscripter.net/viewtopic.php?id=24737
The UI element numbers can also vary, so you would need to account for this to get your script to work with variables.
Edit: As you're getting assistance to use another method, I can see how my answer wouldn't be relevant to your situation now. Although Applescript will get the job done, it's quite a difficult way to accomplish the end goal of downloading the files. I've found that using Selenium, Firefox and Python to be a far easier way to automate the browser to download files. It works best when you understand how to use xpath, and occasionally execute some javascript, but if it's something you're doing more of in the future, you might want to consider this method. You can also use tag, id or class selectors instead of xpath.

Does anyone know why an object would miss a property?

We have a script that export our Indesign documents to HTML and one of the routine is to export tables. In this script we go throught each Tables->Rows->Cells and evaluate some of the properties (i.e. bottomEdgeStrokeType, topEdgeStrokeType, etc...) and transport them to HTML.
Now yesterday we had problem converting one particular document because some cells were missing the "bottomEdgeStrokeType" property entirely. I've discovered this by outputting the properties of each cells and compare the faulty ones with the others.
This line bellow was trowing the error: "Invalid object for this request.".
var cellType = cell["bottomEdgeStrokeType"];
Now, to fix this I've wrapped this around a try catch block to handle the case when it's not there, but now what is puzzling me is how on earth can Extendscript instantiate an object with missing properties?
Indesign version: CS5.5
A property is not only 'undefined' if it cannot exist at all (such as asking for the parent text frame for a character in overset text), but InDesign's Javascript engine also fails to return a reasonably accurate result for multiple values.
If you ask for "the" point size of a paragraph, where this paragraph contains multiple sizes, poor ID does not consider to return something like CONSTANT.Mixed, or the first value only, or (what I might have preferred) an array of the values; it returns undefined instead.
So how can a single table cell have multiple bottom strokes? If the cell underneath it is split into multiple cells, and one has a "top" stroke but the other has not.
It's difficult to recommend an adequate solution. You could first test if the current cell is "merged" (as far as InDesign's internal table model is concerned) with columnSpan; and if so, iterate over the number of columns spanned and test the next row's cells for their top stroke, which in theory should match the bottom stroke of the cell above. (I find myself wondering if this is always true. ID's table model is ... weird. It's not entirely like a HTML table, despite the functional overlaps.)
If columnSpan is greater than 1 and equal to the number of cells immediately below the current one, you could test if all of their "top" values are the same and if so use that value. (I never tested this so ID's table model may simply fail because a cell is merged, regardless of same-values or not.)
One could attempt to flag this cell's next row to output "top" strokes as well -- but alternating top and bottom strokes may not align nicely in CSS, side to side. Perhaps it's best to translate only the first top stroke value to "the" bottom stroke property for your current cell, and fix up manually where needed (how?) or, a reasonable action, hope that no-one will ever notice it.

Applescript, multiple monitors and maximum window sizes

I'm looking into windows management on OS X (trying to achieve something like WinSplit Revolution), and I need to use applescript to pull out the maximum size of a window on a given monitor. Currently I've found:
tell application "Safari"
set screen_width to (do JavaScript "screen.availWidth" in document 1)
set screen_height to (do JavaScript "screen.availHeight" in document 1)
end tell
This works for the main monitor on a multiple monitor setup, but doesn't provide at all for secondary monitors. I've also read into the method detailed here, and this obviously doesn't work for multiple displays in an efficient manner. Is there an effective way to get the maximum window size of multiple displays with applescript?
There have been many attempts to get monitor dimensions by reading system plist files. See http://macscripter.net/viewtopic.php?id=15425
If you have access to AppleScript studio (ASS) terms, you can make method calls into NSScreen to get all the monitors then ask them for their sizes. The easiest way to use ASS terms in a plain AppleScript is to compile an empty ASS application in Xcode. In this example, I've created a simple ASS app name ASSAccess which gives me access to ASS terms:
tell application "ASSAccess"
-- The first item in this list will be your main monitor
set screensArray to call method "screens" of class "NSScreen"
set Displays to {}
if {} is not screensArray then
repeat with displayNo from 1 to (count screensArray)
-- call visibleFrame instead to take into account the Dock and menubar
set dims to call method "frame" of (item displayNo of screensArray)
copy dims to end of Displays
end repeat
end if
end tell
Now, the issue I run into with these dimensions is the coordinate system. NSScreen's frame method gives you a rectangle with the origin in the LOWER left hand corner of the main monitor. Then, any secondary screens are given relative to that origin. If you are trying to determine if a window is within these bounds, window position is giving within a coordinate system with the origin at the UPPER left hand corner. This is a whole mess of conversion that I haven't figure out yet.

Resources