Script (V1b3): Cascade some/all currently opened document/viewer windows

I have three screens and always have many opened document windows scattered all around. This is a small utility to consolidate all document-only windows in all displays and cascade those windows at the main monitor. The initial position and the (x,y) point offset among all cascaded windows are defined at the first few lines of the script. You may modify the script to ur likings.

Cheers

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions


-- by ngan 2020.02.13

global screenWidth, screenHeight, theIniPosition, theXoffset, theYoffset
global theDocWins

set {screenWidth, screenHeight} to {3840, 2160}
set {theXoffset, theYoffset} to {50, 50}
set theIniPosition to "LF" -- LF/CF/RF: 1/3 vertical full screen
set sortByName to true

tell application id "DNtp"
	
	set theDocWins to document windows
	
	if sortByName is true then
		
		set theDocWins to my sortObjlist(theDocWins)
		
	end if
	
	my cascadeWindows(theDocWins, theIniPosition, theXoffset, theYoffset, screenWidth, screenHeight)
	
end tell

on cascadeWindows(theItems, theIniPosition, theXoffset, theYoffset, screenWidth, screenHeight)
	tell application id "DNtp"
		try
			set {WinWidth, WinHeight} to {screenWidth / 3, (screenHeight - 22) / 3} -- position in 1/3 x 1/3 of screen
			set {WinWidthQ, WinHeightQ} to {screenWidth / 2, (screenHeight - 22) / 2} -- pos in 1/2 x 1/2 of screen
			set {WinWidthH, WinHeightH} to {screenWidth / 3, 0.85 * (screenHeight - 22)} -- pos in 1/3 x 7/8 of screen
			
			set yAxis to 22
			set xAxis to 0
			
			-- 1/9 screen e.g. UL = upper left 1/3 x 1/3 of screen
			if theIniPosition is "UL" then set {x1, y1, x2, y2} to {xAxis, yAxis, xAxis + WinWidth, yAxis + WinHeight}
			if theIniPosition is "ML" then set {x1, y1, x2, y2} to {xAxis, yAxis + WinHeight, xAxis + WinWidth, yAxis + 2 * WinHeight}
			if theIniPosition is "LL" then set {x1, y1, x2, y2} to {xAxis, yAxis + 2 * WinHeight, xAxis + WinWidth, yAxis + 3 * WinHeight}
			if theIniPosition is "UM" then set {x1, y1, x2, y2} to {xAxis + WinWidth, yAxis, xAxis + 2 * WinWidth, yAxis + WinHeight}
			if theIniPosition is "MM" then set {x1, y1, x2, y2} to {xAxis + WinWidth, yAxis + WinHeight, xAxis + 2 * WinWidth, yAxis + 2 * WinHeight}
			if theIniPosition is "LM" then set {x1, y1, x2, y2} to {xAxis + WinWidth, yAxis + 2 * WinHeight, xAxis + 2 * WinWidth, yAxis + 3 * WinHeight}
			if theIniPosition is "UR" then set {x1, y1, x2, y2} to {xAxis + 2 * WinWidth, yAxis, xAxis + 3 * WinWidth, yAxis + WinHeight}
			if theIniPosition is "MR" then set {x1, y1, x2, y2} to {xAxis + 2 * WinWidth, yAxis + WinHeight, xAxis + 3 * WinWidth, yAxis + 2 * WinHeight}
			if theIniPosition is "LR" then set {x1, y1, x2, y2} to {xAxis + 2 * WinWidth, yAxis + 2 * WinHeight, xAxis + 3 * WinWidth, yAxis + 3 * WinHeight}
			
			-- 1/4 screen e.g. LU = left-upper quarter screen
			if theIniPosition is "LU" then set {x1, y1, x2, y2} to {xAxis, yAxis, xAxis + WinWidthQ, yAxis + WinHeightQ}
			if theIniPosition is "LD" then set {x1, y1, x2, y2} to {xAxis, yAxis + WinHeightQ, xAxis + WinWidthQ, yAxis + 2 * WinHeightQ}
			if theIniPosition is "RU" then set {x1, y1, x2, y2} to {xAxis + WinWidthQ, yAxis, xAxis + 2 * WinWidthQ, yAxis + WinHeightQ}
			if theIniPosition is "RL" then set {x1, y1, x2, y2} to {xAxis + WinWidthQ, yAxis + WinHeightQ, xAxis + 2 * WinWidthQ, yAxis + 2 * WinHeightQ}
			
			-- 1/3 of 85% of vertical screen e.g. LF = 85% of full height with 1/3 width at left
			if theIniPosition is "LF" then set {x1, y1, x2, y2} to {xAxis, yAxis, xAxis + WinWidthH, yAxis + WinHeightH}
			if theIniPosition is "CF" then set {x1, y1, x2, y2} to {xAxis + WinWidthH, yAxis, xAxis + 2 * WinWidthH, yAxis + WinHeightH}
			if theIniPosition is "RF" then set {x1, y1, x2, y2} to {xAxis + 2 * WinWidthH, yAxis, xAxis + 3 * WinWidthH, yAxis + WinHeightH}
			
			
			repeat with each in theItems
				set index of each to 1
				set bounds of each to {x1, y1, x2, y2}
				set {x1, y1, x2, y2} to {x1 + theXoffset, y1 + theYoffset, x2 + theXoffset, y2 + theYoffset}
			end repeat
			
			
		on error error_message number error_number
			if the error_number is not -128 then display alert "NewCardPosition() has error" message error_message as warning
		end try
	end tell
end cascadeWindows

on sortObjlist(theList)
	tell application id "DNtp"
		set theIndexList to {}
		set theSortedList to {}
		repeat (length of theList) times
			set theLowItem to ""
			repeat with a from 1 to (length of theList)
				if a is not in theIndexList then
					set theCurrentItem to item a of theList
					if theLowItem is "" then
						set theLowItem to theCurrentItem
						set theLowItemIndex to a
					else if (name of theCurrentItem) comes before (name of theLowItem) then
						set theLowItem to theCurrentItem
						set theLowItemIndex to a
					end if
				end if
			end repeat
			set end of theSortedList to theLowItem
			set end of theIndexList to theLowItemIndex
		end repeat
		return theSortedList
	end tell
end sortObjlist

3 Likes

Thanks for sharing it :slight_smile:

Out of curiosity, which monitors are you using?

By the way, Jim (@BLUEFROG) , I kindly request for the ability to drag tabs, so that I could turn a document into a tab in another document/main window or re-organize tabs in determined windows. :upside_down_face:

@cgrunenberg will have to assess the feasibility of that, but the request is noted.

1 Like

iMac 27” + 2 x LG 32UL950. LG 32UD99 is another good choice but UL950 is brighter at 450 nits and has thunderbolt 3.

Thanks, @ngan.

This is another version of the script. I hope this script is useful for some of those with multiple displays and keep multiple documents and viewer windows opened all the time.

(1) Example 1. If only one item is selected, the selected item will be re-positioned to a position specified in the script. In my case, a document window with 1/3 of the screen size in the centre.

(2) Example 2. If multiple items are selected, all of the selected items will be cascaded.

(3) Example 3. If no item is selected and OK is clicked. All items on the list will be cascaded.

(4) An option in the script “property arrangeDocWin : true”. If false, the list will show/cascade viewer windows instead.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

-- CascadeWin
-- by Ngan 2020.02.25
--V1b2
--if only one window is selected, bring to front
--if multiple items are selected, cascade
--if none is selected, all items are cascaded

property arrangeDocWin : true -- true: arrange document windows, false: arrange viewer widows
-- |  1/3 screen vertical : LV/CV/RV  |  1/4 screen horizontal : LU/LL/RU/RL  |  1/9 screen: UL/ML/LL/UM/MM/LM/UR/MR/LR |
property theIniDocWinPos : "CV" -- to be used if arrangeDocWin : true
property theIniViewWinPos : "LU" --to be used if arrangeDocWin : false

global screenWidth, screenHeight, screenHeightRatio, theXoffset, theYoffset
global theWins, theWinsName, theWinsList

set {screenWidth, screenHeight} to {3840, 2160}
set {theXoffset, theYoffset} to {50, 50} -- x and y offsets for cascaded windows 
set screenHeightRatio to 0.875 -- For 1/3 screen vertical window config only. The height of the window as a proportion of the height of the screen. 




tell application id "DNtp"
	
	if arrangeDocWin then
		set theWins to document windows
	else
		set theWins to viewer windows
	end if
	
	set theWins to my sortObjlist(theWins)
	
	set l to {}
	repeat with each in theWins
		set the end of l to name of each
	end repeat
	
	set thePrompt to "Choose window/s to cascade " & return & "If none is selected, all windows are cascaded"
	set theWinsName to (choose from list l with prompt thePrompt default items "" with empty selection allowed and multiple selections allowed) -- as string
	
	if theWinsName is not false then
		set theWinsList to {}
		repeat with i from 1 to length of theWinsName
			repeat with j from 1 to length of theWins
				if theWinsName's item i = (name of theWins's item j as string) then
					set end of theWinsList to theWins's item j
				end if
			end repeat
		end repeat
		
		if length of theWinsList = 0 then set theWinsList to theWins
		
		if arrangeDocWin then
			my cascadeWindows(theWinsList, theIniDocWinPos, theXoffset, theYoffset, screenWidth, screenHeight)
		else
			my cascadeWindows(theWinsList, theIniViewWinPos, theXoffset, theYoffset, screenWidth, screenHeight)
		end if
		
	end if
	
end tell

on cascadeWindows(theItems, theIniPosition, theXoffset, theYoffset, screenWidth, screenHeight)
	tell application id "DNtp"
		try
			set yAxis to 22
			set xAxis to 0
			
			set {WinWidth, WinHeight} to {screenWidth / 3, (screenHeight - 22) / 3} -- position in 1/3 x 1/3 of screen
			set {WinWidthQ, WinHeightQ} to {screenWidth / 2, (screenHeight - 22) / 2} -- pos in 1/2 x 1/2 of screen
			set {WinWidthH, WinHeightH} to {screenWidth / 3, screenHeightRatio * (screenHeight - 22)} -- pos in 1/3 x 7/8 of screen
			-- for LF/CF/RF only
			set verticalOffset to ((screenHeight - 22) - WinHeightH) / 2 -- the distance from the menubar to the top of window
			----
			
			-- 1/9 screen
			if theIniPosition is "UL" then set {x1, y1, x2, y2} to {xAxis, yAxis, xAxis + WinWidth, yAxis + WinHeight}
			if theIniPosition is "ML" then set {x1, y1, x2, y2} to {xAxis, yAxis + WinHeight, xAxis + WinWidth, yAxis + 2 * WinHeight}
			if theIniPosition is "LL" then set {x1, y1, x2, y2} to {xAxis, yAxis + 2 * WinHeight, xAxis + WinWidth, yAxis + 3 * WinHeight}
			if theIniPosition is "UM" then set {x1, y1, x2, y2} to {xAxis + WinWidth, yAxis, xAxis + 2 * WinWidth, yAxis + WinHeight}
			if theIniPosition is "MM" then set {x1, y1, x2, y2} to {xAxis + WinWidth, yAxis + WinHeight, xAxis + 2 * WinWidth, yAxis + 2 * WinHeight}
			if theIniPosition is "LM" then set {x1, y1, x2, y2} to {xAxis + WinWidth, yAxis + 2 * WinHeight, xAxis + 2 * WinWidth, yAxis + 3 * WinHeight}
			if theIniPosition is "UR" then set {x1, y1, x2, y2} to {xAxis + 2 * WinWidth, yAxis, xAxis + 3 * WinWidth, yAxis + WinHeight}
			if theIniPosition is "MR" then set {x1, y1, x2, y2} to {xAxis + 2 * WinWidth, yAxis + WinHeight, xAxis + 3 * WinWidth, yAxis + 2 * WinHeight}
			if theIniPosition is "LR" then set {x1, y1, x2, y2} to {xAxis + 2 * WinWidth, yAxis + 2 * WinHeight, xAxis + 3 * WinWidth, yAxis + 3 * WinHeight}
			
			-- 1/4 screen
			if theIniPosition is "LU" then set {x1, y1, x2, y2} to {xAxis, yAxis, xAxis + WinWidthQ, yAxis + WinHeightQ}
			if theIniPosition is "LL" then set {x1, y1, x2, y2} to {xAxis, yAxis + WinHeightQ, xAxis + WinWidthQ, yAxis + 2 * WinHeightQ}
			if theIniPosition is "RU" then set {x1, y1, x2, y2} to {xAxis + WinWidthQ, yAxis, xAxis + 2 * WinWidthQ, yAxis + WinHeightQ}
			if theIniPosition is "RL" then set {x1, y1, x2, y2} to {xAxis + WinWidthQ, yAxis + WinHeightQ, xAxis + 2 * WinWidthQ, yAxis + 2 * WinHeightQ}
			
			-- 1/3 screen
			if theIniPosition is "LV" then set {x1, y1, x2, y2} to {xAxis, yAxis + verticalOffset, xAxis + WinWidthH, yAxis + verticalOffset + WinHeightH}
			if theIniPosition is "CV" then set {x1, y1, x2, y2} to {xAxis + WinWidthH, yAxis + verticalOffset, xAxis + 2 * WinWidthH, yAxis + verticalOffset + WinHeightH}
			if theIniPosition is "RV" then set {x1, y1, x2, y2} to {xAxis + 2 * WinWidthH, yAxis + verticalOffset, xAxis + 3 * WinWidthH, yAxis + verticalOffset + WinHeightH}
			
			
			repeat with each in theItems
				set index of each to 1
				set bounds of each to {x1, y1, x2, y2}
				set {x1, y1, x2, y2} to {x1 + theXoffset, y1 + theYoffset, x2 + theXoffset, y2 + theYoffset}
			end repeat
			
			
		on error error_message number error_number
			if the error_number is not -128 then display alert "NewCardPosition() has error" message error_message as warning
		end try
	end tell
end cascadeWindows

on sortObjlist(theList)
	tell application id "DNtp"
		set theIndexList to {}
		set theSortedList to {}
		repeat (length of theList) times
			set theLowItem to ""
			repeat with a from 1 to (length of theList)
				if a is not in theIndexList then
					set theCurrentItem to item a of theList
					if theLowItem is "" then
						set theLowItem to theCurrentItem
						set theLowItemIndex to a
					else if (name of theCurrentItem) comes before (name of theLowItem) then
						set theLowItem to theCurrentItem
						set theLowItemIndex to a
					end if
				end if
			end repeat
			set end of theSortedList to theLowItem
			set end of theIndexList to theLowItemIndex
		end repeat
		return theSortedList
	end tell
end sortObjlist

1 Like

Add a minor enhancement for my own convenience.

New: If the default is set to cascade document windows (property listDocWinFirst : true), the list box will offer choice for switching to show and cascade viewer windows instead. And vice versa for property listDocWinFirst : false.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions


-- by Ngan 2020.02.24
--v1b3 2020.03.03
--list will offer to switch from document windows to viewer window, and vice versa

property listDocWinFirst : true -- true: arrange document windows, false: arrange viewer widows

-- |  1/3 screen vertical : LV/CV/RV  |  1/4 screen horizontal : LU/LD/RU/RD  |  1/9 screen: UL/ML/LL/UM/MM/LM/UR/MR/LR |
property theIniDocWinPos : "CV" -- to be used if listDocWinFirst : true
property theIniViewWinPos : "LU" --to be used if listDocWinFirst : false

global screenWidth, screenHeight, screenHeightRatio, theXoffset, theYoffset
global theWins, theWinsName, theWinsList
global theseWins, theseWinsType, theseWinsPos, theOtherWins, theOtherWinsType, theOtherWinsPos

set {screenWidth, screenHeight} to {3840, 2160}
set {theXoffset, theYoffset} to {50, 50} -- x and y offsets for cascaded windows 
set screenHeightRatio to 0.875 -- For 1/3 screen vertical window config only. The height of the window as a proportion of the height of the screen. 



tell application id "DNtp"
	
	
	if listDocWinFirst then
		
		set theseWins to document windows
		set theseWinsType to "document windows"
		set theseWinsPos to theIniDocWinPos
		set theOtherWins to viewer windows
		set theOtherWinsType to "viewer windows"
		set theOtherWinsPos to theIniViewWinPos
		
	else
		
		set theseWins to viewer windows
		set theseWinsType to "viewer windows"
		set theseWinsPos to theIniViewWinPos
		set theOtherWins to document windows
		set theOtherWinsType to "document windows"
		set theOtherWinsPos to theIniDocWinPos
	end if
	
	if theseWins is {} then
		display alert "There is no " & theseWinsType giving up after 1
		return
	end if
	
	set theWins to my sortObjlist(theseWins)
	set l to {}
	repeat with each in theWins
		set the end of l to name of each
	end repeat
	
	set cancelButtonNm to "Show " & theOtherWinsType & " instead"
	set thePrompt to "Choose " & theseWinsType & " to cascade " & return & "If none is selected, all windows are cascaded"
	set theWinsName to (choose from list l with prompt thePrompt default items "" cancel button name cancelButtonNm with empty selection allowed and multiple selections allowed)
	
	
	if theWinsName is not false then
		my showWin(theseWinsPos)
	else
		if theOtherWins is {} then
			display alert "There is no " & theOtherWinsType giving up after 1
			return
		end if
		set theWins to my sortObjlist(theOtherWins)
		set l to {}
		repeat with each in theWins
			set the end of l to name of each
		end repeat
		set thePrompt to "Choose " & theOtherWinsType & " to cascade " & return & "If none is selected, all windows are cascaded"
		set theWinsName to (choose from list l with prompt thePrompt default items "" with empty selection allowed and multiple selections allowed)
		if theWinsName is not false then
			my showWin(theOtherWinsPos)
		end if
	end if
	
end tell

on showWin(thePos)
	
	tell application id "DNtp"
		set theWinsList to {}
		repeat with i from 1 to length of theWinsName
			repeat with j from 1 to length of theWins
				if theWinsName's item i = (name of theWins's item j as string) then
					set end of theWinsList to theWins's item j
				end if
			end repeat
		end repeat
		
		if length of theWinsList = 0 then set theWinsList to theWins
		
		my cascadeWindows(theWinsList, thePos, theXoffset, theYoffset, screenWidth, screenHeight)
		
	end tell
	
end showWin


on cascadeWindows(theItems, theIniPosition, theXoffset, theYoffset, screenWidth, screenHeight)
	tell application id "DNtp"
		try
			set yAxis to 22
			set xAxis to 0
			
			set {WinWidth, WinHeight} to {screenWidth / 3, (screenHeight - 22) / 3} -- position in 1/3 x 1/3 of screen
			set {WinWidthQ, WinHeightQ} to {screenWidth / 2, (screenHeight - 22) / 2} -- pos in 1/2 x 1/2 of screen
			set {WinWidthH, WinHeightH} to {screenWidth / 3, screenHeightRatio * (screenHeight - 22)} -- pos in 1/3 x 7/8 of screen
			-- for LF/CF/RF only
			set verticalOffset to ((screenHeight - 22) - WinHeightH) / 2 -- the distance from the menubar to the top of window
			----
			
			-- 1/9 screen
			if theIniPosition is "UL" then set {x1, y1, x2, y2} to {xAxis, yAxis, xAxis + WinWidth, yAxis + WinHeight}
			if theIniPosition is "ML" then set {x1, y1, x2, y2} to {xAxis, yAxis + WinHeight, xAxis + WinWidth, yAxis + 2 * WinHeight}
			if theIniPosition is "LL" then set {x1, y1, x2, y2} to {xAxis, yAxis + 2 * WinHeight, xAxis + WinWidth, yAxis + 3 * WinHeight}
			if theIniPosition is "UM" then set {x1, y1, x2, y2} to {xAxis + WinWidth, yAxis, xAxis + 2 * WinWidth, yAxis + WinHeight}
			if theIniPosition is "MM" then set {x1, y1, x2, y2} to {xAxis + WinWidth, yAxis + WinHeight, xAxis + 2 * WinWidth, yAxis + 2 * WinHeight}
			if theIniPosition is "LM" then set {x1, y1, x2, y2} to {xAxis + WinWidth, yAxis + 2 * WinHeight, xAxis + 2 * WinWidth, yAxis + 3 * WinHeight}
			if theIniPosition is "UR" then set {x1, y1, x2, y2} to {xAxis + 2 * WinWidth, yAxis, xAxis + 3 * WinWidth, yAxis + WinHeight}
			if theIniPosition is "MR" then set {x1, y1, x2, y2} to {xAxis + 2 * WinWidth, yAxis + WinHeight, xAxis + 3 * WinWidth, yAxis + 2 * WinHeight}
			if theIniPosition is "LR" then set {x1, y1, x2, y2} to {xAxis + 2 * WinWidth, yAxis + 2 * WinHeight, xAxis + 3 * WinWidth, yAxis + 3 * WinHeight}
			
			-- 1/4 screen
			if theIniPosition is "LU" then set {x1, y1, x2, y2} to {xAxis, yAxis, xAxis + WinWidthQ, yAxis + WinHeightQ}
			if theIniPosition is "LD" then set {x1, y1, x2, y2} to {xAxis, yAxis + WinHeightQ, xAxis + WinWidthQ, yAxis + 2 * WinHeightQ}
			if theIniPosition is "RU" then set {x1, y1, x2, y2} to {xAxis + WinWidthQ, yAxis, xAxis + 2 * WinWidthQ, yAxis + WinHeightQ}
			if theIniPosition is "RD" then set {x1, y1, x2, y2} to {xAxis + WinWidthQ, yAxis + WinHeightQ, xAxis + 2 * WinWidthQ, yAxis + 2 * WinHeightQ}
			
			-- 1/3 screen
			if theIniPosition is "LV" then set {x1, y1, x2, y2} to {xAxis, yAxis + verticalOffset, xAxis + WinWidthH, yAxis + verticalOffset + WinHeightH}
			if theIniPosition is "CV" then set {x1, y1, x2, y2} to {xAxis + WinWidthH, yAxis + verticalOffset, xAxis + 2 * WinWidthH, yAxis + verticalOffset + WinHeightH}
			if theIniPosition is "RV" then set {x1, y1, x2, y2} to {xAxis + 2 * WinWidthH, yAxis + verticalOffset, xAxis + 3 * WinWidthH, yAxis + verticalOffset + WinHeightH}
			
			
			repeat with each in theItems
				set index of each to 1
				set bounds of each to {x1, y1, x2, y2}
				set {x1, y1, x2, y2} to {x1 + theXoffset, y1 + theYoffset, x2 + theXoffset, y2 + theYoffset}
			end repeat
			
			
		on error error_message number error_number
			if the error_number is not -128 then display alert "NewCardPosition() has error" message error_message as warning
		end try
	end tell
end cascadeWindows

on sortObjlist(theList)
	tell application id "DNtp"
		set theIndexList to {}
		set theSortedList to {}
		repeat (length of theList) times
			set theLowItem to ""
			repeat with a from 1 to (length of theList)
				if a is not in theIndexList then
					set theCurrentItem to item a of theList
					if theLowItem is "" then
						set theLowItem to theCurrentItem
						set theLowItemIndex to a
					else if (name of theCurrentItem) comes before (name of theLowItem) then
						set theLowItem to theCurrentItem
						set theLowItemIndex to a
					end if
				end if
			end repeat
			set end of theSortedList to theLowItem
			set end of theIndexList to theLowItemIndex
		end repeat
		return theSortedList
	end tell
end sortObjlist