﻿#include-once
#include <MenuConstants.au3>
#include <StructureConstants.au3>
#include <WinAPIGdi.au3>
#include <WinAPISysInternals.au3>
#include <WinAPISysWin.au3>
#include <WindowsConstants.au3>
#include <WinAPISys.au3>
#include <WinAPIvkeysConstants.au3>

OnAutoItExitRegister('_WindowDocking_Exit')

Global $__g_ahDocking_Gui[0][3] ; 0 = hWnd, 1 = ProcOld, 2 = MagneticBorder
Global $__g_hDocking_ProcNew = DllCallbackRegister('_WindowDocking_Proc', 'ptr', 'hwnd;uint;long;ptr')

Global Const $__g_sDocking_Version = '1.5.0.0'
Global Const $__g_sDocking_Date = '2018/07/14 16:00:00'

; #FUNCTION# ====================================================================================================================
; Name ..........: _WindowDocking_Exit
; Description ...: Wird beim beenden des Scripts zum aufraeumen aufgerufen
; Syntax ........: _WindowDocking_Exit()
; Parameters ....: None
; Return values .: None
; Author ........: Oscar (www.autoit.de)
; ===============================================================================================================================
Func _WindowDocking_Exit()
	Local $iCount = UBound($__g_ahDocking_Gui)
	If $iCount > 0 Then
		For $i = 0 To $iCount - 1
			_WinAPI_SetWindowLong($__g_ahDocking_Gui[$i][0], $GWL_WNDPROC, $__g_ahDocking_Gui[$i][1])
		Next
	EndIf
	DllCallbackFree($__g_hDocking_ProcNew)
EndFunc   ;==>_WindowDocking_Exit

; #FUNCTION# ====================================================================================================================
; Name ..........: _WindowDocking_Add
; Description ...: Schaltet fuer das angegebene Fenster das Docking ein
; Syntax ........: _WindowDocking_Add($hWnd[, $iMagneticBorder = 50])
; Parameters ....: $hWnd                - das Fensterhandle
;                  $iMagneticBorder     - [optional] Integerwert fuer den "magnetischen Rahmen" (Standard: 50 px)
; Return values .: Im Erfolgsfall "True", ansonsten "False" und
;                  @error: 1, wenn $hWnd kein Fensterhandle
; Author ........: Oscar (www.autoit.de)
; ===============================================================================================================================
Func _WindowDocking_Add($hWnd, $iMagneticBorder = 50)
	If Not IsHWnd($hWnd) Then Return SetError(1, 0, False)
	Local $iCount = UBound($__g_ahDocking_Gui) + 1, $iColumns = UBound($__g_ahDocking_Gui, $UBOUND_COLUMNS)
	ReDim $__g_ahDocking_Gui[$iCount][$iColumns]
	$__g_ahDocking_Gui[$iCount - 1][0] = $hWnd
	$__g_ahDocking_Gui[$iCount - 1][1] = _WinAPI_SetWindowLong($hWnd, $GWL_WNDPROC, DllCallbackGetPtr($__g_hDocking_ProcNew))
	$__g_ahDocking_Gui[$iCount - 1][2] = $iMagneticBorder
	_WinAPI_InvalidateRect($hWnd)
	Return SetError(0, 0, True)
EndFunc   ;==>_WindowDocking_Add

; #FUNCTION# ====================================================================================================================
; Name ..........: _WindowDocking_Remove
; Description ...: Schaltet fuer das angegebene Fenster das Docking aus
; Syntax ........: _WindowDocking_Remove($hWnd)
; Parameters ....: $hWnd                - das Fensterhandle
; Return values .: Im Erfolgsfall "True", ansonsten "False" und
;                  @error: 1, wenn $hWnd kein Fensterhandle
;                          2, wenn im globalen Array keine Eintraege vorhanden sind
;                          3, wenn das Fensterhandle nicht im globalen Array vorhanden ist
; Author ........: Oscar (www.autoit.de)
; ===============================================================================================================================
Func _WindowDocking_Remove($hWnd)
	Local $iColumns = UBound($__g_ahDocking_Gui, $UBOUND_COLUMNS), $iIndex = _WindowDocking_GetIndex($hWnd)
	If @error Then Return
	_WinAPI_SetWindowLong($__g_ahDocking_Gui[$iIndex][0], $GWL_WNDPROC, $__g_ahDocking_Gui[$iIndex][1])
	For $j = 0 To $iColumns - 1
		$__g_ahDocking_Gui[$iIndex][$j] = $__g_ahDocking_Gui[UBound($__g_ahDocking_Gui) - 1][$j]
	Next
	ReDim $__g_ahDocking_Gui[UBound($__g_ahDocking_Gui) - 1][$iColumns]
	Return SetError(0, 0, True)
EndFunc   ;==>_WindowDocking_Remove

; #FUNCTION# ====================================================================================================================
; Name ..........: _WindowDocking_GetIndex
; Description ...: Wird nur intern verwendet, um anhand des Fensterhandle den Array-Index zu ermitteln
; Syntax ........: _WindowDocking_GetIndex($hWnd)
; Parameters ....: $hWnd                - das Fensterhandle
; Return values .: Im Erfolgsfall den Array-Index, ansonsten
;                  @error: 1, wenn $hWnd kein Fensterhandle
;                          2, wenn im globalen Array keine Eintraege vorhanden sind
;                          3, wenn das Fensterhandle nicht im globalen Array vorhanden ist
; Author ........: Oscar (www.autoit.de)
; ===============================================================================================================================
Func _WindowDocking_GetIndex($hWnd)
	If Not IsHWnd($hWnd) Then Return SetError(1)
	Local $iCount = UBound($__g_ahDocking_Gui)
	If $iCount = 0 Then Return SetError(2)
	For $iIndex = 0 To $iCount - 1
		If $__g_ahDocking_Gui[$iIndex][0] = $hWnd Then Return $iIndex
	Next
	Return SetError(3)
EndFunc

; #FUNCTION# ====================================================================================================================
; Name ..........: _WindowDocking_SetMagneticBorder
; Description ...: Setzt den "magnetischen Rahmen" fuer das angegebene Fenster auf den neuen Wert
; Syntax ........: _WindowDocking_SetMagneticBorder($hWnd, $iMagneticBorder)
; Parameters ....: $hWnd                - das Fensterhandle
;                  $iMagneticBorder     - Integerwert fuer den "magnetischen Rahmen"
; Return values .: Im Erfolgsfall "True", ansonsten "False" und
;                  @error: 1, wenn $hWnd kein Fensterhandle
;                          2, wenn im globalen Array keine Eintraege vorhanden sind
;                          3, wenn das Fensterhandle nicht im globalen Array vorhanden ist
; Author ........: Oscar (www.autoit.de)
; ===============================================================================================================================
Func _WindowDocking_SetMagneticBorder($hWnd, $iMagneticBorder)
	Local $iIndex = _WindowDocking_GetIndex($hWnd)
	If @error Then Return
	$__g_ahDocking_Gui[$iIndex][2] = $iMagneticBorder
	Return SetError(0, 0, True)
EndFunc   ;==>_WindowDocking_SetMagneticBorder

; #FUNCTION# ====================================================================================================================
; Name ..........: _WindowDocking_SetVisible
; Description ...: Zentriert ein Fenster auf dem am naechsten gelegenen Bildschirm, wenn sich die Fensterposition ausserhalb
;                  des Desktopsbereichs befindet
; Syntax ........: _WindowDocking_SetVisible($hWnd)
; Parameters ....: $hWnd                - das Fensterhandle
; Return values .: Im Erfolgsfall (Fenster befand sich auf dem Desktop) "True", ansonsten "False" und
;                  @error: 1, wenn das Fenster verschoben werden musste
; Author ........: Oscar (www.autoit.de)
; ===============================================================================================================================
Func _WindowDocking_SetVisible($hWnd)
	Local $hScreen, $tWinPlmnt, $aScreenInfo, $iScreenW, $iScreenH, $iWinW, $iWinH, $iWinX, $iWinY
	$hScreen = _WinAPI_MonitorFromWindow($hWnd, $MONITOR_DEFAULTTONULL)
	If $hScreen Then Return True
	$tWinPlmnt = _WinAPI_GetWindowPlacement($hWnd)
	$hScreen = _WinAPI_MonitorFromWindow($hWnd, $MONITOR_DEFAULTTONEAREST)
	$aScreenInfo = _WinAPI_GetMonitorInfo($hScreen)
	$iScreenW = DllStructGetData($aScreenInfo[1], 'Right') - DllStructGetData($aScreenInfo[1], 'Left')
	$iScreenH = DllStructGetData($aScreenInfo[1], 'Bottom') - DllStructGetData($aScreenInfo[1], 'Top')
	$iWinW = DllStructGetData($tWinPlmnt, 'rcNormalPosition', 3) - DllStructGetData($tWinPlmnt, 'rcNormalPosition', 1)
	$iWinH = DllStructGetData($tWinPlmnt, 'rcNormalPosition', 4) - DllStructGetData($tWinPlmnt, 'rcNormalPosition', 2)
	$iWinX = $iScreenW / 2 - $iWinW / 2 + DllStructGetData($aScreenInfo[1], 'Left')
	$iWinY = $iScreenH / 2 - $iWinH / 2 + DllStructGetData($aScreenInfo[1], 'Top')
	DllStructSetData($tWinPlmnt, 'rcNormalPosition', $iWinX, 1) ; new left
	DllStructSetData($tWinPlmnt, 'rcNormalPosition', $iWinY, 2) ; new top
	DllStructSetData($tWinPlmnt, 'rcNormalPosition', $iWinX + $iWinW, 3) ; new right
	DllStructSetData($tWinPlmnt, 'rcNormalPosition', $iWinY + $iWinH, 4) ; new bottom
	_WinAPI_SetWindowPos($hWnd, 0, $iWinX, $iWinY, 0, 0, BitOR($SWP_NOSIZE, $SWP_NOSENDCHANGING)) ; ohne Nachricht an WM_WINDOWPOSCHANGING bewegen
	_WinAPI_SetWindowPlacement($hWnd, $tWinPlmnt) ; jetzt erst die neuen Werte dem Fenster zuweisen
	Return SetError(1, 0, False)
EndFunc   ;==>_WindowDocking_SetVisible

; #FUNCTION# ====================================================================================================================
; Name ..........: _WindowDocking_Proc
; Description ...: Die neue WindowProc, die dem Dockingfenster bei "_WindowDocking_Add" zugewiesen wird.
; Syntax ........: _WindowDocking_Proc($hWnd, $iMsg, $wParam, $lParam)
; Return values .: None
; Author ........: Oscar (www.autoit.de)
; ===============================================================================================================================
Func _WindowDocking_Proc($hWnd, $iMsg, $wParam, $lParam)
	Local $iIndex, $iMagneticBorder, $hOldProc, $hScreen, $aInfo, $tWindowPos, $iLeft, $iTop, $iWindowStyle, $iCX, $iCY
	Local Static $bActive = False
	$iIndex = _WindowDocking_GetIndex($hWnd)
	$iMagneticBorder = $__g_ahDocking_Gui[$iIndex][2]
	$hOldProc = $__g_ahDocking_Gui[$iIndex][1]
	Switch $iMsg
		Case $WM_SYSCOMMAND ; auf das Minimieren des Fensters reagieren
			If $wParam = $SC_MINIMIZE Then $bActive = False ; beim Minimieren das Docking abschalten
		Case $WM_Paint ; WM_SYSCOMMAND wird zu frueh aufgerufen, um auf das Restore zu reagieren, deshalb erst bei WM_PAINT wieder einschalten
			$bActive = True ; erst wenn das Restore (Bewegung des Fensters) abgeschlossen ist, das Docking wieder einschalten
		Case $WM_DESTROY ; beim Schliessen des Fensters (GuiDelete) die alte WindowProc wiederherstellen
			_WindowDocking_Remove($hWnd)
		Case $WM_WINDOWPOSCHANGING ; beim bewegen des Fensters
			If Not BitAND(_WinAPI_GetKeyState($VK_RBUTTON), 128) And $bActive Then
				$hScreen = _WinAPI_MonitorFromWindow($hWnd)
				$aInfo = _WinAPI_GetMonitorInfo($hScreen)
				$tWindowPos = DllStructCreate($tagWINDOWPOS, $lParam)
				$iLeft = DllStructGetData($tWindowPos, 'X')
				$iTop = DllStructGetData($tWindowPos, 'Y')
				$iWindowStyle = _WinAPI_GetWindowLong($hWnd, $GWL_STYLE)
				$iCX = BitAND($iWindowStyle, $WS_THICKFRAME) ? 0 : 6
				$iCY = BitAND($iWindowStyle, $WS_THICKFRAME) ? 0 : 6
				; Check Left
				If DllStructGetData($tWindowPos, 'X') < DllStructGetData($aInfo[1], 'Left') + $iCX + $iMagneticBorder Then _
						$iLeft = DllStructGetData($aInfo[1], 'Left') + $iCX
				; Check Right
				If DllStructGetData($tWindowPos, 'X') + DllStructGetData($tWindowPos, 'CX') > DllStructGetData($aInfo[1], 'Right') - $iCX - $iMagneticBorder Then _
						$iLeft = DllStructGetData($aInfo[1], 'Right') - DllStructGetData($tWindowPos, 'CX') - $iCX
				; Check Top
				If DllStructGetData($tWindowPos, 'Y') < DllStructGetData($aInfo[1], 'Top') + $iCY + $iMagneticBorder Then _
						$iTop = DllStructGetData($aInfo[1], 'Top') + $iCY
				; Check Bottom
				If DllStructGetData($tWindowPos, 'Y') + DllStructGetData($tWindowPos, 'CY') > DllStructGetData($aInfo[1], 'Bottom') - $iCY - $iMagneticBorder Then _
						$iTop = DllStructGetData($aInfo[1], 'Bottom') - DllStructGetData($tWindowPos, 'CY') - $iCY
				; Set new Left and Top
				DllStructSetData($tWindowPos, 'X', $iLeft)
				DllStructSetData($tWindowPos, 'Y', $iTop)
			EndIf
	EndSwitch
	Return DllCall('user32.dll', 'lresult', 'CallWindowProc', 'ptr', $hOldProc, 'hwnd', $hWnd, 'uint', $iMsg, 'wparam', $wParam, 'lparam', $lParam)[0]
	; Bug in _WinAPI_CallWindowProc (https://autoit.de/index.php?thread/86028-mehrere-udfs-mit-windowproc-verketten/&postID=690695#post690695)
EndFunc   ;==>_WindowDocking_Proc
