This script directly switches to a DEVONthink window - even if DEVONthink is not the active app and even if the window is on another space.
All limitations of the previous script are solved, every window can be accessed reliably.
Background:
The previous script was a hack as in general it’s not possible to switch to a window that’s not on the current space via AppleScript, BetterTouchTool, Keyboard Maestro etc.
I’ve tried and searched for a solution over and over just to find again that it is not possible. However I’m not the only one, @jasondm007 is searching too. Although I already knew that it’s not possible I couldn’t resist and tried again when he asked here how to do it. Well, turned out it is possible, at least with DEVONthink.
Although the hacky solution is interesting it later turned out that there’s no need to use it in this script. DEVONthink’s “Window” menu and some UI scripting is all that’s needed. No idea why I’ve never tried that before.
Limitations:
- The previous script’s problems (see edit history of this post) are all solved.
Features:
-
Windows of the current space are sorted to the top (if all windows have unique names).
-
Long window names can be trimmed with property
trimNameTo
.-
If a window has a content record then the suffix is not trimmed.
-
If
trimNameTo
> 0 then this part in e.g. PDF names[21.0 x 29.7 cm, 3 Seiten, 100%]
is always cut before the actual window name is trimmed.
-
-
The window class can be shown with property
showWindowClass
. -
Selecting a window can be done with three keystrokes: your shortcut, the number prefix and enter.
-
Script can be used macOS wide if run via e.g. Alfred, BetterTouchTool or Keyboard Maestro.
Using this script actually doesn’t make much sense if you use it via DEVONthink’s script menu as in this case you could simply use the “Window” menu. Of course it can be used with a shortcut from within DEVONthink, but then you’ll miss the best part:
accessing DEVONthink windows from everywhere.
-- Window Switcher (across all spaces)
property trimNameTo : 150 -- set to 0 if you don't want trimmed names. if > 0 then this part in e.g. PDF names "[21.0 x 29.7 cm, 3 Seiten, 100%]" is always cut
property showWindowClass : true
tell application id "DNtp"
try
tell application "System Events" to tell process "DEVONthink 3" to tell menu bar 1 to tell menu bar item 10 to tell menu 1 to set theMenuItems to name of every menu item
try
set theWindowNames_Menu to items 20 thru -1 in theMenuItems
on error
error "Please open a window"
end try
set uniqueNames to true
repeat with thisName in theWindowNames_Menu
if my countInstancesOfItemInList(theWindowNames_Menu, thisName as string) > 1 then
set uniqueNames to false
exit repeat
end if
end repeat
if uniqueNames = true then
set {theWindowNames_sorted, theMenuPositions} to my sortWindowNames()
set theDisplayNames to my getDisplayNames(theWindowNames_sorted)
set thePromptExtension to ""
else
set theMenuPositions to {}
set theCount to 19
repeat (count theWindowNames_Menu) times
set theCount to theCount + 1
set end of theMenuPositions to theCount
end repeat
set theDisplayNames to my getDisplayNames(theWindowNames_Menu)
set thePromptExtension to (ASCII character 9) & (ASCII character 9) & (ASCII character 9) & "-- Unsorted --"
end if
activate
set theChoice to choose from list theDisplayNames with prompt "Go to:" & thePromptExtension default items (item 1 of theDisplayNames) with title ""
if theChoice is false then return
set theChoice to item 1 of theChoice
set theMenuPosition to item ((characters 1 thru ((offset of (ASCII character 9) in theChoice) - 1) in theChoice as string) as integer) in theMenuPositions
if theMenuPosition ≥ 20 then
tell application "System Events" to tell process "DEVONthink 3" to tell menu bar 1 to tell menu bar item 10 to tell menu 1 to click menu item theMenuPosition
return
else
error ("Error: Menu Position " & theMenuPosition) as string
end if
on error error_message number error_number
activate
display alert "DEVONthink" message error_message as warning
return
end try
end tell
on countInstancesOfItemInList(theList, theItem)
set theCount to 0
repeat with a from 1 to count of theList
if item a of theList is theItem then
set theCount to theCount + 1
end if
end repeat
return theCount
end countInstancesOfItemInList
on sortWindowNames()
tell application "System Events" to tell process "DEVONthink 3" to set theWindowNames_CurrentSpace to name of windows
set theMenuPositions to {}
repeat with thisName in theWindowNames_CurrentSpace
set end of theMenuPositions to (19 + (my getPositionOfItemInList(thisName as string, my theWindowNames_Menu))) as integer
end repeat
set theWindowNames_OtherSpaces to {}
set theCount to 19
repeat with thisName in my theWindowNames_Menu
set theCount to theCount + 1
if theCount is not in theMenuPositions then
set end of theWindowNames_OtherSpaces to thisName as string
set end of theMenuPositions to (19 + (my getPositionOfItemInList(thisName as string, my theWindowNames_Menu))) as integer
end if
end repeat
set theWindowNames_sorted to theWindowNames_CurrentSpace & theWindowNames_OtherSpaces
return {theWindowNames_sorted, theMenuPositions}
end sortWindowNames
on getPositionOfItemInList(theItem, theList)
repeat with a from 1 to count of theList
if item a of theList is theItem then return a
end repeat
return 0
end getPositionOfItemInList
on getDisplayNames(theWindowNames)
set theDisplayNames to {}
set theCount to 0
repeat with thisName in theWindowNames
set theCount to theCount + 1
tell application id "DNtp"
try
if trimNameTo = 0 then
set thisDisplayName to thisName as string
else
set thisWindow to (first think window whose name = thisName)
try
set thisContentRecord to content record of thisWindow
if thisContentRecord ≠ missing value then
set thisFileName to filename of thisContentRecord
set thisSuffix to my getSuffix(thisFileName)
else
set thisSuffix to ""
end if
on error
set thisSuffix to ""
end try
if thisName ends with "%]" then
set thisName_reverse to (reverse of (characters in thisName)) as string
set thisName_cleaned_reverse to (characters ((offset of "[ " in thisName_reverse) + 2) thru -1 in thisName_reverse) as string
set thisName_cleaned to (reverse of (characters in thisName_cleaned_reverse)) as string
else
set thisName_cleaned to thisName
end if
if (length of thisName_cleaned) > trimNameTo then
set thisName_trimmed to (characters 1 thru (trimNameTo - 2) in thisName_cleaned) as string
if thisName_trimmed ends with space then set thisName_trimmed to (characters 1 thru -2 in thisName_trimmed) as string
if thisSuffix ≠ "" then
set thisDisplayName to (thisName_trimmed & "[...]." & thisSuffix) as string
else
set thisDisplayName to (thisName_trimmed & "...") as string
end if
else
set thisDisplayName to thisName_cleaned
end if
end if
if showWindowClass = true then
try
if class of thisWindow = viewer window then
set thisClass to ("Viewer" & (ASCII character 9) & (ASCII character 9)) as string
else
set thisClass to ("Document" & (ASCII character 9))
end if
on error
set thisClass to (ASCII character 9) & (ASCII character 9) & (ASCII character 9) as string
end try
else
set thisClass to ""
end if
set end of theDisplayNames to (theCount & (ASCII character 9) & thisClass & thisDisplayName) as string
on error error_message number error_number
error number -128
end try
end tell
end repeat
return theDisplayNames
end getDisplayNames
on getSuffix(PathOrName)
set theSuffix to reverse of characters 1 thru ((offset of "." in (reverse of characters in PathOrName as string)) - 1) in (reverse of characters in PathOrName as string) as string
end getSuffix