Ok, so I can't help myself from tinkering... here's a new version.
Change 1
Version 3.0 introduces a new algorithm for determining the monitor to which a window belongs. Previously, the code used to look at the centre point of the window and see which monitor contained it. The obvious problem with that is that it's quite possible for a window to be hanging so far outside the bounds of its monitor that its centre doesn't show up in any of them.
The new algorithm looks for the monitor which contains 'most' of the window, in the sense of screen area. The only way this new algorithm can 'fail' is for the window to not appear at all in any monitor. I'm not sure if that's even possible -- I'm really not much of a Windows guru.
Change 2
I've introduced what I think is a better way handle moving / resizing. I've defined a new function called Win__AlignToGrid(). Basically, it takes a window and a grid definition given by the user and it moves / re-sizes the window to fit on one or more cells in the grid. See the documentation at the top of the function itself for more details.
Change 3
There was a bunch of code in common with my other current AHK programming project, Win__Fling(). That function is designed to fling (move, shift, throw) a window from one monitor to another in a multi-monitor system. Being a good programmer, I decided it was time to modularize the code, so this new version has a bunch of small functions that are used by both Win__AlignToGrid() and Win__Fling().
I've included the code for Win__Fling() below, but my
other thread which introduced it has had no action so far, so I'm thinking you probably don't care.
Change 4
Inspired by DeRoc's input, I've introduced a new window moving function, Win__Centre(), which centres a window. However, unlike DeRoc's version, this function doesn't attempt to re-size the window, just move it so that it occupies the center portion of its monitor.
The code is included below. I've broken it up into different logical parts, but you'll of course need all of it for the thing to work.
The Main Window Resizing Code
Code:
;; -----------------------------------------------------------------------
;; Verifies that the given window exists. Along the way it also resolves
;; special values of the "WinID" function parameter:
;; 1) The letter "A" means to use the Active window
;; 2) The letter "M" means to use the window under the Mouse
;; The parameter value is checked to see that it corresponds to a valid
;; window, the function returning true or false accordingly.
Win__ResolveWinID(ByRef WinID)
{
if (WinID = "A")
{
; "A" means: Use the Active window
WinID := WinExist("A")
}
else if (WinID = "M")
{
; "M" means: Use the window currently under the Mouse
MouseGetPos,,, WinID ; MouseX, MouseY are not needed
}
; Check to make sure we are working with a valid window ID
IfWinNotExist, ahk_id %WinID%
{
; Make a short noise so the user knows to stop expecting something fun to happen.
SoundPlay, *64
; Debug Support
;MsgBox, 16, Error, Specified window does not exist.`nWindow ID = %WinID%
return false
}
return true
}
;; -----------------------------------------------------------------------
;; Set the min/maximized state of the given window.
;;
;; This function serves as a kind of inverse to the built-in function:
;; WinGet, Var, MinMax, WinID
Win__SetMinMax(TargetMinMaxState, WinID)
{
WinGet, CurrentMinMaxState, MinMax, ahk_id %WinID%
if CurrentMinMaxState <> TargetMinMaxState
{
if (TargetMinMaxState = 1)
{
WinMaximize, ahk_id %WinID%
}
else if (TargetMinMaxState = -1)
{
WinMinimize, ahk_id %WinID%
}
else
{
WinRestore, ahk_id %WinID%
}
}
}
;; -----------------------------------------------------------------------
;; This function returns the position and dimensions of the monitor which
;; contains (the most screen area of) a specified window.
;;
;; Note that the input parameter WinID is resolved to an actual window ID.
;; See the documentation for Win__ResolveWinID() for details.
Win__GetMonitorPosShowingWin(ByRef MonX, ByRef MonY, ByRef MonW, ByRef MonH, ByRef MonN, ByRef WinID)
{
if !Win__ResolveWinID(WinID)
{
; Specified window doesn't exist
return false
}
; Compute the dimensions of the subject window
WinGetPos, WinLeft, WinTop, WinWidth, WinHeight, ahk_id %WinID%
WinRight := WinLeft + WinWidth
WinBottom := WinTop + WinHeight
; How many monitors are we dealing with?
SysGet, MonitorCount, MonitorCount
; For each active monitor, we get Top, Bottom, Left, Right of the monitor's
; 'Work Area' (i.e., excluding taskbar, etc.). From these values we compute Width and Height.
; As we loop, we track which monitor has the largest overlap (in the sense of screen area)
; with the subject window. We call that monitor the window's 'Source Monitor'.
SourceMonitorNum = 0
MaxOverlapArea = 0
Loop, %MonitorCount%
{
MonitorNum := A_Index ; Give the loop variable a sensible name
; Retrieve position / dimensions of the monitor's work area
SysGet, Monitor, MonitorWorkArea, %MonitorNum%
MonitorWidth := MonitorRight - MonitorLeft
MonitorHeight := MonitorBottom - MonitorTop
; Check for any overlap with the subject window
; The following ternary expressions simulate "max(a,b)" and "min(a,b)" type function calls:
; max(a,b) <==> (a>b ? a : b)
; min(a,b) <==> (a<b ? a : b)
; The intersection between two windows is characterized as that part below both
; windows' "Top" values and above both "Bottoms"; similarly to the right of both "Lefts"
; and to the left of both "Rights". Hence the need for all these min/max operations.
MaxTop := (WinTop > MonitorTop ) ? WinTop : MonitorTop
MinBottom := (WinBottom < MonitorBottom) ? WinBottom : MonitorBottom
MaxLeft := (WinLeft > MonitorLeft ) ? WinLeft : MonitorLeft
MinRight := (WinRight < MonitorRight ) ? WinRight : MonitorRight
HorizontalOverlap := MinRight - MaxLeft
VerticalOverlap := MinBottom - MaxTop
if (HorizontalOverlap > 0 and VerticalOverlap > 0)
{
OverlapArea := HorizontalOverlap * VerticalOverlap
if (OverlapArea > MaxOverlapArea)
{
SourceMonitorLeft := MonitorLeft
SourceMonitorRight := MonitorRight ; not used
SourceMonitorTop := MonitorTop
SourceMonitorBottom := MonitorBottom ; not used
SourceMonitorWidth := MonitorWidth
SourceMonitorHeight := MonitorHeight
SourceMonitorNum := MonitorNum
MaxOverlapArea := OverlapArea
}
}
}
if MaxOverlapArea = 0
{
; If the subject window wasn't visible in *ANY* monitor, default to the 'Primary'
SysGet, SourceMonitorNum, MonitorPrimary
SysGet, SourceMonitor, MonitorWorkArea, %SourceMonitorNum%
SourceMonitorWidth := SourceMonitorRight - SourceMonitorLeft
SourceMonitorHeight := SourceMonitorBottom - SourceMonitorTop
}
MonX := SourceMonitorLeft
MonY := SourceMonitorTop
MonW := SourceMonitorWidth
MonH := SourceMonitorHeight
MonN := SourceMonitorNum
return true
}
;; -----------------------------------------------------------------------
;; Prepare a window for any sort of scripted 'move' operation.
;;
;; The first thing to do is to restore the window if it was min/maximized.
;; The reason for this is that the standard min/max window controls don't
;; seem to like it if you script a move / resize while a window is
;; minimized or maximized.
;;
;; After that, we look to see which monitor holds the "most" of the window
;; (in the sense of screen real estate) and we return a bunch of information
;; about that monitor so the caller can figure out the best way to do the move.
;;
;; The original min/max state is also returned in case the window needs to
;; be restored to that state at some future time.
;;
;; The window ID is also resolved, as per the function Win__ResolveWinID().
Win__PrepForMove(ByRef MonX, ByRef MonY, ByRef MonW, ByRef MonH, ByRef MonN, ByRef WinMinMax, ByRef WinID)
{
if !Win__ResolveWinID(WinID)
{
; Subject window doesn't exist
return false
}
; If the subject window started out min/maximized, then we restore it
; in preparation of it being moved or resized.
WinGet, WinMinMax, MinMax, ahk_id %WinID%
if WinMinMax
{
WinRestore, ahk_id %WinID%
}
result := Win__GetMonitorPosShowingWin(MonX, MonY, MonW, MonH, MonN, WinID)
return result
}
;; -----------------------------------------------------------------------
;; Move and resize a window to align it to a specified screen grid.
;;
;; The first two parameters, GridCols and GridRows, determine the granularity
;; of the grid. The other four grid parameters determine which grid cell
;; the window is to fit into and how big it should be in each direction:
;;
;; GridUnitsX: The X coordinate of the top left grid cell, in the range 1..GridCols
;; GridUnitsY: The Y coordinate of the top left grid cell, in the range 1..GridRows
;; GridUnitsW: The width of the window, in units of cells
;; GridUnitsH: The height of the window, in units of cells
;;
;; For example, to arrange six windows on the screen, three across and two
;; down, each occupying a single grid cell, you might issue the following
;; six commands to six different windows (WinID1 .. WinID6):
;;
;; Win__AlignToGrid(3, 2, 1, 1, 1, 1, WinID1)
;; Win__AlignToGrid(3, 2, 1, 2, 1, 1, WinID2)
;; Win__AlignToGrid(3, 2, 1, 3, 1, 1, WinID3)
;; Win__AlignToGrid(3, 2, 2, 1, 1, 1, WinID4)
;; Win__AlignToGrid(3, 2, 2, 2, 1, 1, WinID5)
;; Win__AlignToGrid(3, 2, 2, 3, 1, 1, WinID6)
;;
;; I've added extra spaces between pairs of related parameters to act as visual
;; clues for the reader. The spaces are, of course, not required.
;;
;; These commands would result in the following gridded window arrangement:
;;
;; +---------+---------+---------+
;; | | | |
;; | 1 | 2 | 3 |
;; | | | |
;; +---------+---------+---------+
;; | | | |
;; | 4 | 5 | 6 |
;; | | | |
;; +---------+---------+---------+
;;
;; As another example, consider the following two commands:
;;
;; Win__AlignToGrid(3, 2, 1, 1, 2, 2, WinID7)
;; Win__AlignToGrid(3, 2, 3, 1, 1, 2, WinID8)
;;
;; Here the windows are larger than a single grid cell, as they were in the first example.
;; The first command asks for a 2x2 window and the second one asks for a 1x2 (1 col, 2 rows)
;; window. This ought to result in the following window arrangement:
;;
;; +-------------------+---------+
;; | | |
;; | | |
;; | | |
;; | 7 | 8 |
;; | | |
;; | | |
;; | | |
;; +-------------------+---------+
;;
Win__AlignToGrid(GridCols, GridRows, GridUnitsX, GridUnitsY, GridUnitsW, GridUnitsH, WinID)
{
if !Win__PrepForMove(MonX, MonY, MonW, MonH, MonN, WinMinMax, WinID)
{
return false
}
; Bound the input parameters so that they make sense
; and so the window will actually fit on the screen
Gen__Bound(GridCols, 1, MonW)
Gen__Bound(GridRows, 1, MonH)
Gen__Bound(GridUnitsX, 1, GridCols)
Gen__Bound(GridUnitsY, 1, GridRows)
Gen__Bound(GridUnitsW, 1, GridCols - GridUnitsX + 1)
Gen__Bound(GridUnitsH, 1, GridRows - GridUnitsY + 1)
X := Round(MonW * (GridUnitsX - 1) / GridCols) + MonX
Y := Round(MonH * (GridUnitsY - 1) / GridRows) + MonY
W := Round(MonW * GridUnitsW / GridCols)
H := Round(MonH * GridUnitsH / GridRows)
WinMove, ahk_id %WinID%,, X, Y, W, H
return true
}
The New Half and Quarter Sized Window HandlersCode:
;; -----------------------------------------------------------------------
Win__QuarterTopLeft(WinID)
{
; C R X Y W H
Win__AlignToGrid(2, 2, 1, 1, 1, 1, WinID)
}
;; -----------------------------------------------------------------------
Win__QuarterTopRight(WinID)
{
; C R X Y W H
Win__AlignToGrid(2, 2, 2, 1, 1, 1, WinID)
}
;; -----------------------------------------------------------------------
Win__QuarterBottomLeft(WinID)
{
; C R X Y W H
Win__AlignToGrid(2, 2, 1, 2, 1, 1, WinID)
}
;; -----------------------------------------------------------------------
Win__QuarterBottomRight(WinID)
{
; C R X Y W H
Win__AlignToGrid(2, 2, 2, 2, 1, 1, WinID)
}
;; -----------------------------------------------------------------------
; Mimic Windows-7 Win-Left Key Combination
Win__HalfLeft(WinID)
{
; C R X Y W H
Win__AlignToGrid(2, 2, 1, 1, 1, 2, WinID)
}
;; -----------------------------------------------------------------------
; Mimic Windows-7 Win-Right Key Combination
Win__HalfRight(WinID)
{
; C R X Y W H
Win__AlignToGrid(2, 2, 2, 1, 1, 2, WinID)
}
;; -----------------------------------------------------------------------
Win__HalfTop(WinID)
{
; C R X Y W H
Win__AlignToGrid(2, 2, 1, 1, 2, 1, WinID)
}
;; -----------------------------------------------------------------------
Win__HalfBottom(WinID)
{
; C R X Y W H
Win__AlignToGrid(2, 2, 1, 2, 2, 1, WinID)
}
;; -----------------------------------------------------------------------
Win__Fullscreen(WinID)
{
; C R X Y W H
Win__AlignToGrid(1, 1, 1, 1, 1, 1, WinID)
}
;; -----------------------------------------------------------------------
Win__Centre(WinID)
{
if !Win__PrepForMove(MonX, MonY, MonW, MonH, MonN, MinMax, WinID)
{
return false
}
WinGetPos, WinX, WinY, WinW, WinH, ahk_id %WinID%
BorderX := WinW < MonW ? MonW - WinW : 0
BorderY := WinH < MonH ? MonH - WinH : 0
X := MonX + BorderX // 2
Y := MonY + BorderY // 2
W := MonW - BorderX
H := MonH - BorderY
WinMove, ahk_id %WinID%,, X, Y, W, H
return true
}
Some General Purpose StuffCode:
;; -----------------------------------------------------------------------
;; Bound the given variable between the specified lower and upper limits.
;; i.e., the post-condition is: Lower <= Value <= Upper
Gen__Bound(ByRef Value, Lower, Upper)
{
if (Value < Lower)
{
Value := Lower
}
else if (Value > Upper)
{
Value := Upper
}
}
Code to Implement the Multi-Monitor Window Flinging(This code requires some of the earlier functions to operate)
Code:
;; -----------------------------------------------------------------------
;; Compute the next monitor number in (circular) sequence.
Win__NextMonitorNum(SourceMonitorNum, Direction = 1)
{
SysGet, MonitorCount, MonitorCount
TargetMonitorNum := SourceMonitorNum + (Direction >= 0 ? 1 : -1)
; Valid monitor numbers are 1..MonitorCount.
; Adjust target for out-of-bounds values while effecting a 'circular' sequence.
if (TargetMonitorNum > MonitorCount)
{
TargetMonitorNum = 1
}
else if (TargetMonitorNum <= 0)
{
TargetMonitorNum = %MonitorCount%
}
return TargetMonitorNum
}
;; -----------------------------------------------------------------------
;; Fling (i.e., move, shift, throw) a window from one monitor to the next in a multi-monitor system.
;;
;; Function Parameters:
;;
;; FlingDirection The direction of the fling, expected to be either +1 or -1.
;; The function is not limited to just two monitors; it supports
;; as many monitors as are currently connected to the system and
;; can fling a window serially through each of them in turn, in a
;; circular fashion.
;;
;; WinID The window ID of the window to move.
;; There are two special WinID values supported:
;;
;; 1) The value "A" means to use the [A]ctive window (default).
;; 2) The value "M" means to use the window currently under the [M]ouse.
;;
;; The flung window will be resized to have the same *relative* size in the new monitor.
;; For example, if the window originally occupied the entire right half of its original monitor,
;; it will again on the new monitor (assuming the window is one that can be resized).
;;
;; The return value of the function is non-zero if the window was successfully flung.
;;
;; Example hotkeys:
;; #NumpadEnter:: Win__Fling(1, "A") ; Windows-NumpadEnter flings the active window
;; #LButton:: Win__Fling(1, "M") ; Windows-LeftClick flings the window under the mouse
Win__Fling(FlingDirection = 1, WinID = "A")
{
if FlingDirection = 0
{
; Ok, so we're going nowhere after all
return true
}
; Assume the worst
result = false
; If the subject window started out min/maximized, then the plan is to:
; (a) restore it,
; (b) fling it, then
; (c) re-min/maximize it on the target monitor.
; The reason for this is so that the usual maximize / restore windows controls
; work as you'd expect. You want Windows to use the dimensions of the non-maximized
; window when you click the little restore icon on a previously flung (min/maximized) window.
if Win__PrepForMove(SourceMonitorLeft, SourceMonitorTop, SourceMonitorWidth, SourceMonitorHeight, SourceMonitorNum, WinMinMax, WinID)
{
; Compute the number of the target monitor in the specified direction of the fling
TargetMonitorNum := Win__NextMonitorNum(SourceMonitorNum, FlingDirection)
if SourceMonitorNum <> TargetMonitorNum
{
; Get the dimensions of the subject window
WinGetPos, WinLeft, WinTop, WinWidth, WinHeight, ahk_id %WinID%
; Get the dimensions of the target monitor
SysGet, TargetMonitor, MonitorWorkArea, %TargetMonitorNum%
TargetMonitorWidth := TargetMonitorRight - TargetMonitorLeft
TargetMonitorHeight := TargetMonitorBottom - TargetMonitorTop
; Translate and scale the position / dimensions of the subject window by the ratio of the two monitor sizes.
; Programming Note 1: Do multiplies before divides in order to maintain accuracy.
; Programming Note 2: If you truncate instead of round, the window tends to creep to the upper left and get smaller.
; as you repeatedly switch monitors. That's not a bug, it's just a natural artefact of integer calculations with
; expansion factors that are non-integers. Rounding, however, tends to stablilize after just a couple of flings.
WinFlingLeft := Round((WinLeft - SourceMonitorLeft) * TargetMonitorWidth / SourceMonitorWidth ) + TargetMonitorLeft
WinFlingTop := Round((WinTop - SourceMonitorTop ) * TargetMonitorHeight / SourceMonitorHeight) + TargetMonitorTop
WinFlingWidth := Round( WinWidth * TargetMonitorWidth / SourceMonitorWidth )
WinFlingHeight := Round( WinHeight * TargetMonitorHeight / SourceMonitorHeight)
; It's time for the subject window to make its highly anticipated move
WinMove, ahk_id %WinID%,, WinFlingLeft, WinFlingTop, WinFlingWidth, WinFlingHeight
}
result = true
}
; If the subject window was originally min/maximized, then min/maximize it again on its new monitor.
; Note that this step should be done whether or not the window actually got moved by the above code.
Win__SetMinMax(WinMinMax, WinID)
; As if anybody is listening...
return result
}
And here are some revised hotkeys. I've moved to using only the Windows key prefix macros, as it just makes sense to me for windows manipulating scripts.
Code:
#Numpad0:: Win__Fullscreen("A")
#Numpad1:: Win__QuarterBottomLeft("A")
#Numpad2:: Win__HalfBottom("A")
#Numpad3:: Win__QuarterBottomRight("A")
#Numpad4:: Win__HalfLeft("A")
#Numpad5:: Win__Centre("A")
#Numpad6:: Win__HalfRight("A")
#Numpad7:: Win__QuarterTopLeft("A")
#Numpad8:: Win__HalfTop("A")
#Numpad9:: Win__QuarterTopRight("A")
#NumpadEnter:: Win__Fling(1, "A") ; Windows-NumpadEnter flings the active window
#LButton:: Win__Fling(1, "M") ; Windows-LeftClick flings the window under the mouse