Shove-it: move windows back on to the screen

Post your working scripts, libraries and tools for AHK v1.1 and older
ahbi
Posts: 17
Joined: 15 Jun 2016, 17:11

Shove-it: move windows back on to the screen

28 Apr 2018, 11:32

Ever get annoyed that windows appear partially off screen? Ever had that annoying window that appears way off screen and you can't grab its title bar?
Shove-It makes program windows behave by keeping them from going off the screen. It also keeps them from slipping under the Taskbar when you have the Taskbar at the top, where it surely belongs.
Well Shove-It has been shoving windows back on to the screen for the last 20 years. Once described as one of "10 minor Windows utilities I can't do without" (by some guy on the Internet). The only problem is, the person that made Shove-it went away. It has been unavailable for ~10 years. But now it is back!!!

With Shove-it (AHK), most of the functionality of the old Shove-it has returned.

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
#SingleInstance force
; #Persistent
; #InstallKeybdHook ; needed for $ HotKey modifier (no trigger self) and others (e.g, Inactivity)
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.

/*
;==============================================================================
;========== What This Script Does  ============================================
;==============================================================================
;
;  Shove-It_AHK
;
; Takes windows that are off the screen and shoves them back on the screen (resizing if needed).
; In multi-monitor settings, shoves windows that bridges screens back onto 1 screen.
;
; The original Shove-It (v1.5a) was created by Phord Software in ~1997
;
; Their website (http://www.phord.com) is now defunct, but their software -- written for the age of Win95 -- still runs well on Win10.  I have used it daily on my computers for the last 20 years.  (Good return on the registration fee there.)
;
; In a spell of procrastination or boredom, I felt the urge to re-write Shove-It using AutoHotKey. Most of the original features are implemented in this version (no quick-drag or windoids).  Actually, with the Phrod website down and the Help files in a format Win10 doesn’t use (*.HLP) I really don’t know how the features I don’t use work.  I could fire up a Win7 VM or decompile the HLP file, but I just don’t care that much.  After all, I don’t use those features and my testing regime is “For a week, I used it the way I normally use Shove-it, and it seemed to work” (not a formal system).  
; I have no idea why I felt the need to re-write a perfectly functional program, but I did and it was fun.
;
; (c) 2018, Ahbi Santini
;
;==============================================================================

*/

; =========================================
; ===== Includes
; =========================================
;BeginRegion
; #Include %A_ScriptDir%\Library\Library.ahk 
; #Include %A_ScriptDir%\Library\HtmDlg.ahk 
;EndRegion

; =========================================
; ===== Configuration Variables
; =========================================
;BeginRegion

; none outside the INIfile
; See Main() below

;EndRegion

; ------------------------------------------------------------------------
; Set up SystemTray Menu
;BeginRegion
FileGetTime, ScriptLastModified, %A_ScriptFullPath%, M
FormatTime, ScriptLastModifiedX, %ScriptLastModified%
trayTipString  =	;  Only the first 127 characters of Text are displayed
			( LTrim
				---- %A_ScriptName% ----
				Shoves windows that are off the screen back on the screen.
				
				Last modified:
				%ScriptLastModifiedX%
			)
	Menu, Tray, Tip, %trayTipString% ; Create system tray mouse-over tooltip
; Set script icon (if there is one; icon name is same as the script's name w/ *.ico extension)
IconName := getSupportFilename("ico")
IfExist %IconName%
{
	Menu, Tray, Icon, %IconName%
}
GoSub Main_CreateDebugMenu

;EndRegion

;==============================================================================
;========== Main Script  ======================================================
;==============================================================================
;BeginRegion
; SetTitleMatchMode, 1
DetectHiddenText, On

; Set up the Settings variables.  Everything else assumes they are already set up.  So, better do that first.
global Settings := new SettingsClass()
global scriptStartTime := A_Now

; Shove things any time a window is created by hooking into SHELLHOOK
Gui +LastFound
hWnd := WinExist()
DllCall( "RegisterShellHookWindow", UInt,hWnd )
MsgNum := DllCall( "RegisterWindowMessage", Str,"SHELLHOOK" )
OnMessage( MsgNum, "ShellMessage" )

; When the script ends, perform this function
OnExit("ExitFunction")

; create some GUIs
GoSub DebugGuiCreate
; GoSub MenuHandler_ShowDebugGUI
; GoSub MenuHandler_ShowPropertiesGUI

; now do the actual work
Monitors := [{}]
Monitors := buildMonitorsArray()
Loop
{ ; check for windows that need a good shoving
	
	isLButtonDown := GetKeyState("LButton","P")
	if (!isLButtonDown) ; avoid shoving windows while you're moving the window
	{
		GoSub Main_TestAndShoveWindows ; the SHELLHOOK above is the reason for moving this to a subroutine.  Also useful if we ever use a menu item to shove things.
	}

	; CPU cycle saving Sleep the loop
	Sleep, Settings.shovePeriod_Secs * 1000
}

Return

;EndRegion





;==============================================================================
;========== HotKeys  ==========================================================
;==============================================================================
;BeginRegion
	; -------- Hotkey Definitions Reminder ----------
	; # - Win 
	; ! - Alt
	; ^ - Control
	; + - Shift
	; ~ - pass key command to OS
	; example -- $>!v:: 		; $ (no trigger self)  > (right key of a pair) ---- Alt (!) + V


;EndRegion

; =========================================
; ===== Functions
; =========================================
;BeginRegion

buildMonitorsArray()
{ ; create an array of MonitorClass objects that detail every monitor in the system
	Monitors := [{}]
	List_MonSection = ; Debug output string

	SysGet, MonitorCount, MonitorCount
	SysGet, MonitorPrimary, MonitorPrimary
	List_MonSection.= "Monitor Count:`t" MonitorCount "`nPrimary Monitor:`t" MonitorPrimary "`n"
	Loop, %MonitorCount%
	{
		Monitors[A_Index] := new MonitorClass(A_Index)
		
		List_MonSection.= "`n" Monitors[A_Index].toString()
	}
	return Monitors
}
	
buildWindowsArray(Monitors)
{ ; create an array of what windows are in existence right now
	List_WinSection =  ; Debug output string

	Windows := [{}]
	WinGet,WinList,List,,,Program Manager ; get a list of all the windows in the system
	loop,%WinList%
	{
		CurrentHWND := WinList%A_Index%
		WinGetTitle,WinTitle,ahk_id %CurrentHWND%
		If WinTitle AND !InStr(List,WinTitle)
		{
			; create the window object
			Windows[A_Index] := new WindowClass(CurrentHWND)

			; figure out what monitor owns the window (if it spans 2 monitors, figure out the most owning monitor)
			targetMonitor := whichMonitor(Windows[A_Index], Monitors)
			Windows[A_Index].monitor := targetMonitor

			; determine if the window should be shoved (skip windows that we shouldn't touch)
			if (Windows[A_Index].class != "Windows.UI.Core.CoreWindow") && (Windows[A_Index].isMinMax == 0)
			{
				shoveFlags := determineHowToShove(Windows[A_Index], Monitors[targetMonitor])
			}
			else
			{
				shoveFlags := {}
				shoveFlags.needsShove := false
			}
			Windows[A_Index].shoveFlags := shoveFlags


			
			; Debug GUI output
			; -------------------------------------
				if (Windows[A_Index].class != "Windows.UI.Core.CoreWindow")
				{
					List_WinSection.= "`n`n"
					List_WinSection.= Windows[A_Index].toString()
					List_WinSection.="`tMonitor: " Windows[A_Index].monitor 
					if(Windows[A_Index].monitor > 0) 
					{
						monTmp := Windows[A_Index].monitor
						List_WinSection.= "`n`tMLeft: " Monitors[monTmp].Left "`t`tMTop: " Monitors[monTmp].Top  "`tMRight: " Monitors[monTmp].Right  "`tMBottom: " Monitors[monTmp].Bottom
					}
					else
					{
						List_WinSection.= "`n`tMLeft: " "---" "`t`tMTop: "  "---"  "`tMRight: "  "---"  "`tMBottom: "  "---"
					}
					strTmp := "Do NOT Move"
					if (shoveFlags.needsShove)
					{
						; strTmp := shoveWindow(Windows[A_Index], Monitors[targetMonitor])

						List_WinSection.= "`n`tShove?: " shoveFlags.needsShove "`tReX: " shoveFlags.needsResizeX  "`tReY: " shoveFlags.needsResizeY
						List_WinSection.= "`n`tS->R: " shoveFlags.shoveFromLeft "`tS->L: " shoveFlags.shoveFromRight "`tS->D: " shoveFlags.shoveFromTop "`tS->U: " shoveFlags.shoveFromBottom
					}
					; List_WinSection.= "`n`tCmd: " strTmp
					List_WinSection.= "`n`n"
				}
		}
	}
	List_WinSection.="`n" 

	; update the Debug GUI
	List =
	GuiControl, Debug: ,WinList,%List%
	List := List_MonSection . List_WinSection
	GuiControl, Debug: ,WinList,%List%

	
	return Windows
}

whichMonitor(window, Monitors)
{ ; determine to which monitor a window belongs (by how much of the window is on a monitor; default Primary)
	strTmp := "WinT: " . window.title "`n"
	
	SysGet, MonitorPrimary, MonitorPrimary
	bestMon := MonitorPrimary
	bestPerct := 1 / 100000 ; some value greater than zero
	Loop, % Monitors.length()
	{
		perctInMon := percentageInMonitor(window, Monitors[A_Index])

		if(perctInMon > bestPerct)
		{
			bestPerct := perctInMon
			bestMon := A_Index
		}
	}

	return bestMon
}

percentageInMonitor(window, monitor)
{ ; compute how much of the window is within/displayed by the monitor
	percentage := 0
	
	winArea := window.Width * window.Height
	subWindow := {}
	
	; create a rectangle that is the portion of the window displayed by the monitor
	subWindow.Top := greaterOf( window.Top, monitor.TopWA )
	subWindow.Bottom := lesserOf( window.Bottom, monitor.BottomWA )
	subWindow.Left := greaterOf( window.Left, monitor.LeftWA )
	subWindow.Right := lesserOf( window.Right, monitor.RightWA )
	
	; clean up the sub-window rectangle
	subWindow.Width := subWindow.Right - subWindow.Left
	if ( subWindow.Width < 0)
	{
		subWindow.Width := 0
	}
		subWindow.Height := subWindow.Bottom - subWindow.Top
	if ( subWindow.Height < 0)
	{
		subWindow.Height := 0
	}
	subArea := subWindow.Width * subWindow.Height
	
	; how much of the window is within the monitor?
	percentage := subArea / winArea

		; Debug output
		; StrTmp.= "`nMon: " . A_Index . "`t%: " .  perctInMon
		; strTmp.= "`n`twT: " window.Top "`twB: " window.Bottom  "`twL: " window.Left  "`twR: " window.Right
		; strTmp.= "`n`tmT: " monitor.TopWA "`tmB: " monitor.BottomWA  "`tmL: " monitor.LeftWA  "`tmR: " monitor.RightWA
		; strTmp.= "`n`tsT: " subWindow.Top "`tsB: " subWindow.Bottom  "`tsL: " subWindow.Left  "`tsR: " subWindow.Right
		; strTmp.= "`n`tsHeight: " subWindow.Height "`tsWidth: " subWindow.Width
		; strTmp.= "`n`twinArea: " winArea "`tsubArea: " subArea "`t%: " .  perctInMon
		; infoMsgBox(StrTmp)

	return percentage
}

determineHowToShove(window, targetMonitor)
{ ; create a shoveFlags object that has a bunch of bit flags that indicate which way/if a window is to be shoved/resized
	shoveFlags := {}
	
	; strTmp:= Window.Title
	; strTmp.= "`nLeft: " Window.Left "`tTop: " Window.Top  "`tRight: " Window.Right  "`tBottom: " Window.Bottom
	; strTmp.= "`nMLeft: " targetMonitor.LeftWA "`tMTop: " targetMonitor.TopWA  "`tMRight: " targetMonitor.RightWA  "`tMBottom: " targetMonitor.BottomWA
	; infoMsgBox(strTMp, 3)

	; off the Left side
	if (window.Left < targetMonitor.LeftWA)
	{
		shoveFlags.shoveFromLeft := true
	}
	else
	{
		shoveFlags.shoveFromLeft := false
	}
	; off the Right side
	if (window.Right > targetMonitor.RightWA)
	{
		shoveFlags.shoveFromRight := true
	}
	else
	{
		shoveFlags.shoveFromRight := false
	}
	; off the Top
	if (window.Top < targetMonitor.TopWA)
	{
		shoveFlags.shoveFromTop := true
	}
	else
	{
		shoveFlags.shoveFromTop := false
	}
	; off the Bottom
	if (window.Bottom > targetMonitor.BottomWA)
	{
		shoveFlags.shoveFromBottom := true
	}
	else
	{
		shoveFlags.shoveFromBottom := false
	}
	
	; resize flags
	if (window.Width > targetMonitor.WidthWA)
	{
		shoveFlags.needsResizeX := true
	}
	else
	{
		shoveFlags.needsResizeX := false
	}
	if (window.Height > targetMonitor.HeightWA)
	{
		shoveFlags.needsResizeY := true
	}
	else
	{
		shoveFlags.needsResizeY := false
	}
	; summary flags
	shoveFlags.needsShove := arrayOR([shoveFlags.shoveFromLeft, shoveFlags.shoveFromRight, shoveFlags.shoveFromTop, shoveFlags.shoveFromBottom])
	
	; really not bit flags, which annoys me but the best place for them
	shoveFlags.targetMonitorNumber := targetMonitor.number
	shoveFlags.percentageInMonitor := percentageInMonitor(window, targetMonitor)

	; only allow shoving if the settings allow it
	shoveFlags := gateFlagsWithSettings(shoveFlags)
	
	return shoveFlags
}

gateFlagsWithSettings(shoveFlags)
{ ; now take the raw shoveFlags object and AND it with the Settings variables
	shoveFlags.shoveFromLeft   := (shoveFlags.shoveFromLeft   AND Settings.canShoveFromLeft)
	shoveFlags.shoveFromRight  := (shoveFlags.shoveFromRight  AND Settings.canShoveFromRight)
	shoveFlags.shoveFromTop    := (shoveFlags.shoveFromTop    AND Settings.canShoveFromTop)
	shoveFlags.shoveFromBottom := (shoveFlags.shoveFromBottom AND Settings.canShoveFromBottom)
	shoveFlags.needsResizeX    := (shoveFlags.needsResizeX    AND Settings.canResize)
	shoveFlags.needsResizeY    := (shoveFlags.needsResizeY    AND Settings.canResize)

	; summary flags
	shoveFlags.needsShove := arrayOR([shoveFlags.shoveFromLeft, shoveFlags.shoveFromRight, shoveFlags.shoveFromTop, shoveFlags.shoveFromBottom])
	
	if(shoveFlags.needsShove)
	{
		; tmpStr := "Percent IN: " shoveFlags.percentageInMonitor "`n" "Percent 2B: " Settings.percentOffBeforeShove
		if ( (1 - shoveFlags.percentageInMonitor) >= (Settings.percentOffBeforeShove / 100) )
		{
			; tmpStr .= "`n" "SHOVE!!!"
		}
		else 
		{
			shoveFlags.needsShove := false
			; tmpStr .= "`n" "Ignore"
		}
		; infoMsgBox(tmpStr, 2)
	}
	
	return shoveFlags
}

shoveWindow(window, targetMonitor)
{ ; actually shove the window
	strTmp := -1 ; set to an error value

	shoveFlags := window.shoveFlags ; copy to a more manageable name

	if (shoveFlags.needsShove) ; skip if it doesn't need shoving
	{
		; initialize to the same dimensions as the original window
		WinID := window.WinID
		newLeft := window.Left
		newTop := window.Top
		newWidth := window.Width
		newHeight := window.Height

		; resize
		if (shoveFlags.needsResizeX)
		{
			newWidth:= targetMonitor.WidthWA
		}
		if (shoveFlags.needsResizeY)
		{
			newHeight:= targetMonitor.HeightWA
		}
		
		; determine how to move (set left corner)
		if (shoveFlags.shoveFromLeft)
		{
			newLeft:= targetMonitor.LeftWA
		}
		else if (shoveFlags.shoveFromRight)
		{
			newLeft:= targetMonitor.RightWA - newWidth
		}
		
		; determine how to move (set top corner)
		if (shoveFlags.shoveFromTop)
		{
			newTop:= targetMonitor.TopWA
		}
		else if (shoveFlags.shoveFromBottom)
		{
			newTop:= targetMonitor.BottomWA - newHeight
		}

		; move
		strTmp := "WinMove, ahk_id " . WinID . ", , " . newLeft . ", " . newTop . ", " . newWidth ", " . newHeight
		WinMove, ahk_id %WinID%, , %newLeft%, %newTop%, %newWidth%, %newHeight%
		Settings.shovedAnotherWindow()
	}
	return strTmp
}

	; helper functions
	; ------------------------
greaterOf(A,B)
{
	if( A >= B )
	{
		return A
	}
	else
	{
		return B
	}
}

lesserOf(A,B)
{
	if( A <= B )
	{
		return A
	}
	else
	{
		return B
	}
}

AddCommasToNumber(numb, useDecimals=false)
{ ; re-format a number with commas (or as dictated by the locale)
	; from Skan & PhiLho
	; http://autohotkey.com/board/topic/41644-formatting-numbers-with-commas/?p=259900
	; https://autohotkey.com/board/topic/11642-addcommas-function-solved/#entry74565
	
	LOCALE_USER_DEFAULT = 0x400
	VarSetCapacity(newNumb, 32)
	DllCall("GetNumberFormat"
			, "UInt", LOCALE_USER_DEFAULT ; LCID Locale
			, "UInt", 0 ; DWORD dwFlags
			, "Str", numb ; LPCTSTR lpValue
			, "UInt", 0 ; CONST NUMBERFMT* lpFormat
			, "Str", newNumb ; LPTSTR lpNumberStr
			, "Int", 32) ; int cchNumber

	if(!useDecimals)
	{
		newNumb := SubStr(newNumb,1,StrLen(newNumb) - 3)
	}
	return newNumb
}

arrayOR(array)
{ ; take an array of Booleans of arbitrary length and OR them together.
	; Input variables
	; 	array[] (Booleans) -> an array of Booleans (e.g., bit flags)
	; Output variables
	; 	result (Boolean) -> the OR of the array[]
	For index, arrayVal in array
	{
		if (arrayVal)
		{
			return true  ; at least 1 value is true
		}
	}
	return false ; if you got here all the values were false
}

reverseArrayOrder(inArray)
{ ; take an array and re-index it such that the order is reversed (an array of [1]-[10], [1] goes to [10] and [10] goes to [1])
	outArray := {}
	loop, % inArray.length()
	{
		inArrayIndex := inArray.length() - A_Index + 1
		outArray[A_Index] := inArray[inArrayIndex]
	}
	return outArray
}

getSupportFilename(fileExten, addDot=true)
{ ; provides a filename of a support file (e.g., ini, ico), regardless of whether the script is compiled or not
	; Input variables
	; 	fileExten -> the file extension of the support file (place in quotes)
	; 	addDot -> should a '.' be added before fileExten? (Default=true)
	; Output variables
	; 	a filename of the script with the extension replaced with fileExten

	if(fileExten != "")
	{
		if(addDot)
		{
			replacementStr = .%fileExten%
		}
		else
		{
			replacementStr = %fileExten%
		}
	}
	else
	{
		replacementStr := ""
	}
	if(A_IsCompiled)
	{
		StringReplace, returnName, A_ScriptName,.exe,%replacementStr%, All
	}
	else
	{
		StringReplace, returnName, A_ScriptName,.ahk,%replacementStr%, All
	}
	return returnName
}

ConvertSecondsToDDHHMMSS(TimeInSeconds, ShowLeadingZeros = true, LastLeadingZero = "m")
{ ; convert seconds to Days, Hours, Minutes & Seconds (ddhhmmss)
	; Input variables
	; 	TimeInSeconds -> time in seconds
	;   ShowLeadingZeros -> should leading zeros be shown in te output (default = true)
	;   LastLeadingZero -> provides a middle ground for leaidng zeros; when ShowLeadingZeros==true, where do the leading zeros start?
	;                      For example, a value of "m" would result in an output of "00:ss".  A value of "h" would result in an output of "00:mm:ss"
	;                      Valid values are "d", "h", and "m".  Anything else defaults to "d".
	; Output variables
	; 	ddhhmmss -> time in minutes & seconds (mmss)

	; from  http://www.autohotkey.com/docs/commands/FormatTime.htm
	time = 19990101  ; *Midnight* of an arbitrary date.
    time += %TimeInSeconds%, seconds
	if(TimeInSeconds >= 86400) ; > 1 day
	{
		FormatTime, hhmmss, %time%, HH:mm:ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
		NumberOfDays := TimeInSeconds // 86400
		NumberOfDays := Floor(NumberOfDays) ; // doesn't convert to Int as much as I'd like it to
		ddhhmmss := NumberOfDays . ":" . hhmmss
		if(NumberOfDays < 10)
		{
			ddhhmmss := "0" . NumberOfDays . ":" . hhmmss
		}
	}
	else if(TimeInSeconds < 60) ; less than 1 minute
	{
		if(ShowLeadingZeros)
		{
			if(LastLeadingZero = "m")
			{
				FormatTime, ddhhmmss, %time%, '00':ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
			}
			else if(LastLeadingZero = "h")
			{
				FormatTime, ddhhmmss, %time%, '00:00':ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
			}
			else 
			{
				FormatTime, ddhhmmss, %time%, '00:00:00':ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
			}
		}
		else
		{
			ddhhmmss := TimeInSeconds ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
		}
	}
	else if(TimeInSeconds < 3600) ; less than 1 hour
	{
		if(ShowLeadingZeros)
		{
			if(LastLeadingZero = "m")
			{
				FormatTime, ddhhmmss, %time%, mm:ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
			}
			else if(LastLeadingZero = "h")
			{
				FormatTime, ddhhmmss, %time%, '00':mm:ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
			}
			else 
			{
				FormatTime, ddhhmmss, %time%, '00:00':mm:ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
			}
		}
		else
		{
			FormatTime, ddhhmmss, %time%, mm:ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
		}
	}
	else
	{
		if(ShowLeadingZeros)
		{
			if(LastLeadingZero = "m")
			{
				FormatTime, ddhhmmss, %time%, HH:mm:ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
			}
			else if(LastLeadingZero = "h")
			{
				FormatTime, ddhhmmss, %time%, HH:mm:ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
			}
			else 
			{
				FormatTime, ddhhmmss, %time%, '00':HH:mm:ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
			}
		}
		else
		{
			FormatTime, ddhhmmss, %time%, HH:mm:ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
		}
	}

    return ddhhmmss  ; This method is used to support more than 24 hours worth of sections.
}



	; functions triggered by pre-registered events
	; ----------------------------------------------------------
ShellMessage( wParam,lParam )
{ ; called when Windows creates an event (e.g., a window is created)
	; not useful in this script but standard actions for ShellMessage
	WinGetTitle, Title, ahk_id %lParam%
	WinGetClass, Class, ahk_id %lParam%  ; is a String with # first (e.g., "#123456") or an actual name
		
	; http://msdn.microsoft.com/en-us/library/windows/desktop/ms644991%28v=vs.85%29.aspx
	If ( wParam = 1 ) ;  HSHELL_WINDOWCREATED := 1
	{
		; shove the windows
		GoSub Main_TestAndShoveWindows
	}
	return
}

ExitFunction()
{ ; called when the script ends/closes
	; this isn't the best way to do this.  I should do something where the value is calculated in real-time.  Probably in the get/set of the object.
	; but it is fluff, and this works well enough, and I am moving on
	scriptEndTime := A_Now
	scriptEndTimeSave := scriptEndTime
	EnvSub, scriptEndTime, %scriptStartTime%, seconds
	runTimeSave := Settings.runningTime
	Settings.runningTime := Settings.runningTime + scriptEndTime
		; tmpStr := "Start: " scriptStartTime "`n" "End  : " scriptEndTimeSave "`n" "RunTm: " runTimeSave "`n" "NewTm: " Settings.runningTime
		; infoMsgBox(tmpStr)
	Settings.writeINIFileOnClose()
	return
}

;EndRegion

; =========================================
; ===== Subroutines / Labels
; =========================================
;BeginRegion
Main_TestAndShoveWindows:
{ ; 1/2 of the main program, test all the windows and shove them if needed

	; get a list of all windows currently in the system
	Windows := [{}]
	Windows := buildWindowsArray(Monitors)
	
	; now test the windows' position and shove them as needed
	Loop, % Windows.length()
	{
		targetMonitor := Windows[A_Index].monitor
		shoveWindow(Windows[A_Index], Monitors[targetMonitor]) ; both tests and shoves the window
	}
}
Return

Main_CreateMiniMenu:
{ ; delete the Tray Menu and re-create it with the minimal options
	Menu, Tray, DeleteAll
	
	Menu, tray, NoStandard ; Remove all standard menu items
	Menu, Tray, add, Properties, MenuHandler_ShowPropertiesGUI
	Menu, Tray, Default, Properties
	Menu, Tray, add  ; Creates a separator line.
	Menu, Tray, add, Exit Shove-It, MenuHandler_ExitApp
}
Return

Main_CreateDebugMenu:
{ ; delete the Tray Menu and re-create it with the debug options
	Menu, Tray, DeleteAll
	
	Menu, tray, NoStandard ; Remove all standard menu items
	Menu, Tray, add, Properties, MenuHandler_ShowPropertiesGUI
	Menu, Tray, Default, Properties
	Menu, Tray, add  ; Creates a separator line.
	Menu, Tray, add, Write _DEFAULT_ INI values, MenuHandler_WriteDefaultVariables
	Menu, Tray, add, Re-load INI values, MenuHandler_ReLoadINIVariables
	Menu, Tray, add  ; Creates a separator line.
		Menu, SubMenu_StandardMenuItems, add
		Menu, SubMenu_StandardMenuItems, DeleteAll
	Menu, tray, add, Standard Menu, :SubMenu_StandardMenuItems
	Menu, SubMenu_StandardMenuItems, Standard
	Menu, Tray, add, Show Debug GUI, MenuHandler_ShowDebugGUI
	Menu, Tray, add  ; Creates a separator line.
	Menu, Tray, add, Exit Shove-It, MenuHandler_ExitApp
}
Return

MenuHandler_ShowPropertiesGUI:
{ ; create and show the Properties GUI
	GoSub PropertiesGuiCreate
	GoSub PropertiesGuiUpdateSettings
	Gui, Properties: Show, w420 h290, %A_Scriptname%
}
Return

MenuHandler_ShowDebugGUI:
{ ; show the DEBUG GUI (which is always updating with debug info about the windows, unless I changed that in a future version)
	tmpHgt := A_ScreenHeight - 75
	Gui, Debug: Show, w600 h%tmpHgt% X0 Y0
}
Return

MenuHandler_ExitApp:
	ExitApp
Return

MenuHandler_ReLoadINIVariables:
{ ; re-reads the INI file
	Settings.readINIFile()
}
Return

MenuHandler_WriteDefaultVariables:
{ ; Writes out default INI values
	IniName := getSupportFilename("ini")
	Settings.writeDefaultINIFile(IniName)
	Settings.readINIFile()
}
Return


;EndRegion

; =========================================
; ===== GUI Subroutines / Labels
; =========================================
;BeginRegion
	; Properties GUI
	; --------------------------
	;BeginRegion

PropertiesGuiCreate:
{
	Gui, Properties: New, -MinimizeBox -MaximizeBox +Theme -DPIScale, %A_Scriptname%
	Gui, Properties: Add, Button, x230 y260 w80 h20 gPropertiesButtonOK, &OK
	Gui, Properties: Add, Button, x327 y260 w80 h20 gPropertiesGuiEscape, &Cancel
	Gui, Properties: Add, Tab3, x10 y10 w400 h240 vTabID, General|Advanced|Statistics|About

	Gui, Properties: Tab, 1 ; General options
	Gui, Properties: Add, GroupBox, x50 y50 w250 h100, Shove-it from the: 
	Gui, Properties: Add, CheckBox, x150 y70 h20 vCheckboxTopID, Top
	Gui, Properties: Add, CheckBox, x150 y125 h20 vCheckboxBottomID, Bottom
	Gui, Properties: Add, CheckBox, x60 y95 h20 vCheckboxLeftID, Left
	Gui, Properties: Add, CheckBox, x250 y95 h20 vCheckboxRightID, Right
	Gui, Properties: Add, CheckBox, x50 y160 h23 vCheckboxResizeID, Resize windows when necessary
	Gui, Properties: Add, CheckBox, x50 y185 h23 vCheckboxHideDebugID, Hide Debug menu options
	; I added the hideIcon feature and then realized it was kind of pointless and annoying.
	; Once hidden, you can't get to the Properties GUI to undo it.  So, you have to resort to manually editing the INI file.
	; Gui, Properties: Add, CheckBox, x50 y210 h23 vCheckboxHideIconID, Hide Shove-it Icon
	; GuiControl, Properties: Disable, Hide Shove-it Icon
	Gui, Properties: Add, CheckBox, x50 y210 h23 vCheckboxWindoidID, Shove windoids off of active window buttons
	GuiControl, Properties: Disable, Shove windoids off of active window buttons ; I will probably never enable this option.  I have never used this but had an odd affection for it.  Hence, its survival here. 

	Gui, Properties: Tab, 2 ; Advanced options
	Gui, Properties: Add, Text, x50 y75 h20 +0x200, Auto-shove every: 
	shovePeriod_Str := Settings.shovePeriod_Str
	Gui, Properties: Add, DropDownList, x150 y75 w120 vDropDownListID Choose1 AltSubmit, %shovePeriod_Str%
	Gui, Properties: Add, Text, x300 y75 h20 +0x200, seconds.
	Gui, Properties: Add, GroupBox, x50 y125 w330 h75
	Gui, Properties: Add, Slider, x55 y135 w320 TickInterval10 ToolTip Range0-100 AltSubmit vSliderVariable gPropertiesSliderEvent, SliderVariable
	Gui, Properties: Add, Text, x55 y175 h20 +0x200 w320 Center vSliderTextID, Only shove windows which are at least 888 off the screen.
	GoSub PropertiesSliderEvent

	Gui, Properties: Tab, 3 ; Statistics
	Gui, Properties: Add, Text, x50 y70 h20 +0x200 vStatisticsInstalledTextID, Installed on: Wednesday, December, 25, 2019.
	Gui, Properties: Add, Text, x50 y100 h20 +0x200 vStatisticsTimeRunTextID, Running time: 1,000,000,000 days, 88 hours, and 10 minutes.
	Gui, Properties: Add, Text, x50 y130 h20 +0x200 vStatisticsWinsShovedTextID, Shoved 888,888,888,888 windows so far.

	Gui, Properties: Tab, 4 ; About
	aboutStr  =	
				( LTrim
					The original Shove-It (v1.5a) was created by Phord Software in ~1997
					
					`tTheir website (http://www.phord.com) is now defunct, but their software -- written for the age of Win95 -- still runs well on Win10.  I have used it daily on my computers for the last 20 years.  (Good return on the registration fee there.)
					
					`tIn a spell of procrastination or boredom, I felt the urge to re-write Shove-It using AutoHotKey. Most of the original features are implemented in this version (no quick-drag or windoids).  Actually, with the Phrod website down and the Help files in a format Win10 doesn’t use (*.HLP) I really don’t know how the features I don’t use work.  I could fire up a Win7 VM or decompile the HLP file, but I just don’t care that much.  After all, I don’t use those features and my testing regime is “For a week, I used it the way I normally use Shove-it, and it seemed to work” (not a formal system).  
					`tI have no idea why I felt the need to re-write a perfectly functional program, but I did and it was fun.

					(c) 2018, Ahbi Santini
				)
	Gui, Properties: Add, Edit, w380 h200 vAboutTextID  +Wrap +vScroll ReadOnly, %aboutStr%
	; Gui, Properties: Add, ActiveX, x10 y10 w400 h270 vWB, Shell.Explorer  ; The final parameter is the name of the ActiveX component.
	; aboutMsgBox(trayTipString)
	; AboutHtmlFilename := getSupportFilename("---AboutHtmDlg.htm", false)
	; AboutHtmlFilename := getTempFilename(AboutHtmlFilename)
	; WB.Navigate(AboutHtmlFilename)  ; This is specific to the web browser control.
}
Return

PropertiesGuiUpdateSettings:
{
	GuiControl, Properties: Choose, TabID, 1 ; really here for testing, so I can bring up the tab I am working on

	; Tab 1
	CheckboxTopID := Settings.canShoveFromTop
	GuiControl, Properties: , CheckboxTopID, %CheckboxTopID%
	CheckboxBottomID := Settings.canShoveFromBottom
	GuiControl, Properties: , CheckboxBottomID, %CheckboxBottomID%
	CheckboxLeftID := Settings.canShoveFromLeft
	GuiControl, Properties: , CheckboxLeftID, %CheckboxLeftID%
	CheckboxRightID := Settings.canShoveFromRight
	GuiControl, Properties: , CheckboxRightID, %CheckboxRightID%

	; CheckboxHideIconID := Settings.hideTrayIcon
	; GuiControl, Properties: , CheckboxHideIconID, %CheckboxHideIconID%
	CheckboxHideDebugID := Settings.hideDebugMenu
	GuiControl, Properties: , CheckboxHideDebugID, %CheckboxHideDebugID%
	CheckboxResizeID := Settings.canResize
	GuiControl, Properties: , CheckboxResizeID, %CheckboxResizeID%
	CheckboxWindoidID := Settings.canShoveWinoids
	GuiControl, Properties: , CheckboxWindoidID, %CheckboxWindoidID%
	
	; Tab 2
	DropDownListID := Settings.shovePeriod_Numb
	GuiControl, Properties: Choose, DropDownListID,  %DropDownListID%
	SliderVariable := Settings.percentOffBeforeShove
	GuiControl, Properties: , SliderVariable, %SliderVariable%
	GoSub PropertiesSliderEvent

	; Tab 3
	tmpVar := Settings.installedOn
	FormatTime, OutputVar , %tmpVar%, LongDate
	StatisticsInstalledTextID := "Installed on: " OutputVar
	GuiControl,  Properties: , StatisticsInstalledTextID, %StatisticsInstalledTextID%
	tmpStr := ConvertSecondsToDDHHMMSS(Settings.runningTime, false)
	tmpStrArr := StrSplit(tmpStr, ":")
	tmpStrArr := reverseArrayOrder(tmpStrArr)
	StatisticsTimeRunTextID := "Running time: " 
	if (tmpStrArr.length() = 4 ) ; has days
	{
		StatisticsTimeRunTextID .= AddCommasToNumber(tmpStrArr[4]) " days, "
	}
	if (tmpStrArr.length() >= 3 ) ; has hours
	{
		StatisticsTimeRunTextID .= tmpStrArr[3] " hours, and "
	}
	if (tmpStrArr.length() >= 2 ) ; has minutes
	{
		StatisticsTimeRunTextID .= tmpStrArr[2] " minutes."
	}
	GuiControl,  Properties: , StatisticsTimeRunTextID, %StatisticsTimeRunTextID%
	StatisticsWinsShovedTextID := "Shoved " AddCommasToNumber(Settings.numbWindowsShoved) " windows so far."
	GuiControl,  Properties: , StatisticsWinsShovedTextID, %StatisticsWinsShovedTextID%
}
Return

PropertiesSliderEvent:
{
	if (SliderVariable < 1)
	{
		GuiControl,  Properties: , SliderTextID, Shove windows which are off the screen, even a bit.
	}
	else if (SliderVariable > 99)
	{
		GuiControl,  Properties: , SliderTextID, Only shove windows which are totally off the screen.
	}
	else
	{
		GuiControl,  Properties: , SliderTextID, Only shove windows which are at least %SliderVariable%`% off the screen.
	}
}
Return

PropertiesGuiEscape:
PropertiesGuiClose:
	Gui, Properties: Cancel
Return

PropertiesButtonOK:
{
	Gui, Properties: Submit

	Settings.canShoveFromLeft      := CheckboxLeftID
	Settings.canShoveFromRight     := CheckboxRightID
	Settings.canShoveFromTop       := CheckboxTopID
	Settings.canShoveFromBottom    := CheckboxBottomID
	Settings.canResize             := CheckboxResizeID
	; Settings.hideTrayIcon          := CheckboxHideIconID
	Settings.hideDebugMenu         := CheckboxHideDebugID
	Settings.canShoveWinoids       := CheckboxWindoidID
	Settings.shovePeriod_Numb      := DropDownListID
	Settings.percentOffBeforeShove := SliderVariable
	Settings.writeINIFile()

	GoSub PropertiesGuiClose
}
Return
	; --------------------------
	;EndRegion

	; DEBUG GUI
	; --------------------------
	;BeginRegion

DebugGuiCreate:
{
	Gui, Debug: New, , DEBUG: %A_Scriptname%
	Gui, Debug:Add,ListBox,vWinList w580 r30
	GuiControl, Debug:+HScroll,WinList
	Gui Debug: +Delimiter`n
	Gui Debug: +Resize
	global List =
	global List_MonSection = 
}
Return

DebugGuiClose:
	Gui, Debug: Cancel
Return

DebugGuiSize:
{ ; resize the GUI controls
	if ErrorLevel = 1  ; The window has been minimized.  No action needed.
		return
	; Otherwise, the window has been resized or maximized. Resize the Edit control to match.
	NewWidth := A_GuiWidth - 20
	NewHeight := A_GuiHeight - 20
	GuiControl,  Debug: Move, WinList, W%NewWidth% H%NewHeight%
}
Return
	; --------------------------
	;EndRegion


;EndRegion


; =========================================
; ===== Classes
; =========================================
;BeginRegion

class MonitorClass
{ ; Class that has all/most of the information about a given monitor 
	__New(aNumber=1)
	{
		; Input variables
		; 	aNumber -> the monitor's ID number (e.g., 1 or 2)

		SysGet, MonitorPrimary, MonitorPrimary
		this._primaryMonitor := MonitorPrimary
		
		SysGet, MonitorName, MonitorName, %aNumber%
		SysGet, Monitor, Monitor, %aNumber%
		SysGet, MonitorWorkArea, MonitorWorkArea, %aNumber%

		this.number := aNumber
		this.name := MonitorName
		this.left := MonitorLeft
		this.right := MonitorRight
		this.top := MonitorTop
		this.bottom := MonitorBottom
		this.leftWA := MonitorWorkAreaLeft
		this.rightWA := MonitorWorkAreaRight
		this.topWA := MonitorWorkAreaTop
		this.bottomWA := MonitorWorkAreaBottom
		this.Width := MonitorRight - MonitorLeft
		this.Height := MonitorBottom - MonitorTop
		this.WidthWA := MonitorWorkAreaRight - MonitorWorkAreaLeft
		this.HeightWA := MonitorWorkAreaBottom - MonitorWorkAreaTop
		if ( aNumber == MonitorPrimary )
		{
			this.isPrimary := true
		}
		else
		{
			this.isPrimary := false
		}
		return this
	}
	
	toString()
	{ ; output debug information as a formatted string
		outStr := "Monitor:`t#" this.number "`n"
		outStr .= "`tName:`t" this.name "`t" "isPrimary:`t" this.isPrimary "`n"
		; Δ - file needs to be saved/encoded as Unicode/UTF-8-BOM to see the Delta symbol
		tmpVal := this.left - this.leftWA
		outStr .= "`tLeft:`t" this.left " (" this.leftWA " work; Δ " tmpVal ")`n"
		tmpVal := this.right - this.rightWA
		outStr .= "`tRight:`t" this.right " (" this.rightWA " work; Δ " tmpVal ")`n`"
		tmpVal := this.top - this.topWA
		outStr .= "`tTop:`t" this.top " (" this.topWA " work; Δ " tmpVal ")`n"
		tmpVal := this.bottom - this.bottomWA
		outStr .= "`tBottom:`t" this.bottom " (" this.bottomWA " work; Δ " tmpVal ")`n"
		tmpVal := this.width - this.widthWA
		outStr .= "`tWidth:`t" this.Width " (" this.WidthWA " work; Δ " tmpVal ")`n"
		tmpVal := this.height - this.heightWA
		outStr .= "`tHeight:`t" this.Height " (" this.HeightWA " work; Δ " tmpVal ") `n"
		return outStr
	}
}

class WindowClass
{ ; Class that has all/most of the information about a given window.  Does not include GUI control (i.e., button-level) information 
	__New(WinHandle)
	{
		; Input variables
		; 	WinHandle -> the window's handle/ID number (e.g., 1 or 2)

		WinGetTitle,WinTitle,ahk_id %WinHandle%
		this.Title := WinTitle
		
		WinGetClass,WinClass,ahk_id %WinHandle%
		this.Class := WinClass
		
		WinGetPos, _X, _Y, _Width, _Height, ahk_id %WinHandle%
		this.X := _X
		this.Y := _Y
		this.Width := _Width
		this.Height := _Height
		this.Left := _X
		this.Right := _X + _Width
		this.Top := _Y
		this.Bottom := _Y + _Height

		WinGet,_WinID,ID,ahk_id %WinHandle% ; this really should already be WinHandle but in a fit of safety
		this.WinID := _WinID

		WinGet,_PID,PID,ahk_id %WinHandle%
		this.PID := _PID
		
		WinGet,_ProcessName,ProcessName,ahk_id %WinHandle%
		this.ProcessName := _ProcessName
		
		WinGet,_ProcessPath,ProcessPath,ahk_id %WinHandle%
		this.ProcessPath := _ProcessPath

		WinGet,WinMinMax, MinMax, ahk_id %WinHandle%
		this.isMinMax := WinMinMax

		WinGet,WinStyle, Style, ahk_id %WinHandle%
		this.Style := WinStyle
		
		WinGet,WinExStyle, ExStyle, ahk_id %WinHandle%
		this.ExStyle := WinExStyle
		if (WinStyle & 0x8000000)  ; 0x8000000 is WS_DISABLED.
		{
			this.isDisabled := true
		}
		else
		{
			this.isDisabled := false
		}
		if (WinStyle & 0x10000000)  ; 0x10000000 is WS_VISIBLE
		{
			this.isVisible := true
		}
		else
		{
			this.isVisible := false
		}
		if (WinExStyle & 0x8)  ; 0x8 is WS_EX_TOPMOST.
		{
			this.isAlwaysOnTop := true
		}
		else
		{
			this.isAlwaysOnTop := false
		}

		return this
	}
	
	toString()
	{ ; output debug information as a formatted string
		outStr := ""
		outStr .= this.Title "`n"
		outStr .= "`tClass: " this.Class "`tPID: " this.PID "`tWinID (HWND): " this.WinID "`n"
		outStr .= "`tProcessName: " this.ProcessName "`n"
		outStr .= "`tProcessPath: " this.ProcessPath "`n"
		outStr .= "`tMin/Max: " this.isMinMax "`tDisabled: " this.isDisabled "`tVisible: " this.isVisible "`n"
		outStr .= "`tOnTop: " this.isAlwaysOnTop "`t" "`tStyle: " this.Style  "`t" "`tExStyle: " this.ExStyle "`n"
		outStr .= "`tX: " this.X "`t`tY: " this.Y  "`t" "`tWidth: " this.Width  "`tHeight: " this.Height "`n"
		outStr .= "`tLeft: " this.Left "`tTop: " this.Top  "`t" "`tRight: " this.Right  "`tBottom: " this.Bottom "`n"
		return outStr
	}
}

class SettingsClass
{ ; Class that handles the settings variables, read/writes them to an INI file
	__New(aIniFile="", aSectionName="")
	{
		; Input variables
		; 	aIniFile -> the INI file to read/write (default is A_Scriptname.INI)
		; 	aSectionName -> the section of the INI file to use (default is A_ComputerName)
		if(aIniFile="")
		{
			aIniFile := this.getSupportFilename("ini")
		}
		this.iniFile := aIniFile

		if(aSectionName="")
		{
			this.SectionName := A_ComputerName
		}
		else
		{
			this.SectionName := aSectionName
		}

		this.shovePeriod_Str := "0.5|1|2|3|5" ; not in INI file
		this.readINIFile()
		return this
	}
	
	; Properties
	; ---------------------------------------------
	shovePeriod_Secs
	{ ; make sure shovePeriod_Secs & shovePeriod_Str are in sync (i.e., they include each other's values)
		get
		{
			return this._shovePeriod_Secs
		}
		set
		{
			if value is number
			{
				tmpStr := this.shovePeriod_Str ; this is always defined via _New()
				if( !InStr(tmpStr, value) ) ; if a new value, add it to shovePeriod_Str, then sort shovePeriod_Str by period of time
				{
					tmpStr.= "|" value
					Sort, tmpStr, N D|
					this.shovePeriod_Str := tmpStr
				}
			}
			else
			{
				value := 0.5 ; default to first value in shovePeriod_Str
			}
			return this._shovePeriod_Secs := value
		}
	}
	
	shovePeriod_Numb
	{ ; a pseudo-property.  Really a function that pretends to be a property.  The Index of shovePeriod_Secs in shovePeriod_Str
		get
		{
			if (this.shovePeriod_Secs = "") 
			{
				this.shovePeriod_Secs := 0.5  ; default to first value in shovePeriod_Str
			}
			tmpSecs := this.shovePeriod_Secs
			tmpStr := this.shovePeriod_Str ; this is always defined via _New()
			Loop, Parse, tmpStr, |
			{
				infoStr := "tmpStr: " tmpStr "`n" "Field: " A_LoopField "`n" "tmpSecs: " tmpSecs  "`n" "Index: " A_Index 
				; infoMsgBox(infoStr, 3)
				if (A_LoopField = tmpSecs)
				{
					return A_Index
				}
			}
			return 1 ; default to first value in shovePeriod_Str
		}
		set
		{
			tmpStr := this.shovePeriod_Str ; this is always defined via _New()
			Loop, Parse, tmpStr, |
			{
				if (A_Index = value)
				{
					return this.shovePeriod_Secs := A_LoopField
				}
				retVal := A_LoopField
			}
			return this.shovePeriod_Secs := retVal  ; default to last value in shovePeriod_Str
		}
	}
	
	; I added the hideIcon feature and then realized it was kind of pointless and annoying.
	; Once hidden, you can't get to the Properties GUI to undo it.  So, you have to resort to manually editing the INI file.
	hideTrayIcon
	{
		get
		{
			return false
			; return this._hideTrayIcon
		}
		set
		{
			return false ; Do nothing
			; if( A_IconHidden != value ) ; has the setting changed?
			; {
				; if (value)
				; {
					; Menu, Tray, NoIcon
				; }
				; else
				; {
					; Menu, Tray, Icon
				; }
			; }
			; return this._hideTrayIcon := value
		}
	}
	
	hideDebugMenu
	{ ; redraw the Tray Menu based upon the value of this property
		get
		{
			return this._hideDebugMenu
		}
		set
		{
			if (value)
			{
				; I don't like calling functions outside a class definition but it is the right thing to do here
				GoSub Main_CreateMiniMenu
			}
			else
			{
				; I don't like calling functions outside a class definition but it is the right thing to do here
				GoSub Main_CreateDebugMenu
			}
			return this._hideDebugMenu := value
		}
	}
		
	; Methods (of suitable importance)
	; ---------------------------------------------
	shovedAnotherWindow()
	{ ; every time a window is shoved increment this.numbWindowsShoved and write to the INI value
		this.numbWindowsShoved++
		; maybe we should only write every X times something is shoved?
		errLvl := this.writeINIFileValue("Number_Of_Windows_Shoved", this.numbWindowsShoved)
		return errLvl
	}
	
	readINIFile(aIniFile="")
	{ ; get the INI variables from the file (various default values)
		; Input variables
		; 	aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
		if(aIniFile="")
		{
			aIniFile := this.iniFile
		}
		
		retValF := FileExist(aIniFile) ; indicate if the INI file already existed (and hopefully the default values where NOT used)
		IniRead, OutputVarSectionNames, aIniFile
		secVar := this.SectionName
		retValS := InStr(OutputVarSectionNames, %secVar%) ; is there the Section we need?
		retVal := (retValF AND retValS)
		if (!retVal) ; write an INI file if we don't have one with the needed Section
		{
			this.writeDefaultINIFile(aIniFile)
		}
		
		this.canShoveFromLeft      := this.readINIFileValue("Shove_From_Left", true, aIniFile)
		this.canShoveFromRight     := this.readINIFileValue("Shove_From_Right", true, aIniFile)
		this.canShoveFromTop       := this.readINIFileValue("Shove_From_Top", true, aIniFile)
		this.canShoveFromBottom    := this.readINIFileValue("Shove_From_Bottom", true, aIniFile)
		this.canResize             := this.readINIFileValue("Resize_If_Needed", true, aIniFile)
		this.hideTrayIcon          := this.readINIFileValue("Hide_Tray_Icon", false, aIniFile)
		this.hideDebugMenu         := this.readINIFileValue("Hide_Debug_Menu", true, aIniFile)
		this.canShoveWinoids       := this.readINIFileValue("Shove_Winoids", false, aIniFile)
		this.shovePeriod_Secs      := this.readINIFileValue("AutoShove_Seconds", 0.5, aIniFile)
		this.percentOffBeforeShove := this.readINIFileValue("Percentage_Off_Before_Shove", 0.000001, aIniFile)
		this.runningTime           := this.readINIFileValue("Running_Time", 0, aIniFile)
		this.numbWindowsShoved     := this.readINIFileValue("Number_Of_Windows_Shoved", 0, aIniFile)

		defInstallTime := A_Now
		this.installedOn           := this.readINIFileValue("Installed_On", defInstallTime, aIniFile)
		if (defInstallTime = this.installedOn)
		{
			; Installed_On is the one value that is never written outside of writeDefINI().  So, write it here too.
			this.writeINIFileValue("Installed_On", defInstallTime, aIniFile)
		}

		return retVal
	}
	

	writeINIFile(aIniFile="")
	{ ; write user changeable settings to the INI file
		; Input variables
		; 	aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
		; Output variables
		; 	errorLevel (Boolean) -> did an error occur while writing to the INI file?
		if(aIniFile="")
		{
			aIniFile := this.iniFile
		}
		errLvl := []
		errLvl[1]  := this.writeINIFileValue("Shove_From_Left",     this.canShoveFromLeft, aIniFile)
		errLvl[2]  := this.writeINIFileValue("Shove_From_Right",    this.canShoveFromRight, aIniFile)
		errLvl[3]  := this.writeINIFileValue("Shove_From_Top",      this.canShoveFromTop, aIniFile)
		errLvl[4]  := this.writeINIFileValue("Shove_From_Bottom",   this.canShoveFromBottom, aIniFile)
		errLvl[5]  := this.writeINIFileValue("Resize_If_Needed",    this.canResize, aIniFile)
		; errLvl[6]  := this.writeINIFileValue("Hide_Tray_Icon",       this.hideTrayIcon, aIniFile)
		errLvl[6]  := this.writeINIFileValue("Hide_Debug_Menu",     this.hideDebugMenu, aIniFile)
		errLvl[7]  := this.writeINIFileValue("Shove_Winoids",       this.canShoveWinoids, aIniFile)
		errLvl[8]  := this.writeINIFileValue("AutoShove_Seconds",   this.shovePeriod_Secs, aIniFile)
		errLvl[9]  := this.writeINIFileValue("Percentage_Off_Before_Shove", this.percentOffBeforeShove, aIniFile)
		
		retVal := this.arrayOR(errLvl) ; indicate if any error occurred
		return retVal
	}
	
	writeDefaultINIFile(aIniFile="")
	{ ; write default values ot the INI file, including the non-user changeable ones (i.e., installed_on)
		; Input variables
		; 	aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
		; Output variables
		; 	errorLevel (Boolean) -> did an error occur while writing to the INI file?
		if(aIniFile="")
		{
			aIniFile := this.iniFile
		}
		secVar := this.SectionName
		IniDelete, %aIniFile%, %secVar%
		
		errLvl := []
		errLvl[1]  := this.writeINIFileValue("Shove_From_Left", 1, aIniFile)
		errLvl[2]  := this.writeINIFileValue("Shove_From_Right", 1, aIniFile)
		errLvl[3]  := this.writeINIFileValue("Shove_From_Top", 1, aIniFile)
		errLvl[4]  := this.writeINIFileValue("Shove_From_Bottom", 1, aIniFile)
		errLvl[5]  := this.writeINIFileValue("Resize_If_Needed", 1, aIniFile)
		; errLvl[6]  := this.writeINIFileValue("Hide_Tray_Icon", 0, aIniFile)
		errLvl[6]  := this.writeINIFileValue("Hide_Debug_Menu", 1, aIniFile)
		errLvl[7]  := this.writeINIFileValue("Shove_Winoids", 0, aIniFile)
		errLvl[8]  := this.writeINIFileValue("AutoShove_Seconds", 0.5, aIniFile)
		errLvl[9]  := this.writeINIFileValue("Percentage_Off_Before_Shove", 0, aIniFile)
		errLvl[10] := this.writeINIFileValue("Installed_On", A_Now, aIniFile)
		errLvl[11] := this.writeINIFileValue("Running_Time", 0, aIniFile)
		errLvl[12] := this.writeINIFileValue("Number_Of_Windows_Shoved", 0, aIniFile)
		
		retVal := this.arrayOR(errLvl) ; indicate if any error occurred
		return retVal
	}
	
	writeINIFileOnClose()
	{ ; write non-user editable values on the close of the script.
		; Output variables
		; 	errorLevel (Boolean) -> did an error occur while writing to the INI file?
		errLvl := []
		errLvl[1] := this.writeINIFileValue("Running_Time", this.runningTime, aIniFile)
		errLvl[2] := this.writeINIFileValue("Number_Of_Windows_Shoved", this.numbWindowsShoved, aIniFile)
		
		retVal := this.arrayOR(errLvl) ; indicate if any error occurred
		return retVal
	}
	
	
	; Methods (helpers)
	; ---------------------------------------------
	writeINIFileValue(aKey, aValue, aIniFile="", aSection="")
	{ ; a wrapper to IniWrite
		; Input variables
		; 	aKey/aValue -> the key/value to write to the INI file
		; 	aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
		; 	aSectionName -> the section of the INI file to use (default is A_ComputerName)
		; Output variables
		; 	errorLevel (Boolean) -> did an error occur while writing to the INI file?
		if(aIniFile="")
		{
			aIniFile := this.iniFile
		}
		if(aSection="")
		{
			aSection := this.SectionName
		}
		IniWrite, %aValue%, %aIniFile%, %aSection%, %aKey%
		return ErrorLevel
	}
	
	readINIFileValue(aKey, aDefValue, aIniFile="", aSection="")
	{ ; a wrapper to IniRead (mostly a workaround for the fact that you can't use obj.var syntax in IniRead)
		; Input variables
		; 	aKey -> the key to read from the INI file
		; 	aDefValue -> if the aKey cannot be read, supply this as the returned value
		; 	aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
		; 	aSectionName -> the section of the INI file to use (default is A_ComputerName)
		; Output variables
		; 	Value -> the value associated with the aKey
		
		if(aIniFile="")
		{
			aIniFile := this.iniFile
		}
		if(aSection="")
		{
			aSection := this.SectionName
		}
		IniRead, OutVar, %aIniFile%, %aSection%, %aKey%, %aDefValue%
		return OutVar
	}
	
	getSupportFilename(fileExten, addDot=true)
	{ ; provides a filename of a support file (e.g., ini, ico), regardless of whether the script is compiled or not
		; Input variables
		; 	fileExten -> the file extension of the support file (place in quotes)
		; 	addDot -> should a '.' be added before fileExten? (Default=true)
		; Output variables
		; 	a filename of the script with the extension replaced with fileExten

		if(fileExten != "")
		{
			if(addDot)
			{
				replacementStr = .%fileExten%
			}
			else
			{
				replacementStr = %fileExten%
			}
		}
		else
		{
			replacementStr := ""
		}
		if(A_IsCompiled)
		{
			StringReplace, returnName, A_ScriptName,.exe,%replacementStr%, All
		}
		else
		{
			StringReplace, returnName, A_ScriptName,.ahk,%replacementStr%, All
		}
		return returnName
	}
	
	arrayOR(array)
	{ ; take an array of Booleans of arbitrary length and OR them together.
		; Input variables
		; 	array[] (Booleans) -> an array of Booleans (e.g., bit flags)
		; Output variables
		; 	result (Boolean) -> the OR of the array[]

		For index, arrayVal in array
		{
			if (arrayVal)
			{
				return true  ; at least 1 value is true
			}
		}
		return false ; if you got here all the values were false
	}
}

;EndRegion

/*
;==============================================================================
;========== Version History ===================================================
;==============================================================================
; v0.5 2018-03-07
; * basic information gathering and display in the Debug GUI
; 
; v1.0 2018-04-24
; * all major functions present
; * seems to work
;
; v1.1 2018-04-27
; * pulled out Hide Icon
; * added Hide Debug menu (moved Menu create to subroutines)
; * cleaned up code, moved anything called from the Library.ahk file to this file
; * I actually did try to pull up the original HLP files.  They appear to be essentially empty (no text of note, about the same as trayTipString).
; 
;==============================================================================
*/



bmondragon
Posts: 1
Joined: 07 Sep 2018, 01:52

Re: Shove-it: move windows back on to the screen

08 Sep 2018, 03:22

Ahhhh thank you so much for this!! Long-time user of Shove-It and was looking for exactly such a thing (AutoHotkey alternative). This works beautifully... and I'm so glad you made it and posted it here!
burque505
Posts: 1736
Joined: 22 Jan 2017, 19:37

Re: Shove-it: move windows back on to the screen

08 Sep 2018, 17:26

@ahbi, thanks for this script. I think that what's line 1303 in what I downloaded needs to be

Code: Select all

retValS := InStr(OutputVarSectionNames, secVar) ; is there the Section we need?
instead of

Code: Select all

retValS := InStr(OutputVarSectionNames, %secVar%) ; is there the Section we need?
If I don't change it, I get an error about an illegal variable name. When I changed it, though, it works great on my Win7-64 box. (BTW, moving a window off-screen to the top on my machine automatically maximizes it anyway, and that appears to override the "top" settings in Shove-it.ahk. I also added some generic code to make it run as Admin, but that's just me.)
Very nice script, very useful.
Thanks!
Regards,
burque505
carno
Posts: 265
Joined: 20 Jun 2014, 16:48

Re: Shove-it: move windows back on to the screen

12 Sep 2018, 06:11

Can you list your code (added some generic code to make it run as Admin)?
burque505 wrote:@ahbi, thanks for this script. I think that what's line 1303 in what I downloaded needs to be

Code: Select all

retValS := InStr(OutputVarSectionNames, secVar) ; is there the Section we need?
instead of

Code: Select all

retValS := InStr(OutputVarSectionNames, %secVar%) ; is there the Section we need?
If I don't change it, I get an error about an illegal variable name. When I changed it, though, it works great on my Win7-64 box. (BTW, moving a window off-screen to the top on my machine automatically maximizes it anyway, and that appears to override the "top" settings in Shove-it.ahk. I also added some generic code to make it run as Admin, but that's just me.)
Very nice script, very useful.
Thanks!
Regards,
burque505
burque505
Posts: 1736
Joined: 22 Jan 2017, 19:37

Re: Shove-it: move windows back on to the screen

12 Sep 2018, 07:53

Sure, this is not my code, it's filched from here..

Code: Select all

full_command_line := DllCall("GetCommandLine", "str")

if not (A_IsAdmin or RegExMatch(full_command_line, " /restart(?!\S)"))
{
    try ; leads to having the script re-launching itself as administrator
    {
        if A_IsCompiled
            Run *RunAs "%A_ScriptFullPath%" /restart
        else
            Run *RunAs "%A_AhkPath%" /restart "%A_ScriptFullPath%"
    }
    ExitApp
}
Regards,
burque505
ahbi
Posts: 17
Joined: 15 Jun 2016, 17:11

Re: Shove-it: move windows back on to the screen

06 Oct 2018, 23:55

Interesting. No issues for me. But to %% or not to %% is the bane of my AutoHotKey existence

What is your section name?

I have a new version of Shove-It semi-complete (messing with the log ability and ignoring windows that refuse to be shoved).
I'll add your code to it before I release it.

And thanks for the encouragement.


burque505 wrote:@ahbi, thanks for this script. I think that what's line 1303 in what I downloaded needs to be

Code: Select all

retValS := InStr(OutputVarSectionNames, secVar) ; is there the Section we need?
instead of

Code: Select all

retValS := InStr(OutputVarSectionNames, %secVar%) ; is there the Section we need?
If I don't change it, I get an error about an illegal variable name. When I changed it, though, it works great on my Win7-64 box. (BTW, moving a window off-screen to the top on my machine automatically maximizes it anyway, and that appears to override the "top" settings in Shove-it.ahk. I also added some generic code to make it run as Admin, but that's just me.)
Very nice script, very useful.
Thanks!
Regards,
burque505
ninjaz5736
Posts: 3
Joined: 03 May 2016, 13:34

Re: Shove-it: move windows back on to the screen

13 Sep 2020, 15:27

Just thought I'd add my $0.02, I've fixed the variable name errors (and there was a "Tab3" that needed changing to "Tab") and here is my working file (tested on Windows 10 Home 64-bit)

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
#SingleInstance force
; #Persistent
; #InstallKeybdHook ; needed for $ HotKey modifier (no trigger self) and others (e.g, Inactivity)
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.

/*
;==============================================================================
;========== What This Script Does  ============================================
;==============================================================================
;
;  Shove-It_AHK
;
; Takes windows that are off the screen and shoves them back on the screen (resizing if needed).
; In multi-monitor settings, shoves windows that bridges screens back onto 1 screen.
;
; The original Shove-It (v1.5a) was created by Phord Software in ~1997
;
; Their website (http www.phord.com) is now defunct, but their software -- written for the age of Win95 -- still runs well on Win10.  I have used it daily on my computers for the last 20 years.  (Good return on the registration fee there.)   Broken Link for safety
;
; In a spell of procrastination or boredom, I felt the urge to re-write Shove-It using AutoHotKey. Most of the original features are implemented in this version (no quick-drag or windoids).  Actually, with the Phrod website down and the Help files in a format Win10 doesn’t use (*.HLP) I really don’t know how the features I don’t use work.  I could fire up a Win7 VM or decompile the HLP file, but I just don’t care that much.  After all, I don’t use those features and my testing regime is “For a week, I used it the way I normally use Shove-it, and it seemed to work” (not a formal system).  
; I have no idea why I felt the need to re-write a perfectly functional program, but I did and it was fun.
;
; (c) 2018, Ahbi Santini
;
;==============================================================================

*/

; =========================================
; ===== Includes
; =========================================
;BeginRegion
; #Include %A_ScriptDir%\Library\Library.ahk 
; #Include %A_ScriptDir%\Library\HtmDlg.ahk 
;EndRegion

; =========================================
; ===== Configuration Variables
; =========================================
;BeginRegion

; none outside the INIfile
; See Main() below

;EndRegion

; ------------------------------------------------------------------------
; Set up SystemTray Menu
;BeginRegion
FileGetTime, ScriptLastModified, %A_ScriptFullPath%, M
FormatTime, ScriptLastModifiedX, %ScriptLastModified%
trayTipString  =	;  Only the first 127 characters of Text are displayed
			( LTrim
				---- %A_ScriptName% ----
				Shoves windows that are off the screen back on the screen.
				
				Last modified:
				%ScriptLastModifiedX%
			)
	Menu, Tray, Tip, %trayTipString% ; Create system tray mouse-over tooltip
; Set script icon (if there is one; icon name is same as the script's name w/ *.ico extension)
IconName := getSupportFilename("ico")
IfExist %IconName%
{
	Menu, Tray, Icon, %IconName%
}
GoSub Main_CreateDebugMenu

;EndRegion

;==============================================================================
;========== Main Script  ======================================================
;==============================================================================
;BeginRegion
; SetTitleMatchMode, 1
DetectHiddenText, On

; Set up the Settings variables.  Everything else assumes they are already set up.  So, better do that first.
global Settings := new SettingsClass()
global scriptStartTime := A_Now

; Shove things any time a window is created by hooking into SHELLHOOK
Gui +LastFound
hWnd := WinExist()
DllCall( "RegisterShellHookWindow", UInt,hWnd )
MsgNum := DllCall( "RegisterWindowMessage", Str,"SHELLHOOK" )
OnMessage( MsgNum, "ShellMessage" )

; When the script ends, perform this function
OnExit("ExitFunction")

; create some GUIs
GoSub DebugGuiCreate
; GoSub MenuHandler_ShowDebugGUI
; GoSub MenuHandler_ShowPropertiesGUI

; now do the actual work
Monitors := [{}]
Monitors := buildMonitorsArray()
Loop
{ ; check for windows that need a good shoving
	
	isLButtonDown := GetKeyState("LButton","P")
	if (!isLButtonDown) ; avoid shoving windows while you're moving the window
	{
		GoSub Main_TestAndShoveWindows ; the SHELLHOOK above is the reason for moving this to a subroutine.  Also useful if we ever use a menu item to shove things.
	}

	; CPU cycle saving Sleep the loop
	Sleep, Settings.shovePeriod_Secs * 1000
}

Return

;EndRegion





;==============================================================================
;========== HotKeys  ==========================================================
;==============================================================================
;BeginRegion
	; -------- Hotkey Definitions Reminder ----------
	; # - Win 
	; ! - Alt
	; ^ - Control
	; + - Shift
	; ~ - pass key command to OS
	; example -- $>!v:: 		; $ (no trigger self)  > (right key of a pair) ---- Alt (!) + V


;EndRegion

; =========================================
; ===== Functions
; =========================================
;BeginRegion

buildMonitorsArray()
{ ; create an array of MonitorClass objects that detail every monitor in the system
	Monitors := [{}]
	List_MonSection = ; Debug output string

	SysGet, MonitorCount, MonitorCount
	SysGet, MonitorPrimary, MonitorPrimary
	List_MonSection.= "Monitor Count:`t" MonitorCount "`nPrimary Monitor:`t" MonitorPrimary "`n"
	Loop, %MonitorCount%
	{
		Monitors[A_Index] := new MonitorClass(A_Index)
		
		List_MonSection.= "`n" Monitors[A_Index].toString()
	}
	return Monitors
}
	
buildWindowsArray(Monitors)
{ ; create an array of what windows are in existence right now
	List_WinSection =  ; Debug output string

	Windows := [{}]
	WinGet,WinList,List,,,Program Manager ; get a list of all the windows in the system
	loop,%WinList%
	{
		CurrentHWND := WinList%A_Index%
		WinGetTitle,WinTitle,ahk_id %CurrentHWND%
		If WinTitle AND !InStr(List,WinTitle)
		{
			; create the window object
			Windows[A_Index] := new WindowClass(CurrentHWND)

			; figure out what monitor owns the window (if it spans 2 monitors, figure out the most owning monitor)
			targetMonitor := whichMonitor(Windows[A_Index], Monitors)
			Windows[A_Index].monitor := targetMonitor

			; determine if the window should be shoved (skip windows that we shouldn't touch)
			if (Windows[A_Index].class != "Windows.UI.Core.CoreWindow") && (Windows[A_Index].isMinMax == 0)
			{
				shoveFlags := determineHowToShove(Windows[A_Index], Monitors[targetMonitor])
			}
			else
			{
				shoveFlags := {}
				shoveFlags.needsShove := false
			}
			Windows[A_Index].shoveFlags := shoveFlags


			
			; Debug GUI output
			; -------------------------------------
				if (Windows[A_Index].class != "Windows.UI.Core.CoreWindow")
				{
					List_WinSection.= "`n`n"
					List_WinSection.= Windows[A_Index].toString()
					List_WinSection.="`tMonitor: " Windows[A_Index].monitor 
					if(Windows[A_Index].monitor > 0) 
					{
						monTmp := Windows[A_Index].monitor
						List_WinSection.= "`n`tMLeft: " Monitors[monTmp].Left "`t`tMTop: " Monitors[monTmp].Top  "`tMRight: " Monitors[monTmp].Right  "`tMBottom: " Monitors[monTmp].Bottom
					}
					else
					{
						List_WinSection.= "`n`tMLeft: " "---" "`t`tMTop: "  "---"  "`tMRight: "  "---"  "`tMBottom: "  "---"
					}
					strTmp := "Do NOT Move"
					if (shoveFlags.needsShove)
					{
						; strTmp := shoveWindow(Windows[A_Index], Monitors[targetMonitor])

						List_WinSection.= "`n`tShove?: " shoveFlags.needsShove "`tReX: " shoveFlags.needsResizeX  "`tReY: " shoveFlags.needsResizeY
						List_WinSection.= "`n`tS->R: " shoveFlags.shoveFromLeft "`tS->L: " shoveFlags.shoveFromRight "`tS->D: " shoveFlags.shoveFromTop "`tS->U: " shoveFlags.shoveFromBottom
					}
					; List_WinSection.= "`n`tCmd: " strTmp
					List_WinSection.= "`n`n"
				}
		}
	}
	List_WinSection.="`n" 

	; update the Debug GUI
	List =
	GuiControl, Debug: ,WinList,%List%
	List := List_MonSection . List_WinSection
	GuiControl, Debug: ,WinList,%List%

	
	return Windows
}

whichMonitor(window, Monitors)
{ ; determine to which monitor a window belongs (by how much of the window is on a monitor; default Primary)
	strTmp := "WinT: " . window.title "`n"
	
	SysGet, MonitorPrimary, MonitorPrimary
	bestMon := MonitorPrimary
	bestPerct := 1 / 100000 ; some value greater than zero
	Loop, % Monitors.length()
	{
		perctInMon := percentageInMonitor(window, Monitors[A_Index])

		if(perctInMon > bestPerct)
		{
			bestPerct := perctInMon
			bestMon := A_Index
		}
	}

	return bestMon
}

percentageInMonitor(window, monitor)
{ ; compute how much of the window is within/displayed by the monitor
	percentage := 0
	
	winArea := window.Width * window.Height
	subWindow := {}
	
	; create a rectangle that is the portion of the window displayed by the monitor
	subWindow.Top := greaterOf( window.Top, monitor.TopWA )
	subWindow.Bottom := lesserOf( window.Bottom, monitor.BottomWA )
	subWindow.Left := greaterOf( window.Left, monitor.LeftWA )
	subWindow.Right := lesserOf( window.Right, monitor.RightWA )
	
	; clean up the sub-window rectangle
	subWindow.Width := subWindow.Right - subWindow.Left
	if ( subWindow.Width < 0)
	{
		subWindow.Width := 0
	}
		subWindow.Height := subWindow.Bottom - subWindow.Top
	if ( subWindow.Height < 0)
	{
		subWindow.Height := 0
	}
	subArea := subWindow.Width * subWindow.Height
	
	; how much of the window is within the monitor?
	percentage := subArea / winArea

		; Debug output
		; StrTmp.= "`nMon: " . A_Index . "`t%: " .  perctInMon
		; strTmp.= "`n`twT: " window.Top "`twB: " window.Bottom  "`twL: " window.Left  "`twR: " window.Right
		; strTmp.= "`n`tmT: " monitor.TopWA "`tmB: " monitor.BottomWA  "`tmL: " monitor.LeftWA  "`tmR: " monitor.RightWA
		; strTmp.= "`n`tsT: " subWindow.Top "`tsB: " subWindow.Bottom  "`tsL: " subWindow.Left  "`tsR: " subWindow.Right
		; strTmp.= "`n`tsHeight: " subWindow.Height "`tsWidth: " subWindow.Width
		; strTmp.= "`n`twinArea: " winArea "`tsubArea: " subArea "`t%: " .  perctInMon
		; infoMsgBox(StrTmp)

	return percentage
}

determineHowToShove(window, targetMonitor)
{ ; create a shoveFlags object that has a bunch of bit flags that indicate which way/if a window is to be shoved/resized
	shoveFlags := {}
	
	; strTmp:= Window.Title
	; strTmp.= "`nLeft: " Window.Left "`tTop: " Window.Top  "`tRight: " Window.Right  "`tBottom: " Window.Bottom
	; strTmp.= "`nMLeft: " targetMonitor.LeftWA "`tMTop: " targetMonitor.TopWA  "`tMRight: " targetMonitor.RightWA  "`tMBottom: " targetMonitor.BottomWA
	; infoMsgBox(strTMp, 3)

	; off the Left side
	if (window.Left < targetMonitor.LeftWA)
	{
		shoveFlags.shoveFromLeft := true
	}
	else
	{
		shoveFlags.shoveFromLeft := false
	}
	; off the Right side
	if (window.Right > targetMonitor.RightWA)
	{
		shoveFlags.shoveFromRight := true
	}
	else
	{
		shoveFlags.shoveFromRight := false
	}
	; off the Top
	if (window.Top < targetMonitor.TopWA)
	{
		shoveFlags.shoveFromTop := true
	}
	else
	{
		shoveFlags.shoveFromTop := false
	}
	; off the Bottom
	if (window.Bottom > targetMonitor.BottomWA)
	{
		shoveFlags.shoveFromBottom := true
	}
	else
	{
		shoveFlags.shoveFromBottom := false
	}
	
	; resize flags
	if (window.Width > targetMonitor.WidthWA)
	{
		shoveFlags.needsResizeX := true
	}
	else
	{
		shoveFlags.needsResizeX := false
	}
	if (window.Height > targetMonitor.HeightWA)
	{
		shoveFlags.needsResizeY := true
	}
	else
	{
		shoveFlags.needsResizeY := false
	}
	; summary flags
	shoveFlags.needsShove := arrayOR([shoveFlags.shoveFromLeft, shoveFlags.shoveFromRight, shoveFlags.shoveFromTop, shoveFlags.shoveFromBottom])
	
	; really not bit flags, which annoys me but the best place for them
	shoveFlags.targetMonitorNumber := targetMonitor.number
	shoveFlags.percentageInMonitor := percentageInMonitor(window, targetMonitor)

	; only allow shoving if the settings allow it
	shoveFlags := gateFlagsWithSettings(shoveFlags)
	
	return shoveFlags
}

gateFlagsWithSettings(shoveFlags)
{ ; now take the raw shoveFlags object and AND it with the Settings variables
	shoveFlags.shoveFromLeft   := (shoveFlags.shoveFromLeft   AND Settings.canShoveFromLeft)
	shoveFlags.shoveFromRight  := (shoveFlags.shoveFromRight  AND Settings.canShoveFromRight)
	shoveFlags.shoveFromTop    := (shoveFlags.shoveFromTop    AND Settings.canShoveFromTop)
	shoveFlags.shoveFromBottom := (shoveFlags.shoveFromBottom AND Settings.canShoveFromBottom)
	shoveFlags.needsResizeX    := (shoveFlags.needsResizeX    AND Settings.canResize)
	shoveFlags.needsResizeY    := (shoveFlags.needsResizeY    AND Settings.canResize)

	; summary flags
	shoveFlags.needsShove := arrayOR([shoveFlags.shoveFromLeft, shoveFlags.shoveFromRight, shoveFlags.shoveFromTop, shoveFlags.shoveFromBottom])
	
	if(shoveFlags.needsShove)
	{
		; tmpStr := "Percent IN: " shoveFlags.percentageInMonitor "`n" "Percent 2B: " Settings.percentOffBeforeShove
		if ( (1 - shoveFlags.percentageInMonitor) >= (Settings.percentOffBeforeShove / 100) )
		{
			; tmpStr .= "`n" "SHOVE!!!"
		}
		else 
		{
			shoveFlags.needsShove := false
			; tmpStr .= "`n" "Ignore"
		}
		; infoMsgBox(tmpStr, 2)
	}
	
	return shoveFlags
}

shoveWindow(window, targetMonitor)
{ ; actually shove the window
	strTmp := -1 ; set to an error value

	shoveFlags := window.shoveFlags ; copy to a more manageable name

	if (shoveFlags.needsShove) ; skip if it doesn't need shoving
	{
		; initialize to the same dimensions as the original window
		WinID := window.WinID
		newLeft := window.Left
		newTop := window.Top
		newWidth := window.Width
		newHeight := window.Height

		; resize
		if (shoveFlags.needsResizeX)
		{
			newWidth:= targetMonitor.WidthWA
		}
		if (shoveFlags.needsResizeY)
		{
			newHeight:= targetMonitor.HeightWA
		}
		
		; determine how to move (set left corner)
		if (shoveFlags.shoveFromLeft)
		{
			newLeft:= targetMonitor.LeftWA
		}
		else if (shoveFlags.shoveFromRight)
		{
			newLeft:= targetMonitor.RightWA - newWidth
		}
		
		; determine how to move (set top corner)
		if (shoveFlags.shoveFromTop)
		{
			newTop:= targetMonitor.TopWA
		}
		else if (shoveFlags.shoveFromBottom)
		{
			newTop:= targetMonitor.BottomWA - newHeight
		}

		; move
		strTmp := "WinMove, ahk_id " . WinID . ", , " . newLeft . ", " . newTop . ", " . newWidth ", " . newHeight
		WinMove, ahk_id %WinID%, , %newLeft%, %newTop%, %newWidth%, %newHeight%
		Settings.shovedAnotherWindow()
	}
	return strTmp
}

	; helper functions
	; ------------------------
greaterOf(A,B)
{
	if( A >= B )
	{
		return A
	}
	else
	{
		return B
	}
}

lesserOf(A,B)
{
	if( A <= B )
	{
		return A
	}
	else
	{
		return B
	}
}

AddCommasToNumber(numb, useDecimals=false)
{ ; re-format a number with commas (or as dictated by the locale)
	; from Skan & PhiLho
	; http://autohotkey.com/board/topic/41644-formatting-numbers-with-commas/?p=259900
	; https://autohotkey.com/board/topic/11642-addcommas-function-solved/#entry74565
	
	LOCALE_USER_DEFAULT = 0x400
	VarSetCapacity(newNumb, 32)
	DllCall("GetNumberFormat"
			, "UInt", LOCALE_USER_DEFAULT ; LCID Locale
			, "UInt", 0 ; DWORD dwFlags
			, "Str", numb ; LPCTSTR lpValue
			, "UInt", 0 ; CONST NUMBERFMT* lpFormat
			, "Str", newNumb ; LPTSTR lpNumberStr
			, "Int", 32) ; int cchNumber

	if(!useDecimals)
	{
		newNumb := SubStr(newNumb,1,StrLen(newNumb) - 3)
	}
	return newNumb
}

arrayOR(array)
{ ; take an array of Booleans of arbitrary length and OR them together.
	; Input variables
	; 	array[] (Booleans) -> an array of Booleans (e.g., bit flags)
	; Output variables
	; 	result (Boolean) -> the OR of the array[]
	For index, arrayVal in array
	{
		if (arrayVal)
		{
			return true  ; at least 1 value is true
		}
	}
	return false ; if you got here all the values were false
}

reverseArrayOrder(inArray)
{ ; take an array and re-index it such that the order is reversed (an array of [1]-[10], [1] goes to [10] and [10] goes to [1])
	outArray := {}
	loop, % inArray.length()
	{
		inArrayIndex := inArray.length() - A_Index + 1
		outArray[A_Index] := inArray[inArrayIndex]
	}
	return outArray
}

getSupportFilename(fileExten, addDot=true)
{ ; provides a filename of a support file (e.g., ini, ico), regardless of whether the script is compiled or not
	; Input variables
	; 	fileExten -> the file extension of the support file (place in quotes)
	; 	addDot -> should a '.' be added before fileExten? (Default=true)
	; Output variables
	; 	a filename of the script with the extension replaced with fileExten

	if(fileExten != "")
	{
		if(addDot)
		{
			replacementStr = .%fileExten%
		}
		else
		{
			replacementStr = %fileExten%
		}
	}
	else
	{
		replacementStr := ""
	}
	if(A_IsCompiled)
	{
		StringReplace, returnName, A_ScriptName,.exe,%replacementStr%, All
	}
	else
	{
		StringReplace, returnName, A_ScriptName,.ahk,%replacementStr%, All
	}
	return returnName
}

ConvertSecondsToDDHHMMSS(TimeInSeconds, ShowLeadingZeros = true, LastLeadingZero = "m")
{ ; convert seconds to Days, Hours, Minutes & Seconds (ddhhmmss)
	; Input variables
	; 	TimeInSeconds -> time in seconds
	;   ShowLeadingZeros -> should leading zeros be shown in te output (default = true)
	;   LastLeadingZero -> provides a middle ground for leaidng zeros; when ShowLeadingZeros==true, where do the leading zeros start?
	;                      For example, a value of "m" would result in an output of "00:ss".  A value of "h" would result in an output of "00:mm:ss"
	;                      Valid values are "d", "h", and "m".  Anything else defaults to "d".
	; Output variables
	; 	ddhhmmss -> time in minutes & seconds (mmss)

	; from  http://www.autohotkey.com/docs/commands/FormatTime.htm
	time = 19990101  ; *Midnight* of an arbitrary date.
    time += %TimeInSeconds%, seconds
	if(TimeInSeconds >= 86400) ; > 1 day
	{
		FormatTime, hhmmss, %time%, HH:mm:ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
		NumberOfDays := TimeInSeconds // 86400
		NumberOfDays := Floor(NumberOfDays) ; // doesn't convert to Int as much as I'd like it to
		ddhhmmss := NumberOfDays . ":" . hhmmss
		if(NumberOfDays < 10)
		{
			ddhhmmss := "0" . NumberOfDays . ":" . hhmmss
		}
	}
	else if(TimeInSeconds < 60) ; less than 1 minute
	{
		if(ShowLeadingZeros)
		{
			if(LastLeadingZero = "m")
			{
				FormatTime, ddhhmmss, %time%, '00':ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
			}
			else if(LastLeadingZero = "h")
			{
				FormatTime, ddhhmmss, %time%, '00:00':ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
			}
			else 
			{
				FormatTime, ddhhmmss, %time%, '00:00:00':ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
			}
		}
		else
		{
			ddhhmmss := TimeInSeconds ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
		}
	}
	else if(TimeInSeconds < 3600) ; less than 1 hour
	{
		if(ShowLeadingZeros)
		{
			if(LastLeadingZero = "m")
			{
				FormatTime, ddhhmmss, %time%, mm:ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
			}
			else if(LastLeadingZero = "h")
			{
				FormatTime, ddhhmmss, %time%, '00':mm:ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
			}
			else 
			{
				FormatTime, ddhhmmss, %time%, '00:00':mm:ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
			}
		}
		else
		{
			FormatTime, ddhhmmss, %time%, mm:ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
		}
	}
	else
	{
		if(ShowLeadingZeros)
		{
			if(LastLeadingZero = "m")
			{
				FormatTime, ddhhmmss, %time%, HH:mm:ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
			}
			else if(LastLeadingZero = "h")
			{
				FormatTime, ddhhmmss, %time%, HH:mm:ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
			}
			else 
			{
				FormatTime, ddhhmmss, %time%, '00':HH:mm:ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
			}
		}
		else
		{
			FormatTime, ddhhmmss, %time%, HH:mm:ss ; throw away any "days" info (treat TimeInMinutes as if it is < 1440)
		}
	}

    return ddhhmmss  ; This method is used to support more than 24 hours worth of sections.
}



	; functions triggered by pre-registered events
	; ----------------------------------------------------------
ShellMessage( wParam,lParam )
{ ; called when Windows creates an event (e.g., a window is created)
	; not useful in this script but standard actions for ShellMessage
	WinGetTitle, Title, ahk_id %lParam%
	WinGetClass, Class, ahk_id %lParam%  ; is a String with # first (e.g., "#123456") or an actual name
		
	; http msdn.microsoft.com /en-us/library/windows/desktop/ms644991%28v=vs.85%29.aspx  Broken Link for safety
	If ( wParam = 1 ) ;  HSHELL_WINDOWCREATED := 1
	{
		; shove the windows
		GoSub Main_TestAndShoveWindows
	}
	return
}

ExitFunction()
{ ; called when the script ends/closes
	; this isn't the best way to do this.  I should do something where the value is calculated in real-time.  Probably in the get/set of the object.
	; but it is fluff, and this works well enough, and I am moving on
	scriptEndTime := A_Now
	scriptEndTimeSave := scriptEndTime
	EnvSub, scriptEndTime, %scriptStartTime%, seconds
	runTimeSave := Settings.runningTime
	Settings.runningTime := Settings.runningTime + scriptEndTime
		; tmpStr := "Start: " scriptStartTime "`n" "End  : " scriptEndTimeSave "`n" "RunTm: " runTimeSave "`n" "NewTm: " Settings.runningTime
		; infoMsgBox(tmpStr)
	Settings.writeINIFileOnClose()
	return
}

;EndRegion

; =========================================
; ===== Subroutines / Labels
; =========================================
;BeginRegion
Main_TestAndShoveWindows:
{ ; 1/2 of the main program, test all the windows and shove them if needed

	; get a list of all windows currently in the system
	Windows := [{}]
	Windows := buildWindowsArray(Monitors)
	
	; now test the windows' position and shove them as needed
	Loop, % Windows.length()
	{
		targetMonitor := Windows[A_Index].monitor
		shoveWindow(Windows[A_Index], Monitors[targetMonitor]) ; both tests and shoves the window
	}
}
Return

Main_CreateMiniMenu:
{ ; delete the Tray Menu and re-create it with the minimal options
	Menu, Tray, DeleteAll
	
	Menu, tray, NoStandard ; Remove all standard menu items
	Menu, Tray, add, Properties, MenuHandler_ShowPropertiesGUI
	Menu, Tray, Default, Properties
	Menu, Tray, add  ; Creates a separator line.
	Menu, Tray, add, Exit Shove-It, MenuHandler_ExitApp
}
Return

Main_CreateDebugMenu:
{ ; delete the Tray Menu and re-create it with the debug options
	Menu, Tray, DeleteAll
	
	Menu, tray, NoStandard ; Remove all standard menu items
	Menu, Tray, add, Properties, MenuHandler_ShowPropertiesGUI
	Menu, Tray, Default, Properties
	Menu, Tray, add  ; Creates a separator line.
	Menu, Tray, add, Write _DEFAULT_ INI values, MenuHandler_WriteDefaultVariables
	Menu, Tray, add, Re-load INI values, MenuHandler_ReLoadINIVariables
	Menu, Tray, add  ; Creates a separator line.
		Menu, SubMenu_StandardMenuItems, add
		Menu, SubMenu_StandardMenuItems, DeleteAll
	Menu, tray, add, Standard Menu, :SubMenu_StandardMenuItems
	Menu, SubMenu_StandardMenuItems, Standard
	Menu, Tray, add, Show Debug GUI, MenuHandler_ShowDebugGUI
	Menu, Tray, add  ; Creates a separator line.
	Menu, Tray, add, Exit Shove-It, MenuHandler_ExitApp
}
Return

MenuHandler_ShowPropertiesGUI:
{ ; create and show the Properties GUI
	GoSub PropertiesGuiCreate
	GoSub PropertiesGuiUpdateSettings
	Gui, Properties: Show, w420 h290, %A_Scriptname%
}
Return

MenuHandler_ShowDebugGUI:
{ ; show the DEBUG GUI (which is always updating with debug info about the windows, unless I changed that in a future version)
	tmpHgt := A_ScreenHeight - 75
	Gui, Debug: Show, w600 h%tmpHgt% X0 Y0
}
Return

MenuHandler_ExitApp:
	ExitApp
Return

MenuHandler_ReLoadINIVariables:
{ ; re-reads the INI file
	Settings.readINIFile()
}
Return

MenuHandler_WriteDefaultVariables:
{ ; Writes out default INI values
	IniName := getSupportFilename("ini")
	Settings.writeDefaultINIFile(IniName)
	Settings.readINIFile()
}
Return


;EndRegion

; =========================================
; ===== GUI Subroutines / Labels
; =========================================
;BeginRegion
	; Properties GUI
	; --------------------------
	;BeginRegion

PropertiesGuiCreate:
{
	Gui, Properties: New, -MinimizeBox -MaximizeBox +Theme -DPIScale, %A_Scriptname%
	Gui, Properties: Add, Button, x230 y260 w80 h20 gPropertiesButtonOK, &OK
	Gui, Properties: Add, Button, x327 y260 w80 h20 gPropertiesGuiEscape, &Cancel
	Gui, Properties: Add, Tab, x10 y10 w400 h240 vTabID, General|Advanced|Statistics|About
	Gui, Properties: Tab, 1 ; General options
	Gui, Properties: Add, GroupBox, x50 y50 w250 h100, Shove-it from the: 
	Gui, Properties: Add, CheckBox, x150 y70 h20 vCheckboxTopID, Top
	Gui, Properties: Add, CheckBox, x150 y125 h20 vCheckboxBottomID, Bottom
	Gui, Properties: Add, CheckBox, x60 y95 h20 vCheckboxLeftID, Left
	Gui, Properties: Add, CheckBox, x250 y95 h20 vCheckboxRightID, Right
	Gui, Properties: Add, CheckBox, x50 y160 h23 vCheckboxResizeID, Resize windows when necessary
	Gui, Properties: Add, CheckBox, x50 y185 h23 vCheckboxHideDebugID, Hide Debug menu options
	; I added the hideIcon feature and then realized it was kind of pointless and annoying.
	; Once hidden, you can't get to the Properties GUI to undo it.  So, you have to resort to manually editing the INI file.
	; Gui, Properties: Add, CheckBox, x50 y210 h23 vCheckboxHideIconID, Hide Shove-it Icon
	; GuiControl, Properties: Disable, Hide Shove-it Icon
	Gui, Properties: Add, CheckBox, x50 y210 h23 vCheckboxWindoidID, Shove windoids off of active window buttons
	GuiControl, Properties: Disable, Shove windoids off of active window buttons ; I will probably never enable this option.  I have never used this but had an odd affection for it.  Hence, its survival here. 

	Gui, Properties: Tab, 2 ; Advanced options
	Gui, Properties: Add, Text, x50 y75 h20 +0x200, Auto-shove every: 
	shovePeriod_Str := Settings.shovePeriod_Str
	Gui, Properties: Add, DropDownList, x150 y75 w120 vDropDownListID Choose1 AltSubmit, %shovePeriod_Str%
	Gui, Properties: Add, Text, x300 y75 h20 +0x200, seconds.
	Gui, Properties: Add, GroupBox, x50 y125 w330 h75
	Gui, Properties: Add, Slider, x55 y135 w320 TickInterval10 ToolTip Range0-100 AltSubmit vSliderVariable gPropertiesSliderEvent, SliderVariable
	Gui, Properties: Add, Text, x55 y175 h20 +0x200 w320 Center vSliderTextID, Only shove windows which are at least 888 off the screen.
	GoSub PropertiesSliderEvent

	Gui, Properties: Tab, 3 ; Statistics
	Gui, Properties: Add, Text, x50 y70 h20 +0x200 vStatisticsInstalledTextID, Installed on: Wednesday, December, 25, 2019.
	Gui, Properties: Add, Text, x50 y100 h20 +0x200 vStatisticsTimeRunTextID, Running time: 1,000,000,000 days, 88 hours, and 10 minutes.
	Gui, Properties: Add, Text, x50 y130 h20 +0x200 vStatisticsWinsShovedTextID, Shoved 888,888,888,888 windows so far.

	Gui, Properties: Tab, 4 ; About
	aboutStr  =	
				( LTrim
					The original Shove-It (v1.5a) was created by Phord Software in ~1997
					
					`tTheir website (http www.phord.com) is now defunct, but their software -- written for the age of Win95 -- still runs well on Win10.  I have used it daily on my computers for the last 20 years.  (Good return on the registration fee there.)   Broken Link for safety
					
					`tIn a spell of procrastination or boredom, I felt the urge to re-write Shove-It using AutoHotKey. Most of the original features are implemented in this version (no quick-drag or windoids).  Actually, with the Phrod website down and the Help files in a format Win10 doesn’t use (*.HLP) I really don’t know how the features I don’t use work.  I could fire up a Win7 VM or decompile the HLP file, but I just don’t care that much.  After all, I don’t use those features and my testing regime is “For a week, I used it the way I normally use Shove-it, and it seemed to work” (not a formal system).  
					`tI have no idea why I felt the need to re-write a perfectly functional program, but I did and it was fun.

					(c) 2018, Ahbi Santini
				)
	Gui, Properties: Add, Edit, w380 h200 vAboutTextID  +Wrap +vScroll ReadOnly, %aboutStr%
	; Gui, Properties: Add, ActiveX, x10 y10 w400 h270 vWB, Shell.Explorer  ; The final parameter is the name of the ActiveX component.
	; aboutMsgBox(trayTipString)
	; AboutHtmlFilename := getSupportFilename("---AboutHtmDlg.htm", false)
	; AboutHtmlFilename := getTempFilename(AboutHtmlFilename)
	; WB.Navigate(AboutHtmlFilename)  ; This is specific to the web browser control.
}
Return

PropertiesGuiUpdateSettings:
{
	GuiControl, Properties: Choose, TabID, 1 ; really here for testing, so I can bring up the tab I am working on

	; Tab 1
	CheckboxTopID := Settings.canShoveFromTop
	GuiControl, Properties: , CheckboxTopID, %CheckboxTopID%
	CheckboxBottomID := Settings.canShoveFromBottom
	GuiControl, Properties: , CheckboxBottomID, %CheckboxBottomID%
	CheckboxLeftID := Settings.canShoveFromLeft
	GuiControl, Properties: , CheckboxLeftID, %CheckboxLeftID%
	CheckboxRightID := Settings.canShoveFromRight
	GuiControl, Properties: , CheckboxRightID, %CheckboxRightID%

	; CheckboxHideIconID := Settings.hideTrayIcon
	; GuiControl, Properties: , CheckboxHideIconID, %CheckboxHideIconID%
	CheckboxHideDebugID := Settings.hideDebugMenu
	GuiControl, Properties: , CheckboxHideDebugID, %CheckboxHideDebugID%
	CheckboxResizeID := Settings.canResize
	GuiControl, Properties: , CheckboxResizeID, %CheckboxResizeID%
	CheckboxWindoidID := Settings.canShoveWinoids
	GuiControl, Properties: , CheckboxWindoidID, %CheckboxWindoidID%
	
	; Tab 2
	DropDownListID := Settings.shovePeriod_Numb
	GuiControl, Properties: Choose, DropDownListID,  %DropDownListID%
	SliderVariable := Settings.percentOffBeforeShove
	GuiControl, Properties: , SliderVariable, %SliderVariable%
	GoSub PropertiesSliderEvent

	; Tab 3
	tmpVar := Settings.installedOn
	FormatTime, OutputVar , %tmpVar%, LongDate
	StatisticsInstalledTextID := "Installed on: " OutputVar
	GuiControl,  Properties: , StatisticsInstalledTextID, %StatisticsInstalledTextID%
	tmpStr := ConvertSecondsToDDHHMMSS(Settings.runningTime, false)
	tmpStrArr := StrSplit(tmpStr, ":")
	tmpStrArr := reverseArrayOrder(tmpStrArr)
	StatisticsTimeRunTextID := "Running time: " 
	if (tmpStrArr.length() = 4 ) ; has days
	{
		StatisticsTimeRunTextID .= AddCommasToNumber(tmpStrArr[4]) " days, "
	}
	if (tmpStrArr.length() >= 3 ) ; has hours
	{
		StatisticsTimeRunTextID .= tmpStrArr[3] " hours, and "
	}
	if (tmpStrArr.length() >= 2 ) ; has minutes
	{
		StatisticsTimeRunTextID .= tmpStrArr[2] " minutes."
	}
	GuiControl,  Properties: , StatisticsTimeRunTextID, %StatisticsTimeRunTextID%
	StatisticsWinsShovedTextID := "Shoved " AddCommasToNumber(Settings.numbWindowsShoved) " windows so far."
	GuiControl,  Properties: , StatisticsWinsShovedTextID, %StatisticsWinsShovedTextID%
}
Return

PropertiesSliderEvent:
{
	if (SliderVariable < 1)
	{
		GuiControl,  Properties: , SliderTextID, Shove windows which are off the screen, even a bit.
	}
	else if (SliderVariable > 99)
	{
		GuiControl,  Properties: , SliderTextID, Only shove windows which are totally off the screen.
	}
	else
	{
		GuiControl,  Properties: , SliderTextID, Only shove windows which are at least %SliderVariable%`% off the screen.
	}
}
Return

PropertiesGuiEscape:
PropertiesGuiClose:
	Gui, Properties: Cancel
Return

PropertiesButtonOK:
{
	Gui, Properties: Submit

	Settings.canShoveFromLeft      := CheckboxLeftID
	Settings.canShoveFromRight     := CheckboxRightID
	Settings.canShoveFromTop       := CheckboxTopID
	Settings.canShoveFromBottom    := CheckboxBottomID
	Settings.canResize             := CheckboxResizeID
	; Settings.hideTrayIcon          := CheckboxHideIconID
	Settings.hideDebugMenu         := CheckboxHideDebugID
	Settings.canShoveWinoids       := CheckboxWindoidID
	Settings.shovePeriod_Numb      := DropDownListID
	Settings.percentOffBeforeShove := SliderVariable
	Settings.writeINIFile()

	GoSub PropertiesGuiClose
}
Return
	; --------------------------
	;EndRegion

	; DEBUG GUI
	; --------------------------
	;BeginRegion

DebugGuiCreate:
{
	Gui, Debug: New, , DEBUG: %A_Scriptname%
	Gui, Debug:Add,ListBox,vWinList w580 r30
	GuiControl, Debug:+HScroll,WinList
	Gui Debug: +Delimiter`n
	Gui Debug: +Resize
	global List =
	global List_MonSection = 
}
Return

DebugGuiClose:
	Gui, Debug: Cancel
Return

DebugGuiSize:
{ ; resize the GUI controls
	if ErrorLevel = 1  ; The window has been minimized.  No action needed.
		return
	; Otherwise, the window has been resized or maximized. Resize the Edit control to match.
	NewWidth := A_GuiWidth - 20
	NewHeight := A_GuiHeight - 20
	GuiControl,  Debug: Move, WinList, W%NewWidth% H%NewHeight%
}
Return
	; --------------------------
	;EndRegion


;EndRegion


; =========================================
; ===== Classes
; =========================================
;BeginRegion

class MonitorClass
{ ; Class that has all/most of the information about a given monitor 
	__New(aNumber=1)
	{
		; Input variables
		; 	aNumber -> the monitor's ID number (e.g., 1 or 2)

		SysGet, MonitorPrimary, MonitorPrimary
		this._primaryMonitor := MonitorPrimary
		
		SysGet, MonitorName, MonitorName, %aNumber%
		SysGet, Monitor, Monitor, %aNumber%
		SysGet, MonitorWorkArea, MonitorWorkArea, %aNumber%

		this.number := aNumber
		this.name := MonitorName
		this.left := MonitorLeft
		this.right := MonitorRight
		this.top := MonitorTop
		this.bottom := MonitorBottom
		this.leftWA := MonitorWorkAreaLeft
		this.rightWA := MonitorWorkAreaRight
		this.topWA := MonitorWorkAreaTop
		this.bottomWA := MonitorWorkAreaBottom
		this.Width := MonitorRight - MonitorLeft
		this.Height := MonitorBottom - MonitorTop
		this.WidthWA := MonitorWorkAreaRight - MonitorWorkAreaLeft
		this.HeightWA := MonitorWorkAreaBottom - MonitorWorkAreaTop
		if ( aNumber == MonitorPrimary )
		{
			this.isPrimary := true
		}
		else
		{
			this.isPrimary := false
		}
		return this
	}
	
	toString()
	{ ; output debug information as a formatted string
		outStr := "Monitor:`t#" this.number "`n"
		outStr .= "`tName:`t" this.name "`t" "isPrimary:`t" this.isPrimary "`n"
		; Δ - file needs to be saved/encoded as Unicode/UTF-8-BOM to see the Delta symbol
		tmpVal := this.left - this.leftWA
		outStr .= "`tLeft:`t" this.left " (" this.leftWA " work; Δ " tmpVal ")`n"
		tmpVal := this.right - this.rightWA
		outStr .= "`tRight:`t" this.right " (" this.rightWA " work; Δ " tmpVal ")`n`"
		tmpVal := this.top - this.topWA
		outStr .= "`tTop:`t" this.top " (" this.topWA " work; Δ " tmpVal ")`n"
		tmpVal := this.bottom - this.bottomWA
		outStr .= "`tBottom:`t" this.bottom " (" this.bottomWA " work; Δ " tmpVal ")`n"
		tmpVal := this.width - this.widthWA
		outStr .= "`tWidth:`t" this.Width " (" this.WidthWA " work; Δ " tmpVal ")`n"
		tmpVal := this.height - this.heightWA
		outStr .= "`tHeight:`t" this.Height " (" this.HeightWA " work; Δ " tmpVal ") `n"
		return outStr
	}
}

class WindowClass
{ ; Class that has all/most of the information about a given window.  Does not include GUI control (i.e., button-level) information 
	__New(WinHandle)
	{
		; Input variables
		; 	WinHandle -> the window's handle/ID number (e.g., 1 or 2)

		WinGetTitle,WinTitle,ahk_id %WinHandle%
		this.Title := WinTitle
		
		WinGetClass,WinClass,ahk_id %WinHandle%
		this.Class := WinClass
		
		WinGetPos, _X, _Y, _Width, _Height, ahk_id %WinHandle%
		this.X := _X
		this.Y := _Y
		this.Width := _Width
		this.Height := _Height
		this.Left := _X
		this.Right := _X + _Width
		this.Top := _Y
		this.Bottom := _Y + _Height

		WinGet,_WinID,ID,ahk_id %WinHandle% ; this really should already be WinHandle but in a fit of safety
		this.WinID := _WinID

		WinGet,_PID,PID,ahk_id %WinHandle%
		this.PID := _PID
		
		WinGet,_ProcessName,ProcessName,ahk_id %WinHandle%
		this.ProcessName := _ProcessName
		
		WinGet,_ProcessPath,ProcessPath,ahk_id %WinHandle%
		this.ProcessPath := _ProcessPath

		WinGet,WinMinMax, MinMax, ahk_id %WinHandle%
		this.isMinMax := WinMinMax

		WinGet,WinStyle, Style, ahk_id %WinHandle%
		this.Style := WinStyle
		
		WinGet,WinExStyle, ExStyle, ahk_id %WinHandle%
		this.ExStyle := WinExStyle
		if (WinStyle & 0x8000000)  ; 0x8000000 is WS_DISABLED.
		{
			this.isDisabled := true
		}
		else
		{
			this.isDisabled := false
		}
		if (WinStyle & 0x10000000)  ; 0x10000000 is WS_VISIBLE
		{
			this.isVisible := true
		}
		else
		{
			this.isVisible := false
		}
		if (WinExStyle & 0x8)  ; 0x8 is WS_EX_TOPMOST.
		{
			this.isAlwaysOnTop := true
		}
		else
		{
			this.isAlwaysOnTop := false
		}

		return this
	}
	
	toString()
	{ ; output debug information as a formatted string
		outStr := ""
		outStr .= this.Title "`n"
		outStr .= "`tClass: " this.Class "`tPID: " this.PID "`tWinID (HWND): " this.WinID "`n"
		outStr .= "`tProcessName: " this.ProcessName "`n"
		outStr .= "`tProcessPath: " this.ProcessPath "`n"
		outStr .= "`tMin/Max: " this.isMinMax "`tDisabled: " this.isDisabled "`tVisible: " this.isVisible "`n"
		outStr .= "`tOnTop: " this.isAlwaysOnTop "`t" "`tStyle: " this.Style  "`t" "`tExStyle: " this.ExStyle "`n"
		outStr .= "`tX: " this.X "`t`tY: " this.Y  "`t" "`tWidth: " this.Width  "`tHeight: " this.Height "`n"
		outStr .= "`tLeft: " this.Left "`tTop: " this.Top  "`t" "`tRight: " this.Right  "`tBottom: " this.Bottom "`n"
		return outStr
	}
}

class SettingsClass
{ ; Class that handles the settings variables, read/writes them to an INI file
	__New(aIniFile="", aSectionName="")
	{
		; Input variables
		; 	aIniFile -> the INI file to read/write (default is A_Scriptname.INI)
		; 	aSectionName -> the section of the INI file to use (default is A_ComputerName)
		if(aIniFile="")
		{
			aIniFile := this.getSupportFilename("ini")
		}
		this.iniFile := aIniFile

		if(aSectionName="")
		{
			this.SectionName := A_ComputerName
		}
		else
		{
			this.SectionName := aSectionName
		}

		this.shovePeriod_Str := "0.5|1|2|3|5" ; not in INI file
		this.readINIFile()
		return this
	}
	
	; Properties
	; ---------------------------------------------
	shovePeriod_Secs
	{ ; make sure shovePeriod_Secs & shovePeriod_Str are in sync (i.e., they include each other's values)
		get
		{
			return this._shovePeriod_Secs
		}
		set
		{
			if value is number
			{
				tmpStr := this.shovePeriod_Str ; this is always defined via _New()
				if( !InStr(tmpStr, value) ) ; if a new value, add it to shovePeriod_Str, then sort shovePeriod_Str by period of time
				{
					tmpStr.= "|" value
					Sort, tmpStr, N D|
					this.shovePeriod_Str := tmpStr
				}
			}
			else
			{
				value := 0.5 ; default to first value in shovePeriod_Str
			}
			return this._shovePeriod_Secs := value
		}
	}
	
	shovePeriod_Numb
	{ ; a pseudo-property.  Really a function that pretends to be a property.  The Index of shovePeriod_Secs in shovePeriod_Str
		get
		{
			if (this.shovePeriod_Secs = "") 
			{
				this.shovePeriod_Secs := 0.5  ; default to first value in shovePeriod_Str
			}
			tmpSecs := this.shovePeriod_Secs
			tmpStr := this.shovePeriod_Str ; this is always defined via _New()
			Loop, Parse, tmpStr, |
			{
				infoStr := "tmpStr: " tmpStr "`n" "Field: " A_LoopField "`n" "tmpSecs: " tmpSecs  "`n" "Index: " A_Index 
				; infoMsgBox(infoStr, 3)
				if (A_LoopField = tmpSecs)
				{
					return A_Index
				}
			}
			return 1 ; default to first value in shovePeriod_Str
		}
		set
		{
			tmpStr := this.shovePeriod_Str ; this is always defined via _New()
			Loop, Parse, tmpStr, |
			{
				if (A_Index = value)
				{
					return this.shovePeriod_Secs := A_LoopField
				}
				retVal := A_LoopField
			}
			return this.shovePeriod_Secs := retVal  ; default to last value in shovePeriod_Str
		}
	}
	
	; I added the hideIcon feature and then realized it was kind of pointless and annoying.
	; Once hidden, you can't get to the Properties GUI to undo it.  So, you have to resort to manually editing the INI file.
	hideTrayIcon
	{
		get
		{
			return false
			; return this._hideTrayIcon
		}
		set
		{
			return false ; Do nothing
			; if( A_IconHidden != value ) ; has the setting changed?
			; {
				; if (value)
				; {
					; Menu, Tray, NoIcon
				; }
				; else
				; {
					; Menu, Tray, Icon
				; }
			; }
			; return this._hideTrayIcon := value
		}
	}
	
	hideDebugMenu
	{ ; redraw the Tray Menu based upon the value of this property
		get
		{
			return this._hideDebugMenu
		}
		set
		{
			if (value)
			{
				; I don't like calling functions outside a class definition but it is the right thing to do here
				GoSub Main_CreateMiniMenu
			}
			else
			{
				; I don't like calling functions outside a class definition but it is the right thing to do here
				GoSub Main_CreateDebugMenu
			}
			return this._hideDebugMenu := value
		}
	}
		
	; Methods (of suitable importance)
	; ---------------------------------------------
	shovedAnotherWindow()
	{ ; every time a window is shoved increment this.numbWindowsShoved and write to the INI value
		this.numbWindowsShoved++
		; maybe we should only write every X times something is shoved?
		errLvl := this.writeINIFileValue("Number_Of_Windows_Shoved", this.numbWindowsShoved)
		return errLvl
	}
	
	readINIFile(aIniFile="")
	{ ; get the INI variables from the file (various default values)
		; Input variables
		; 	aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
		if(aIniFile="")
		{
			aIniFile := this.iniFile
		}
		
		retValF := FileExist(aIniFile) ; indicate if the INI file already existed (and hopefully the default values where NOT used)
		IniRead, OutputVarSectionNames, aIniFile
		secVar := this.SectionName
		retValS := InStr(OutputVarSectionNames, secVar) ; is there the Section we need?
		retVal := (retValF AND retValS)
		if (!retVal) ; write an INI file if we don't have one with the needed Section
		{
			this.writeDefaultINIFile(aIniFile)
		}
		
		this.canShoveFromLeft      := this.readINIFileValue("Shove_From_Left", true, aIniFile)
		this.canShoveFromRight     := this.readINIFileValue("Shove_From_Right", true, aIniFile)
		this.canShoveFromTop       := this.readINIFileValue("Shove_From_Top", true, aIniFile)
		this.canShoveFromBottom    := this.readINIFileValue("Shove_From_Bottom", true, aIniFile)
		this.canResize             := this.readINIFileValue("Resize_If_Needed", true, aIniFile)
		this.hideTrayIcon          := this.readINIFileValue("Hide_Tray_Icon", false, aIniFile)
		this.hideDebugMenu         := this.readINIFileValue("Hide_Debug_Menu", true, aIniFile)
		this.canShoveWinoids       := this.readINIFileValue("Shove_Winoids", false, aIniFile)
		this.shovePeriod_Secs      := this.readINIFileValue("AutoShove_Seconds", 0.5, aIniFile)
		this.percentOffBeforeShove := this.readINIFileValue("Percentage_Off_Before_Shove", 0.000001, aIniFile)
		this.runningTime           := this.readINIFileValue("Running_Time", 0, aIniFile)
		this.numbWindowsShoved     := this.readINIFileValue("Number_Of_Windows_Shoved", 0, aIniFile)

		defInstallTime := A_Now
		this.installedOn           := this.readINIFileValue("Installed_On", defInstallTime, aIniFile)
		if (defInstallTime = this.installedOn)
		{
			; Installed_On is the one value that is never written outside of writeDefINI().  So, write it here too.
			this.writeINIFileValue("Installed_On", defInstallTime, aIniFile)
		}

		return retVal
	}
	

	writeINIFile(aIniFile="")
	{ ; write user changeable settings to the INI file
		; Input variables
		; 	aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
		; Output variables
		; 	errorLevel (Boolean) -> did an error occur while writing to the INI file?
		if(aIniFile="")
		{
			aIniFile := this.iniFile
		}
		errLvl := []
		errLvl[1]  := this.writeINIFileValue("Shove_From_Left",     this.canShoveFromLeft, aIniFile)
		errLvl[2]  := this.writeINIFileValue("Shove_From_Right",    this.canShoveFromRight, aIniFile)
		errLvl[3]  := this.writeINIFileValue("Shove_From_Top",      this.canShoveFromTop, aIniFile)
		errLvl[4]  := this.writeINIFileValue("Shove_From_Bottom",   this.canShoveFromBottom, aIniFile)
		errLvl[5]  := this.writeINIFileValue("Resize_If_Needed",    this.canResize, aIniFile)
		; errLvl[6]  := this.writeINIFileValue("Hide_Tray_Icon",       this.hideTrayIcon, aIniFile)
		errLvl[6]  := this.writeINIFileValue("Hide_Debug_Menu",     this.hideDebugMenu, aIniFile)
		errLvl[7]  := this.writeINIFileValue("Shove_Winoids",       this.canShoveWinoids, aIniFile)
		errLvl[8]  := this.writeINIFileValue("AutoShove_Seconds",   this.shovePeriod_Secs, aIniFile)
		errLvl[9]  := this.writeINIFileValue("Percentage_Off_Before_Shove", this.percentOffBeforeShove, aIniFile)
		
		retVal := this.arrayOR(errLvl) ; indicate if any error occurred
		return retVal
	}
	
	writeDefaultINIFile(aIniFile="")
	{ ; write default values ot the INI file, including the non-user changeable ones (i.e., installed_on)
		; Input variables
		; 	aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
		; Output variables
		; 	errorLevel (Boolean) -> did an error occur while writing to the INI file?
		if(aIniFile="")
		{
			aIniFile := this.iniFile
		}
		secVar := this.SectionName
		IniDelete, %aIniFile%, %secVar%
		
		errLvl := []
		errLvl[1]  := this.writeINIFileValue("Shove_From_Left", 1, aIniFile)
		errLvl[2]  := this.writeINIFileValue("Shove_From_Right", 1, aIniFile)
		errLvl[3]  := this.writeINIFileValue("Shove_From_Top", 1, aIniFile)
		errLvl[4]  := this.writeINIFileValue("Shove_From_Bottom", 1, aIniFile)
		errLvl[5]  := this.writeINIFileValue("Resize_If_Needed", 1, aIniFile)
		; errLvl[6]  := this.writeINIFileValue("Hide_Tray_Icon", 0, aIniFile)
		errLvl[6]  := this.writeINIFileValue("Hide_Debug_Menu", 1, aIniFile)
		errLvl[7]  := this.writeINIFileValue("Shove_Winoids", 0, aIniFile)
		errLvl[8]  := this.writeINIFileValue("AutoShove_Seconds", 0.5, aIniFile)
		errLvl[9]  := this.writeINIFileValue("Percentage_Off_Before_Shove", 0, aIniFile)
		errLvl[10] := this.writeINIFileValue("Installed_On", A_Now, aIniFile)
		errLvl[11] := this.writeINIFileValue("Running_Time", 0, aIniFile)
		errLvl[12] := this.writeINIFileValue("Number_Of_Windows_Shoved", 0, aIniFile)
		
		retVal := this.arrayOR(errLvl) ; indicate if any error occurred
		return retVal
	}
	
	writeINIFileOnClose()
	{ ; write non-user editable values on the close of the script.
		; Output variables
		; 	errorLevel (Boolean) -> did an error occur while writing to the INI file?
		errLvl := []
		errLvl[1] := this.writeINIFileValue("Running_Time", this.runningTime, aIniFile)
		errLvl[2] := this.writeINIFileValue("Number_Of_Windows_Shoved", this.numbWindowsShoved, aIniFile)
		
		retVal := this.arrayOR(errLvl) ; indicate if any error occurred
		return retVal
	}
	
	
	; Methods (helpers)
	; ---------------------------------------------
	writeINIFileValue(aKey, aValue, aIniFile="", aSection="")
	{ ; a wrapper to IniWrite
		; Input variables
		; 	aKey/aValue -> the key/value to write to the INI file
		; 	aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
		; 	aSectionName -> the section of the INI file to use (default is A_ComputerName)
		; Output variables
		; 	errorLevel (Boolean) -> did an error occur while writing to the INI file?
		if(aIniFile="")
		{
			aIniFile := this.iniFile
		}
		if(aSection="")
		{
			aSection := this.SectionName
		}
		IniWrite, %aValue%, %aIniFile%, %aSection%, %aKey%
		return ErrorLevel
	}
	
	readINIFileValue(aKey, aDefValue, aIniFile="", aSection="")
	{ ; a wrapper to IniRead (mostly a workaround for the fact that you can't use obj.var syntax in IniRead)
		; Input variables
		; 	aKey -> the key to read from the INI file
		; 	aDefValue -> if the aKey cannot be read, supply this as the returned value
		; 	aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
		; 	aSectionName -> the section of the INI file to use (default is A_ComputerName)
		; Output variables
		; 	Value -> the value associated with the aKey
		
		if(aIniFile="")
		{
			aIniFile := this.iniFile
		}
		if(aSection="")
		{
			aSection := this.SectionName
		}
		IniRead, OutVar, %aIniFile%, %aSection%, %aKey%, %aDefValue%
		return OutVar
	}
	
	getSupportFilename(fileExten, addDot=true)
	{ ; provides a filename of a support file (e.g., ini, ico), regardless of whether the script is compiled or not
		; Input variables
		; 	fileExten -> the file extension of the support file (place in quotes)
		; 	addDot -> should a '.' be added before fileExten? (Default=true)
		; Output variables
		; 	a filename of the script with the extension replaced with fileExten

		if(fileExten != "")
		{
			if(addDot)
			{
				replacementStr = .%fileExten%
			}
			else
			{
				replacementStr = %fileExten%
			}
		}
		else
		{
			replacementStr := ""
		}
		if(A_IsCompiled)
		{
			StringReplace, returnName, A_ScriptName,.exe,%replacementStr%, All
		}
		else
		{
			StringReplace, returnName, A_ScriptName,.ahk,%replacementStr%, All
		}
		return returnName
	}
	
	arrayOR(array)
	{ ; take an array of Booleans of arbitrary length and OR them together.
		; Input variables
		; 	array[] (Booleans) -> an array of Booleans (e.g., bit flags)
		; Output variables
		; 	result (Boolean) -> the OR of the array[]

		For index, arrayVal in array
		{
			if (arrayVal)
			{
				return true  ; at least 1 value is true
			}
		}
		return false ; if you got here all the values were false
	}
}

;EndRegion

/*
;==============================================================================
;========== Version History ===================================================
;==============================================================================
; v0.5 2018-03-07
; * basic information gathering and display in the Debug GUI
; 
; v1.0 2018-04-24
; * all major functions present
; * seems to work
;
; v1.1 2018-04-27
; * pulled out Hide Icon
; * added Hide Debug menu (moved Menu create to subroutines)
; * cleaned up code, moved anything called from the Library.ahk file to this file
; * I actually did try to pull up the original HLP files.  They appear to be essentially empty (no text of note, about the same as trayTipString).
; 
;==============================================================================
*/




Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 147 guests