The macosxhints Forums

The macosxhints Forums (http://hintsforums.macworld.com/index.php)
-   OS X Developer (http://hintsforums.macworld.com/forumdisplay.php?f=27)
-   -   Applescript: How do I select menu item with Quotes (http://hintsforums.macworld.com/showthread.php?t=74854)

JordanX 07-06-2007 11:02 PM

Applescript: How do I select menu item with Quotes
 
Hi all, I'm new to this forum, but not new to Macs or Programming. Just got my iPhone and was looking to do some automating.

I'm trying to automate the clicking of the File->Sync "Jordan's iPhone" menu item in iTunes. Notice the Quotes around "Jordan's iPhone".

Here's what I have:

PHP Code:

    --set inputStr to "iTunes, View, Full Screen"
    
set inputStr to "iTunes, File, Sync " "\"Jordan's iPhone\""
    
tell application "iTunes" to activate
    set AppleScript
's text item delimiters to ", "
    set x to (every text item of inputStr)
    menu_click(x) 

The first "set inputStr" is a test, and Full Screen works great with a movie playing. The second "set inputStr" statement is where the problem is. The menu item for Sync on iTunes adds Quotes around the item. Here's the ERROR:

Quote:

click menu item "Sync \"Jordan's iPhone\"" of menu "File" of menu bar item "File" of menu bar 1 of application process "iTunes"
"System Events got an error: NSReceiverEvaluationScriptError: 4"
If I do it with out the quotes (Sync Jordan's iPhone). I get the same error:

Quote:

click menu item "Sync Jordan's iPhone" of menu "File" of menu bar item "File" of menu bar 1 of application process "iTunes"
"System Events got an error: NSReceiverEvaluationScriptError: 4"
I've also tried
PHP Code:

set inputStr to "iTunes, File, Sync " quote "Jordan's iPhone" quote 

Anyone have any ideas on how to handle the quotes?

The Method menu_click was provide by jacobolus (maxosxhints), and all works fine.

PHP Code:

on menu_click(mList)
    
local appNamecarcdr
    
    
-- Validate our input
    
if mList's length < 3 then error "Menu list is not long enough"
    
    -- Set these variables for clarity and brevity later on
    set {appName, car} to (items 1 thru 2 of mList)
    set cdr to (items 3 thru end of mList)
    
    -- This overly-long line calls the menu_recurse function with
    -- two arguments: cdr, and a reference to the top-level menu
    tell application "System Events"
        my menu_click_recurse(cdr, ((process appName)'
(menu bar 1)'s (menu bar item car)'(menu car)))
    
end tell
end menu_click

on menu_click_recurse
(mListparentObject)
    
local carcdr
    
    
-- `car` = first item, `cdr` = rest of items
    set car to item 1 of mList
    
if mList's length > 1 then set cdr to (items 2 thru end of mList)
    
    -- either actually click the menu item, or recurse again
    tell application "System Events"
        if mList'
s length is 1 then
            click parentObject
's menu item car
        else
            my menu_click_recurse(cdr, (parentObject'
(menu item car)'s (menu car)))
        end if
    end tell
end menu_click_recurse 


tw 07-07-2007 01:35 PM

I have to say, I love it when Apple decides not to play by their own rules. <sigh...>

when I investigated this (and please note that I checked it out with my iPod on the assumption that that would work equivalently to the iPhone), I found that even though the visible menu says Sync "so-and-so's iPod", in fact, the system only sees Sync iPod. you can test this for yourself by inserting the following code in an appropriate place in your script:

Code:

tell application "System Events"
        set testList to every menu item of parentObject
        set Applescript's text item delimiters to return
        display dialog testList as text
        set Applescript's text item delimiters to ""
end tell

or you can buy me an iPhone and I will happily test further.

needless to say, I prefer option 2 :D

JordanX 07-07-2007 02:23 PM

tw,

Thanks so much for the response.

I tried just "Sync iPhone" and it didn't work. And I couldn't get the list via the code you sent.

I added your code and got the error:

Quote:

System Events got an error: Can't make every menu item of menu "Controls" of menu bar item "Controls" of menu bar 1 of application process "iTunes" into type reference.
I included the script to give an idea where I put the code you sent.


PHP Code:

set inputStr to "iTunes, File, Sync iPhone"
set oldDels to AppleScript's text item delimiters
set AppleScript'
s text item delimiters to ", "
set x to (every text item of inputStr)

menu_click(x)

-- `
menu_click`, by Jacob RusSeptember 2006
-- Accepts a list of form: `{"Finder", "View", "Arrange By", "Date"}`
-- 
Execute the specified menu item.  In this case, assuming the Finder 
-- is the active applicationarranging the frontmost folder by date.

on menu_click(mList)
    
local appNametopMenur
    
    
-- Validate our input
    
if mList's length < 3 then error "Menu list is not long enough"
    
    -- Set these variables for clarity and brevity later on
    set {appName, topMenu} to (items 1 through 2 of mList)
    set r to (items 3 through (mList'
s lengthof mList)
    
    -- 
This overly-long line calls the menu_recurse function with
    
-- two argumentsr, and a reference to the top-level menu
    
    tell application 
"System Events" to my menu_click_recurse(r, ((process appName)'s ¬
        (menu bar 1)'
(menu bar item topMenu)'s (menu topMenu)))
end menu_click

on menu_click_recurse(mList, parentObject)
    
    set testList to every menu item of parentObject -- ERROR HERE!
    set AppleScript'
s text item delimiters to return
    
display dialog testList as text
    set AppleScript
's text item delimiters to ""
    
    local f, r
    
    -- `f` = first item, `r` = rest of items
    set f to item 1 of mList
    if mList'
s length 1 then set r to (items 2 through (mList's length) of mList)
    
    -- either actually click the menu item, or recurse again
    tell application "System Events"
        
        if mList'
s length is 1 then
            click parentObject
's menu item f
            
        else
            my menu_click_recurse(r, (parentObject'
(menu item f)'s (menu f)))
        end if
    end tell
end menu_click_recurse 


tw 07-07-2007 04:28 PM

sending your script back at you, with revisions (in red)

Code:

set inputStr to "iTunes, File, Sync iPhone"
set oldDels to AppleScript's text item delimiters
set AppleScript's text item delimiters to ", "
set x to (every text item of inputStr)

menu_click(x)

set AppleScript's text item delimiters to oldDels
-- reset tid here

-- `menu_click`, by Jacob Rus, September 2006
-- Accepts a list of form: `{"Finder", "View", "Arrange By", "Date"}`
-- Execute the specified menu item.  In this case, assuming the Finder
-- is the active application, arranging the frontmost folder by date.

on menu_click(mList)
        local AppName, topMenu, r
       
        -- Validate our input
        if mList's length < 3 then error "Menu list is not long enough"
       
        -- Set these variables for clarity and brevity later on
        set {AppName, topMenu} to (items 1 through 2 of mList)
        set r to (items 3 through (mList's length) of mList)
       
        -- This overly-long line calls the menu_recurse function with
        -- two arguments: r, and a reference to the top-level menu
       
        tell application AppName to activate
        -- you need this for the GUI scripting to work, though not for testing the objects

        tell application "System Events" to my menu_click_recurse(r, ((process AppName)'s ¬
                (menu bar 1)'s (menu bar item topMenu)'s (menu topMenu)))
end menu_click

on menu_click_recurse(mList, parentObject)
       
        tell application "System Events"
                set testList to name of (every menu item of parentObject)
        end tell
        set AppleScript's text item delimiters to return
        display dialog testList as text
        -- this should give you a list of all the actual names in iTunes' File menu
       
        local f, r
       
        -- `f` = first item, `r` = rest of items
        set f to item 1 of mList
        if mList's length > 1 then set r to (items 2 through (mList's length) of mList)
       
        -- either actually click the menu item, or recurse again
        tell application "System Events"
                -- I've disabled the clicks for testing...
                if mList's length is 1 then
                        --click parentObject's menu item f
                else
                        --my menu_click_recurse(r, (parentObject's (menu item f)'s (menu f)))
                end if
        end tell
end menu_click_recurse


JordanX 07-07-2007 05:39 PM

tw,

That helped a lot. Here's the strange thing ;)

All menu items that have quotes or "..." after them fail. All other menu items work great! ;) Is that nuts or what?

With the same code were you able to click on an item with ... let's say

set inputStr to "iTunes, File, New Smart Playlist..."

Isn't that strange behavior or is there some trick to it?

Red_Menace 07-07-2007 06:30 PM

It might be that the OS menu handler is putting the name into a placeholder, and that the actual menu item text you see is not what the menu description is (I think I got that right), so a matching comparison is failing somewhere.

By the way, you can skip all that assigning to a string and text item delimiter stuff by just directly passing the list items (the handler code is confusing enough):

menu_click({"iTunes", "View", "Full Screen"})

tw 07-07-2007 06:44 PM

well, I understand the '...' problem. apparently, iTunes is not using three periods, but rather a single charachter called the horizontal ellipsis (it looks like three dots, but it's really a single entity, go figure...). if you can find that character and paste it into your script instead of three dots, that would work.

in case you're curious, this is the character - … - and this is three dots in a row - ... -. hope that comes through in the forum.

also, when I look at the list of menu items that comes out of applescript, there aren't any with quotes in them. you shouldn't have to use quotes anywhere here, apparently.

Red_Menace 07-07-2007 07:43 PM

I also use a constant for the elipse, but I was referring to the original problem. I have seen some string definitions with placeholders like "Do Something %1", and was wondering if something like that was causing the problem, since there are quotes in the menu item, but apparently not in the menu definition.

tw 07-07-2007 08:03 PM

Quote:

Originally Posted by Red_Menace (Post 391477)
I also use a constant for the elipse, but I was referring to the original problem.

sorry RM, my response was aimed at the post before yours. I think you're right - there's some kind of variable working in that menu - but I'm not sure what can be done about it except peek in through system events and see the actual menu values. I've actually been trying to figure out whether Apple designers are getting sloppy and inconsistent, or whether they're transitioning slowly into some new structures. I've just been running into a lot of inaccessible features, unscriptable apps, and undocumented effects.

JordanX 07-07-2007 11:06 PM

I couldn't figure out how to select a menu item with quotes in it so I scrapped the whole thing and looped through the menu items using the code that "tw" provided. I used an offset to find what I was looking for. By keeping track of the count I was then able to do click menu item (count).

It bugs me that I couldn't figure it out, but I don't know Applescript all that well so alas, resigned to defeat.

Thanks for all of your kind help. This is a terrific board. Much appreciated all of the responses.

Cheers, Jordan

tw 07-08-2007 12:53 AM

Quote:

Originally Posted by JordanX (Post 391502)
I couldn't figure out how to select a menu item with quotes in it so I scrapped the whole thing...

first rule of learning a new computer language - never get frustrated. remember, talking to a computer is worse than talking to a toddler; it's not even trying to meet you halfway, so you have to do all the thinking for both of you. drains the mental batteries... :-)

so, plug in your iPhone, launch iTunes, take a deep iBreath, and run this simplified script - it will do nothing except make a list of iTunes' File menu items in TextEdit. copy that list into your next post here and let us see what we can see.

Code:

tell application "iTunes" to launch
tell application "System Events"
        tell process "iTunes"
                tell menu bar 1
                        tell menu bar item "File"
                                tell menu "File"
                                        set menuItemList to name of every menu item
                                end tell
                        end tell
                end tell
        end tell
end tell

tell application "TextEdit"
        activate
        set outputDoc to make new document
        set name of window 1 to "Output"
        set tid to AppleScript's text item delimiters
        set AppleScript's text item delimiters to return
        set text of outputDoc to menuItemList as text
        set AppleScript's text item delimiters to tid
end tell


JordanX 07-08-2007 01:39 AM

Here's the output even with the iPhone in the cradle and Sync "Jordan's iPhone" under the File Menu.

Quote:

New Playlist
New Playlist from Selection
New Smart Playlist…
New Folder
missing value
Add to Library…
Close Window
missing value
Import…
Export…
Export Library…
Back Up to Disc…
missing value
Get Info
My Rating
Edit Smart Playlist
Show in Finder
Show Current Song
missing value
Burn Playlist to Disc
Create an iMix…
missing value
Sync iPod
Transfer Purchases from iPod
missing value
Page Setup…
Print…
I changed the script a little bit and here's the output, this matches what's on the running program with iPhone in cradle:

Quote:

New Playlist
New Playlist from Selection
New Smart Playlist…
New Folder
missing value
Add to Library…
Close Window
missing value
Import…
Export…
Export Library…
Back Up to Disc…
missing value
Get Info
My Rating
Edit Smart Playlist
Show in Finder
Show Current Song
missing value
Burn Playlist to Disc
Create an iMix…
missing value
Sync “Jordan’s iPhone”
Transfer Purchases from “Jordan’s iPhone”
missing value
Page Setup…
Print…
Where do we go from here? ;)

tw 07-08-2007 02:00 AM

well faaaaaaascinating. according to this, iTunes treats your iPhone as though it were an iPod. try using "Sync iPod" in your script when your iPhone is plugged in, see if you get the results you expect.

Red_Menace 07-08-2007 02:11 AM

Ah HA! Success is mine!

The reason for the goofiness is that the characters are NOT the standard quotes, but the left and right double quotation marks! (Troubleshooting tip: copy and paste text from the results window to a text editor like BBEdit)

I was unable to get an ascii number for the characters, but you can use Unicode or paste the character from the input menu (Script Editor > Edit > Special Characters…) into a string variable. For Example:

set LeftQuote to "“"
set RightQuote to "”"
menu_click({"iTunes", "File", "Sync " & LeftQuote & "Jordan's iPhone" & RightQuote})

tw 07-08-2007 02:37 AM

Quote:

Originally Posted by Red_Menace (Post 391533)
I was unable to get an ascii number for the characters, but you can use Unicode or paste the character from the input menu (Script Editor > Edit > Special Characters…) into a string variable.

ah, ok, cool. :) incidentally, to play with the ascii, you can use the following commands:

Code:

set RQNum to ASCII number of "“"  -- returns 210
set RQuote to ASCII character 210  -- returns "“"
set ellipsis to ASCII character 201  -- returns "…"

set syncString to "Sync " & ASCII character 210 & "Jordan's iPhone" & ASCII character 211


Red_Menace 07-08-2007 02:50 AM

Hmmm - I was using ASCII 201 for the elipse, but the other characters failed for some reason at the time. Seems to be working now though, must be getting late or something...

mark hunte 07-08-2007 05:41 AM

Sorry to come in so late.
But have you tried


Code:

tell application "iTunes"
        activate
        update
end tell

I just tested it and it makes my connected iPod sync.

JordanX 07-08-2007 09:52 AM

OMG! ;)

mark_hunte in five minutes you did what I was trying to do in a day and a half. ;)

Modified a little bit, but works just fine:

PHP Code:

tell application "iTunes"
    
local errText
    activate
    
try
        
update
    on error errText
        
return errText as text
    end 
try
end tell 

I did want to be able to distinguish between iPhone and iPod, for example I'm calling the Applescript from another program and wanted to say Update iPhone, and if there was an iPod in the cradle then reply back with "iPhone not available".

Do you know what I mean? In that scenario, I'd need to check the menu items. Do you agree?

So now, looks like I have to go back to Red_Menace's or tw's suggestion of using Left/Right quotes or ascii codes respectively.

btw: tw, I did try "Sync iPod". It didn't match, so iTunes definitely knows there's a difference.

JordanX 07-08-2007 10:04 AM

Latest update: Check out this line. (Something happened in the cut and paste, probably encoding from UTF-16 to UTF-8).
In Applescript, the menu items has curly quotes around Jordan's iPhone. Seems to make a difference because the offset says there is NO MATCH.

Quote:

offset of "Sync “Jordan's iPhone”" in "Sync “Jordan’s iPhone”"
The first Sync above uses ascii 210 and 211. The second Sync above (from Apple's Menu Item) uses Curly or Paired Quotes. So the offset above does NOT match in applescript. ??

The objective is to select the Menu item by Name. As in:

Quote:

set inputStr to "iTunes, File, Sync " & (ASCII character 210) & "Jordan's iPhone" & (ASCII character 211)<-- using the paired or curly quotes around Jordan's iPhone, that Apple uses (not shown here).
ASCII 210 and 211 are straight quotes, The iTunes menu has paired or curly quotes. The rational is I want to update/sync a specific device, not any device that happens to be in the cradle. Anyone have any bright ideas?

tw 07-08-2007 11:31 AM

Quote:

Originally Posted by JordanX (Post 391566)
ASCII 210 and 211 are straight quotes, The iTunes menu has paired or curly quotes. The rational is I want to update/sync a specific device, not any device that happens to be in the cradle. Anyone have any bright ideas?

ugh. you may be running into a current limitation of applescript: it doesn't really handle unicode text well. if the curly quotes are unicode, AS will implicitly translate them to standard ASCII without telling you.

I will say that I remember reading a nice discussion of this on the Satimage website, somewhere, but for the time being you might have to use the cut and paste approach.

as to specifying updates - well, a quick glance at the iTunes AS dictionary shows that the update command has an optional source parameter. don't know the syntax they are looking for, but that should allow you to specify which device you're updating.

Red_Menace 07-08-2007 11:57 AM

Quote:

offset of "Sync “Jordan's iPhone”" in "Sync “Jordan’s iPhone”"
The reason your offset doesn't match is that you are using different single quotes. What I did was copy characters from the result of listing the menu items (from Script Editor) and testing/comparing those. I used another application (Mail) that uses the quotes in a menu item, and was able to click on it using the ASCII codes.

tw 07-08-2007 12:48 PM

Apple sure could have made this easier... lol

mark hunte 07-08-2007 03:17 PM

The thing is you should not need to enter the name of the menu item in the script, but use what you have to get the script to do it for you.

Code:

activate application "iTunes"

tell application "System Events"
        tell process "iTunes"
                set parentObject to menu 1 of menu bar item "File" of menu bar 1
                set SyncList to name of (every menu item of parentObject whose name contains "Sync")
               
                repeat with i from 1 to number of items in SyncList
                        set SyncItem to item i of SyncList as Unicode text
                        if SyncItem contains "iPhone" then
                                click menu item SyncItem of parentObject
                        end if
                end repeat
               
        end tell
end tell

I do not have a iPhone, but it worked when i used "iPod" in the line:
if SyncItem contains "iPhone" then

Red_Menace 07-08-2007 04:19 PM

Good idea (see, this is why I don't program for a living) - my menu click handler now just looks if the item is contained in one of the menu items. In case anyone is interested, here is the handler:

Code:

on run -- example
        ClickMenu({"Script Editor", "Edit", "Find", "Find"}) -- matches "Find…"
end run


on ClickMenu(MenuList)
        (*
        clicks a menu item in an application (the application will be activated)
            the last menu item doesn't need to be exact, just close enough for a match
                parameters -                MenuList[list] -
                                                item 1 [string]: the application name
                                                item 2 [string]: the menu bar item name
                                                    item(s) 3+ [string]: the menu item(s)
                returns [boolean]:        true if successful, false if no match or error
        *)
        try
                set ItemCount to the count of the items in MenuList
                if ItemCount is less than 3 then error "The menu item list contains too few items"
                set ApplName to the first item of MenuList
                set MenuName to the second item of MenuList
                set MenuItem to the last item of MenuList
                tell application ApplName to activate
                tell application "System Events"
                        set MyMenu to (application process ApplName)'s (menu bar 1)'s (menu bar item MenuName)'s menu MenuName
                        if ItemCount is greater than 3 then -- process any sub menu items
                                repeat with MyItem from 3 to (ItemCount - 1)
                                        set MenuName to item MyItem of MenuList
                                        set MyMenu to MyMenu's (menu item MenuName)'s menu MenuName
                                end repeat
                        end if
                        set MenuItemList to the name of every menu item of MyMenu -- get a list of all the menu items
                        repeat with MyItem in MenuItemList
                                if MyItem contains MenuItem then -- look for a match
                                        click MyMenu's menu item MyItem
                                        return true -- match found
                                end if
                        end repeat
                end tell
                return false -- no match
        on error ErrorMessage number ErrorNumber
                if (ErrorNumber is -128) or (ErrorNumber is -1711) then -- user cancelled
                else
                        activate me
                        display alert "ClickMenu Handler Error " & (ErrorNumber as string) message ErrorMessage as warning buttons {"OK"} default button 1
                end if
                return false -- error
        end try
end ClickMenu


JordanX 11-17-2007 11:01 AM

Red_Menace and Mark Hunte,

Just wanted to follow up with an update. I dropped the project and worked on some other things before Red posted the solution above. Went back to the project today and BINGO!

the code works flawlessly now. I'm so excited I don't know what to do.

Thank everyone for contributing to this post, I'm sure it helps tons of people.

Thanks, Jordan


All times are GMT -5. The time now is 05:51 AM.

Powered by vBulletin® Version 3.8.7
Copyright ©2000 - 2014, vBulletin Solutions, Inc.
Site design © IDG Consumer & SMB; individuals retain copyright of their postings
but consent to the possible use of their material in other areas of IDG Consumer & SMB.