GUI Scripting questions

Hey folks,

Going to throw this out here and see what sticks but I am trying to get my head around GUI scripting, I have been tinkering and testing but not finding much effect, I’m hoping some of the better hive mind here have some great resources for someone to learn GUI Scripting or can point me in a better direction to learn this myself.

Essentially I am working within Numbers, I want it to close the inspector when opening the application (Command, Option, I) only if the inspector is open, otherwise it becomes rather pointless if meaning to close something that is closed and then opening it, I’m also wanting to make a separate instruction to confirm if the title of the Table on a sheet in Numbers matches the date, if not to move it to another page via cutting and pasting then returning to another instruction, any guidance or resources is welcomed and gratefully received.

Regards,

Mark

There are a few approaches you could take to this problem. Given that this is relatively straightforward, here is a basic approach. Begin by opening a numbers document and showing the inspector. The menus will change depending upon the state of the application, window or document.

What I did here was begin with menu bar 1, which is common to every application that has standard menus, get the UI elements of, and then repeat the process, extending the chain a link at a time based on the prior result.

tell application "Numbers"
	tell application "System Events" to tell application process "Numbers"
		
		UI elements of menu bar 1
		--> menu bar item "View" of menu bar 1 of application process "Numbers" of application "System Events"
		UI elements of menu bar item "View" of menu bar 1
		--> {menu "View" of menu bar item "View" of menu bar 1 of application process "Numbers" of application "System Events"}
		UI elements of menu "View" of menu bar item "View" of menu bar 1
		--> menu item "Inspector" of menu "View" of menu bar item "View" of menu bar 1
		UI elements of menu item "Inspector" of menu "View" of menu bar item "View" of menu bar 1
		--> {menu "Inspector" of menu item "Inspector" of menu "View" of menu bar item "View" of menu bar 1 of application process "Numbers" of application "System Events"}
		UI elements of menu "Inspector" of menu item "Inspector" of menu "View" of menu bar item "View" of menu bar 1
		--> menu item "Show Inspector" of menu "Inspector" of menu item "Inspector" of menu "View" of menu bar item "View" of menu bar 1 of application process "Numbers" of application "System Events"
		--> menu item "Hide Inspector" of menu "Inspector" of menu item "Inspector" of menu "View" of menu bar item "View" of menu bar 1 of application process "Numbers" of application "System Events"
	end tell
end tell

Each of the commands actually returns a list. Many of the lists are large so I’ve only reported the key item here. When you get the ui elements of a menu item or menu bar item then the list should have but a single item; in such cases, I included the entire result. For the last command, I show the two relevant items but note that you don’t get them both in one go. As mentioned above, depending upon the window’s state, you will get one or the other. Oddly, I get the opposite of the expected result, ie when the inspector is already hidden, the above returns ‘hide inspector’. Go figure.

Often with menus, once you understand the structure, you can construct the full chain of items without any of the above.

Now that we know what we’re working with, here is a script that will open each menu in sequence and then check to see the state of the show/hide menu item. If the Inspector menu offers Hide Inspector, it will choose it, otherwise it will cancel out of the menus.

Note the brief delay after each press to allow time for the menu items to load.

tell application "Numbers"
	activate
	tell application "System Events" to tell application process "Numbers"
		
		set mui1 to menu bar item "View" of menu bar 1
		perform action "AXPress" of mui1
		delay 0.1
		
		set mui2 to menu item "Inspector" of menu "View" of mui1
		perform action "AXPress" of mui2
		delay 0.1
		
		set mui3 to menu "Inspector" of mui2
		tell mui3
			delay 0.1
			set lame to last menu item of mui3
			-- log lame
			try
				if lame is menu item "Hide Inspector" then
					perform action "AXPress" of last menu item of mui3
				end if
			on error
				perform action "AXCancel" of last menu item of mui3
			end try
		end tell
	end tell
end tell

-- Result:
--> action "AXPress" of menu item "Hide Inspector" of menu "Inspector" of menu item "Inspector" of menu "View" of menu bar item "View" of menu bar 1 of application process "Numbers" of application "System Events"

While it is rarely this straightforward, you can use the same process to root through a window’s elements in order to script that. Somewhere —I think— there is a post hereabouts that works with some elements of Numbers’ inspectors to change some formatting.

Finally, here is an alternate approach that avoids simulating menu clicking and instead simply gets the last menu item of the Inspector menu and if it is ‘hide’, simulates key presses using key codes to use the keyboard shortcut to hide the inspector.

tell application "Numbers"
	activate
	tell application "System Events" to tell application process "Numbers"
		
		set mui1 to menu bar item "View" of menu bar 1
		set mui2 to menu item "Inspector" of menu "View" of mui1
		set mui3 to menu "Inspector" of mui2

		tell mui3
			set lame to last menu item of mui3
			if name of lame is "Hide Inspector" then -- otherwise "Show Inspector"
				-- log name of lame
				key code 34 using {command down, option down} -- command-shift-I
			end if
		end tell
	end tell
end tell

Hope this helps.

2 Likes

This is an awkward thing to try and do. I actually started this post by noting that it probably wasn’t doable as tables aren’t a very responsive thing but I think you can by mucking around with menu items and keyboard shortcuts. It turns out that with ui scripting you can cycle through the tables using the ‘tab’ key. Apple actually documented something useful. Who knew?

You can get the table name and compare it to the date. Depending upon your locale and date settings, the short date string may look something like this: ‘2024-09-19’. If yours has a different form then you’re on your own.

use scripting additions
set cd to current date
set sds to short date string of cd
log sds
--> "2024-09-19"

tell application "Numbers"
	activate
	-- activate sheet 1, deselect all
	set sh1 to sheet 1 of document 1
	set active sheet of document 1 to sh1 -- switch to sheet 1
	tell application "System Events" to tell application process "Numbers"
		set editMenu to menu "Edit" of menu bar item "Edit" of menu bar 1
		perform action "AXPress" of menu item "Deselect All" of editMenu
	end tell
	delay 0.5
	
	-- cycle through potential tables (eg those containing string 'saucy'
	-- find a saucy table whose name contains short date string
	set ct to count of tables in sh1
	set proceed to false
	repeat with c from 1 to ct
		set tableName to name of table c of sh1
		log tableName
		-- select table by simulating 'tab' key
		tell application "System Events" to tell application process "Numbers" to key code 48
		delay 0.5
		if tableName begins with "saucy" then
			if tableName contains sds then
				display dialog "Processing table " & c giving up after 1
				delay 0.5
				set proceed to true -- if table name contains today's date string
				exit repeat
			end if
		end if
	end repeat
	
	-- move matching table
	if proceed then
		tell application "System Events" to tell application process "Numbers"
			set editMenu to menu "Edit" of menu bar item "Edit" of menu bar 1
			perform action "AXPress" of menu item "Cut" of editMenu -- copy selected table
		end tell
		
		set active sheet of document 1 to sheet 2 of document 1 -- switch to sheet 2
		delay 0.5
		
		tell application "System Events" to tell application process "Numbers"
			perform action "AXPress" of menu item "Deselect All" of editMenu
			delay 0.5
			perform action "AXPress" of menu item "Paste" of editMenu -- paste matching table to sheet 2
		end tell
		set active sheet of document 1 to sheet 1 of document 1 -- switch back to sheet 1
	else -- if no table matches
		display dialog "Table names do not contain '" & sds & "'"
	end if
	
end tell

I have tested the script with a document that has two sheets; on the first sheet there are two tables, the second has one table. It first checks to determine which tables are eligible, in this example, those with ‘source’ in the name — you can remove this if it isn’t needed. It cycles through the eligible tables looking for the date string. If you will only have one table on the sheet then you can simplify this. When it finds a matching table, it notifies the user of which table, cuts it, switches to the second sheet and pastes it there. It will follow whatever norms Numbers uses. For example, it will place the pasted table below a table already on sheet 2. When done, it switches back to the first sheet.

There isn’t any real error handling so if your document has but one sheet then you’ll probably get an error. If no table matches, then it will notify with a dialogue. As with any ui scripting, it is fragile. You shouldn’t do anything while it runs. It may break with OS or application updates.

NB The script logs a few items. You can see the resulting values in the Log History (Window > Log History) to get a clearer view of what’s happening when the script runs. You can also comment them out or delete them as desired.

I shall reply to each of these separately because I do appreciate all of the time and the effort you have put into these responses Mockman, as ever you are a font of knowledge and I hope some day have a small percent of what you know but it is something in the future for now at least for me, in the end I went with the keystroke option which answered what I wanted/needed without any possible intrusion from people as I found the scroll through the menu option allowed some possibility of that happening.

Currently I’m working on instigating my version of the move the table between sheets (I use variables for particular sheets instead of numbers because if I need to have multiple sheets then numbers can be lost), otherwise you have hit the nail on the head with this, again I can’t thank you for your time, effort and expertise enough, I always learn something new on this site and its one reason I keep coming back and try to help out also with my own knowledge and expertise.

You can mix and match, as well as referring to sheets by name, depending upon what’s appropriate for the situation.

Glad it worked out.

a slightly cleaned up version of this script

tell application "Numbers"
	activate
	tell application "System Events" to tell application process "Numbers"
		set mui1 to menu "Inspector" of menu item "Inspector" of menu "View" of menu bar item "View" of menu bar 1
		set lame to last menu item of mui1
		if name of lame is "Hide Inspector" then -- otherwise "Show Inspector"
			-- log name of lame
			click lame
		end if
	end tell
end tell