Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Dock'nDrag - Docks Two Windows Together for Move and Resize


  • Please log in to reply
20 replies to this topic
DAT
  • Members
  • 58 posts
  • Last active: Jul 17 2014 08:55 AM
  • Joined: 01 Dec 2008

Note added 07-02-2009: Instructions and screen shots available at http://docs.google.c...ck29f_0ghgsjtmh.

Note added 16-03-2013: The above link is now working again (after recent changes in Google Docs).

I got the idea for this script because my primary monitor isn’t big enough to have the AHK ScITE editing and Help windows wide enough at the same time when side-by-side. Now I just dock the two windows together and drag the dividing line back and forth as needed. Anyway that’s what got me started on trying to make a script and here it is in case anyone else finds it useful.

I call it Dock’nDrag because you can dock two windows side-by-side (or one above the other) after which you can drag and resize them as a pair. I use WindowPad by Lexikos all the time anyway so it was natural to use CapsLock as modifier and Left-Drag for moving and resizing. Dock’nDrag doesn’t replace WindowPad but I think it complements it quite well.

Plenty of scripts do dragging and resizing but I’ve not seen any that do docking. I cribbed ideas and code from scripts like ‘Easy Window Dragging - KDE style’ by Jonny, and NiftyWindows (both in Script Showcase). A couple of things I felt were missing I’ve added in. When you’re stretching a window in any direction, once it’s reached maximum size the whole window starts moving. In EWD this happens only when you’re pulling upward or to the left but not downward or to the right. Also I added min and max size limits because some applications crash if you accidentally make their window zero-width (Directory Opus for example).

To make the docking as intuitive as the drag and resize part I used CapsLock+RightClick for the ‘Dock’ and ‘Undock’ commands. A CapsLock+RightClick on any of the four sides of a window makes it grab the nearest other window that’s on the same side, and on the same monitor. Do the same in the centre of either window, and it releases the dock. If you imagine a 3-by-3 grid superimposed on each window, it’s the cells in the grid that you have to click in. The docking cells are the middle ones along each edge.

When you dock two nearby windows, the edge that you click in stays fixed but both windows expand out sideways to the full width of the monitor. The height of the ‘slave’ adjusts to match the master. (For vertical docking it’s the same but height become width, etc.). After docking, when you move or resize either window, the other one moves as well but they stay linked along one edge and their total width remains the monitor width. This seems to work out quite well. A fun way to swap the positions of two side-by-side screens is to move one of them around the other in three stages by docking it to each side in turn.

There are three options for selecting the window to grab. I prefer ‘Edge’ which selects the one whose appropriate edge is nearest but I’ve left in two experimental options in case anyone wants to try them. ‘Area’ grabs the window with the greatest area and ‘Centre’ the one whose centre is nearest to the centre of the master.

The script works fine on my XP and Vista laptops and it supports multiple monitors, although the most I’ve tried is three arranged side-by-side. While experimenting I found it useful to have a hotkey that would force an ExitApp. Otherwise if the script caused an error message it would leave CapsLock permanently on making it awkward to terminate from Task Manager. It hasn’t crashed for quite a while now but the panic button is still there as CapsLock+RControl. That’s ok for me because I never use the right-hand control key.

To share CapsLock as the hotkey modifier for both Dock’nDrag and WindowPad, I found I had either to make sure to load WP first and DnD second, or to alter the ini file for WP. When I load WP after DnD, it seems to take over CapsLock completely and it won’t work for DnD. However this restriction goes away if you change the WindowPad ini file slightly. All I had to do was this:

1. Delete or comment out the line “CapsLock = Hotkeys, Active Window (WADS)”.
2. Move the entries that were under [Hotkeys] and put them under [Hotkeys]. (Leave the originals in place if you prefer as they won’t affect anything).
3. Then edit all the new entries under [Hotkeys] by adding “Capslock & ” [note the two spaces] immediately before each of the hotkey characters. So for example, “z = WindowPadMove, -1, +1, 0.5, 0.5” becomes “Capslock & z = WindowPadMove, -1, +1, 0.5, 0.5”.

There’s also a feature in Dock’nDrag that other Directory Opus users might find useful. Copernic Desktop Search and Picasa both ignore the fact that I’ve set Directory Opus as my default folder browser. So as a workaround I added subroutine DoExplorer. Now if Explorer opens instead of DOpus, all I have to do is Caps-Lock+Right Click in the left-hand third of Explorer’s Title Bar – the part that’s in the top left-hand square of the grid - and Explorer shuts down and the folder reopens in DOpus. (Thanks to Andreone’s function ‘IsOverTitleBar’). There’s still cope to trigger else from the right-hand side of a Title Bar.

I use Dock’nDrag everyday and getting it to work taught me a lot, but it will be especially nice if someone else finds it useful as well. Apologies for all the syntax bloomers like unnecessary brackets. Also the coding style is terribly inconsistent. However at least it kind of acknowledges my debt to all those generous forum contributors who may be able to recognise their bits of code that I’ve gratefully ‘recycled’.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 'Dock'nDrag'
; By David Tong 03-02-2009.
;
; Primary Functions:
; 1. CapsLock+left-drag from the central rectangle to move a window without resizing it.
; 2. CapsLock+left-drag a window by corner or side to resize it. Select either '‘Easy Window
; Dragging' or 'NiftyWindows' modes of dragging and resizing.
; 3. CapsLock+rightclick in centre of the strip along an edge to 'dock' a nearby window to it
; Afterwards both windows move or resize as one. Do the same in centre of either window
; to uncouple. [Great for using a Help window at same time as AHK ScITE editor].
; 4. Works with multiple monitors. Boundary limits ensure a coupled window is not stranded on
; a non-existent monitor.
; 5. Max width (height) of a coupled window is that of the monitor. Minimum for either partner
; is set by a parameter.
; 6. When stretching a window in any direction, after it reaches maximum size the whole
; window starts moving. This is more ergonomic than EWD or NiftyW which do this only when
; pulling up or to the left, but not down or to the right.
; 7. Supports multiple monitors and ensures partnered window is not accidentally left
; stranded on a non-existent monitor.
; 8. Tested in XP and Vista.

; Extra Functions:
; 1. CapsLock+Leftclick in left-hand side of title bar of Windows Explorer closes it and opens
; same folder in Directory Opus. (Useful because Picasa and Copernic Desktop Search
; don't respect default browser settings).
; 2. Keeps CapsLock off to avoid typing in caps by mistake. [No longer done as standard]
; 3. As a precaution while testing I included CapsLock+RControl as a hotkey to force an exit
; if the script becomes unresponsive but with CapsLock activated.

; Acknowledgements:
; Basic window dragging functions inspired by ‘Easy Window Dragging - KDE style’
; by Jonny, and NiftyWindows (both in Script Showcase) and similar variants.
; I cribbed lots of code from the many great scripts on the AHK forum, in particular
; WindowPad by Lexikos. Most thanks of all of course to Chris M. for developing
; AutoHotkey in the first place.

SetBatchLines , 20ms ; If too low, non-dragged windows take too long to redraw.
SetWinDelay , 10 ; If too low the dragged window leaves too much of a trail.

#NoEnv
SendMode Input
SetWorkingDir %A_ScriptDir%
#NoTrayIcon
#SingleInstance , ;ignore

;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Set up user options:
;
; Define minimum width limit for windows.
MinDimx := 200 ;Min window width
MinDimy := 200 ;Min window height

; Define the criterion for selecting a partner window when coupling two together.
; In all cases only visible windows on the same monitor are selected, and only those
; whose centres are on the same side of target centre as the side of target that
; you click on.
;
; 'Area' chooses the window with greatest area.
; 'Centre' chooses the window whose centre is nearest to the centre of target window.
; 'Edge' [recommended] Selects the one whose edge-to-be-shared is nearest to the
; edge you clicked.

Criterion := edge

; If DragMode=1, each edge has three clickable sections allowing dragging by side or corner
; like on a normal window. (Same mode as used in NiftyWindows).
; If DragMode<>1, four clickable quadrants giving corner-dragging modes only as in ‘Easy
; Window Dragging - KDE style’.

DragMode = 1 ; set to 1 or 0 (or blank) as desired

; Define the hotkeys:

Modifier := "CapsLock `& " ; Other examples: "!", "+", "Insert `& ", etc.
DragButton := "LButton"
DockButton := "RButton"
EmergencyButton := "RControl"

; End of user setup section
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;;;;;;;;
; Might want to alter the next two lines and CheckCapslock: if using Insert, ScrollLock, etc
IfInString , Modifier, CapsLock
Settimer, CheckCapslock, 200

FullDragButton = %Modifier%%DragButton%
FullDockButton = %Modifier%%DockButton%
FullEmergencyButton = %Modifier%%EmergencyButton%

Hotkey , %FullDragButton% , Resizer
Hotkey , %FullDockButton% , WinDock
Hotkey , %FullEmergencyButton% , Finish
Return

;;;;;;;;;;;;;;;;;;;;
Finish: ; Used for emergency exit if script stalls with CapsLock activated.Gosub , CheckCapslock
Gosub , CheckCapslock
SoundBeep 2000, 500
ExitApp
;;;;;;;;;;;;;;;;;
; Make sure Capslock is not left in an activated state.
CheckCapslock:
GetKeyState , caps , CapsLock , T ; 'D' means key is 'on', 'U' means 'off'.
IfEqual , caps , D
{
sleep 20 ; used to use 100
SetCapsLockState , OFF
}
Return
;;;;;;;;;;;;;;;;;;;;;;;;



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Combines two windows as a pair so they can be moved and resized as one.
;
WinDock:
; Action depends on which of the nine cells (in a 3x3 grid) you clicked in.
; Top left-hand cell is row 1, column 1. Bottom right is 3,3.
; -----------------
; | 1,1 | 1,2 | 1,3 |
; -----------------
; | 2,1 | 2,1 | 2,3 |
; -----------------
; | 3,1 | 3,2 | 3,3 |
; -----------------

; Click in cell:
; 1,2 to couple another window to top edge.
; 3,2 to couple another window to bottom edge.
; 2,1 to couple another window to lefthand edge.
; 2,3 to couple another window to righthand edge.
; 2,2 to uncouple a coupled pair.
; 1,1 and within title bar of Windows Explorer to close Explorer and open
; same folder in Directory Opus.

CoordMode , Mouse , Screen
MouseGetPos ,xx , yy , ID1, Ctrl, 1 ; defines window that you clicked in

; Next command required else first right-click on desktop gives
; context menu with some items missing (those for Directory Opus).
WinActivate , ahk_id %ID1%

; Ignore clicks on desktop
WinGetClass , clss, ahk_id %ID1%
If clss = Progman
Return
Gosub , ClearContextPopup ; Clear unwanted right-click pop-up menus

; Find which cell in the target window was clicked in.
Get_Cell(Column,Row)


; Store a shortlist of acceptable windows in Item1, Item2, etc
WinGetPos , My_x, My_y, My_w, My_h , ahk_id %ID1% ;details for current window
My_mon := GetMonitorAt(My_x+My_w/2, My_y+My_h/2) ; Using which monitor?
Gosub , CalcMonStats ; loads My_monRight, etc
GoSub, Sort

; Do work as defined by the cell.

; LatVert = +1 defines lateral coupling, +1 defines vertical (also used in Resizer).
; LftRght = +1 for coupling to right (or up) and -1 for left (or down)

If (Column=1 and Row=1)
{
If IsOverTitleBar(xx, yy, ID1)
Gosub, DoExplorer
}
Else If (Row=2 and Column=2)
{
If (ID1 = Master) or (ID1 = Slave)
Gosub, UnCouple
Return
}
Else If (Column=1 and Row=2)
{
LatVert := 1 ; lateral
LftRght := -1 ; to left
Winid := FindWin(LatVert, LftRght) ; returns selected id from the list.
IfEqual , Winid ,, Return ; skip if no suitable window
Gosub , Coupled ; announce coupled state

; Master - leave gap for partner and extend other edge to monitor limit
newmx := My_x
If (My_x < (My_monLeft+MinDimx)) ;NB outer brackets are essential here!
newmx := My_monLeft+MinDimx
newmw := My_monRight-newmx

; Partner - move to gap and match height to Master.
newpx := My_monLeft
newpw := Newmx-My_monLeft
WinMove , ahk_id %Master% ,, %newmx% ,, %newmw% ,
WinMove , ahk_id %Winid% ,, %newpx% ,My_y, %newpw% , My_h
Return
}
Else If (Column=3 and Row=2)
{
LatVert := 1 ; lateral
LftRght := 1 ; to right
Winid := FindWin(LatVert, LftRght) ; returns selected id from the list.
IfEqual , Winid ,, Return ; skip if no suitable window
Gosub , Coupled ; announce coupled state

; Master - leave gap for partner and extend other edge to monitor limit
newmx := My_monLeft
newmw := My_w+(My_x-My_monLeft)
If (My_monRight-(My_x+My_w)) < MinDimx
newmw := MaxDimx-MinDimx

; Move Partner into gap and match height to Master.
newpx := My_monLeft+newmw ; newmw value on right refers to main window
newpw := MaxDimx-newmw ; same here
WinMove , ahk_id %Master% ,, %newmx% ,, %newmw% ,
WinMove , ahk_id %Slave% ,, %newpx% , My_y, %newpw% ,My_h
return
}
Else If (Row=1 and Column=2)
{
LatVert := -1 ; vertical
LftRght := +1 ; upward
Winid := FindWin(LatVert, LftRght) ; returns selected id from the list.
IfEqual , Winid ,, Return ; skip if no suitable window
Gosub , Coupled ; announce coupled state

; Master - leave gap for partner and extend other edge to monitor limit
newmy := My_y
If (My_y<MinDimy)
newmy := MinDimy
newmh := MaxDimy-newmy

; Move Partner into gap and match height to Master.
newpy := My_monTop
newph := MaxDimy-newmh
WinMove , ahk_id %Master% ,, ,%newmy%, , %newmh%
WinMove , ahk_id %Slave% ,,%My_x%,%newpy%, %My_w% ,%newph%
Return
}
Else If (Row=3 and Column=2)
{
LatVert := -1 ; vertical
LftRght := -1 ; downward
Winid := FindWin(LatVert, LftRght) ; returns selected id from the list.
IfEqual , Winid ,, Return ; skip if no suitable window
Gosub , Coupled ; announce coupled state

; Master - leave gap for partner and extend other edge to monitor limit
newmy := My_monTop
newmh := My_h+ My_y
If (MaxDimy-newmh)<MinDimy
newmh := MaxDimy- MinDimy

; Move Partner into gap and match height to Master.
newpy := -(My_monTop - newmh)
newph := MaxDimy-newmh
WinMove , ahk_id %Master% ,, ,%newmy%, , %newmh%
WinMove , ahk_id %Slave% ,,%My_x%,%newpy%, %My_w% ,%newph%
Return
}
Return
;;;;;;;;;;

;;;;;;;;;;
; Clears spurious Right-Click menus
;
; Not so easy to find a method that works with all windows. For example,
; sending 'ControlSend,,{ESC}, ahk_id %ID1%' to Word2003 makes it crash.
; This seems ok with everything so far:
;
ClearContextPopup:
WinActivate , ahk_id %ID1%
WinWaitActive , ahk_id %ID1%
sleep ,100
send , {alt}
sleep ,100
send , {esc}
Return
;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;
; Closes an open Explorer window and re-opens same folder in DOpus as a new tab.
; (Useful with Picasa and Copernic which insist on using Explorer instead of DOpus).

DoExplorer:
WinGet, Prcs , ProcessName, ahk_id %ID1%
WinGetTitle , Ttle, ahk_id %ID1%
If Prcs contains explorer
{
Ttle1 = "%Ttle%" ; DOpus command line wants it in quotes
run , "C:\Program Files\GPSoftware\Directory Opus\dopusrt.exe" /CMD Go %Ttle1% Newtab
WinClose , ahk_id %ID1%
}
Return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;
; Get work area (excludes taskbar-reserved space).
; Copied from WindowPad. Modified to suit DAT work area
CalcMonStats:
; Get work area less a guard-band to minimise accidental triggering of
; popup toolbars on three sides of main monitor
SysGet, My_mon, MonitorWorkArea, %My_mon%
IfEqual , My_mon , 1
{
My_monRight := My_monRight ;- 25
My_monTop := My_monTop ;+ 25
My_monLeft := My_monLeft ;+ 25
}
MaxDimx := My_monRight - My_monLeft
MaxDimy := My_monBottom - My_monTop
return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;;;;;;
; Returns the id of a window that meets the selection criteria.
; LatVert = +1 means 'lateral' and -1, 'vertical' coupling
;
; LftRght defines direction, seen from centre of Master window, in which
; to look for a target window.
; If LatVert=+1: LftRght = +1 means look right and -1 means look left
; If LatVert=-1, LftRght = +1 means look up and -1 means look down.
;
; For example: If LatVert=+1 and LftRght = +1, a window will be selected
; whose right edge [see note] is nearest to the left edge of the Master
; (in either direction), and whose centre is to the right of
; the centre of the Master.
;
; Note: Instead of selecting by 'edge' you can select on 'area' or
; 'centre' by setting the variable 'Criterion').

FindWin(LatVert, LftRght)
{
global ID1, Item, Item1, Item2, Item3, Item4, Item5, Criterion ; Assumes max of 5 candidates.

WinGetPos , My_x, My_y, My_w, My_h , ahk_id %ID1%

; Discard unsuitable windows and store parameters for shortlist candidates
{
My_centrex := (My_x+My_w/2)
My_centrey := (My_y+My_h/2) ; missed off until 24-01-2009 12:18
index := "" ; count selected windows
Loop , %Item%
{
this_Item := Item%A_Index%
WinGetPos , this_x, this_y, this_w, this_h , ahk_id %this_Item%
This_centrex := (this_x+this_w/2)
This_centrey := (this_y+this_h/2)

If ((LatVert = +1)
and ((-LftRght+1)/2*this_centrex + (LftRght+1)/2*My_centrex)
>((LftRght+1)/2*this_centrex + (-LftRght+1)/2*My_centrex))
or ((LatVert = -1)
and ((LftRght+1)/2*this_centrey + (-LftRght+1)/2*My_centrey)
>((-LftRght+1)/2*this_centrey + (LftRght+1)/2*My_centrey))
{
Item%A_Index% := "" ; wrong side of my window so discard
continue
}
index++
Item%A_Index% := ""

If (LatVert = +1) {
EdgeSep := Abs(This_x - My_x + (-LftRght+1)/2*This_w - (LftRght+1)/2*My_w)
CentreSep := Abs(My_centrex-this_centrex) ; Centre-to-centre separation
}
If (LatVert = -1) {
EdgeSep := Abs(This_y - My_y + (LftRght+1)/2*This_h - (-LftRght+1)/2*My_h)
CentreSep := Abs(My_centrey-this_centrey) ; Centre-to-centre separation
}
Area := (this_w * this_h) ; area
Item%index% = %this_Item%%A_Space%%CentreSep%%A_Space%%Area%%A_Space%%EdgeSep% ; store results
}
}
; Select the best candidate
Item:= index ; number of candidates
Dmin := 10000 ; Start high. Will drop to lowest value after loop
Dmax := 0 ; Start at zero. Will rise to higher value after loop
Loop , %Item%
{
this_Item := Item%A_Index%
StringSplit , string , this_Item, %A_Space%
If (Criterion = edge) { ; select on edge-to-edge separation
If (string4 < Dmin) {
Dmin := string4
Item1 := string1
}
Continue
}
If (Criterion = area) { ; select for largest area
If (string3 > Dmax) {
Dmax := string3
Item1 := string1
}
Continue
}
If (Criterion = centre) { ; select for nearest centre
If (string2 < Dmin) {
Dmin := string2
Item1 := string1
}
Continue
}
}
Return, Item1 ; returns id of chosen window
}
;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;
Sort:
; Lists other windows on same monitor that have style 0x16CF0000 or 0x14CF0000.
;
WinGet, Item, list,,, Program Manager
index := "" ; count number in selection
Loop, %Item% ; loop thro' all current windows
{
this_Item := Item%A_Index% ; Item1 contains id of first window, etc
WinGet, this_style , style , ahk_id %this_Item% ; get 'Style' of the window
If ((this_Style <> 0x16CF0000) and (this_Style <> 0x14CF0000)) or (this_Item = ID1)
{
Item%A_Index% := ""
continue ; wrong style or is Master window
}
WinGetPos , this_x, this_y, this_w, this_h , ahk_id %this_Item%
this_mon := GetMonitorAt(this_x+this_w/2, this_y+this_h/2) ; which monitor?
If (this_mon <> My_mon)
{
Item%A_Index% := ""
continue ; window is on wrong monitor
}
index++ ; increment pointer
Item%index% = %this_Item%
}
Item:= index ; store revised total of windows
Return

;;;;;;;;;;;;;;;;;;;;;
; Determine which cell the mouse is in. Use a 3x3 grid. Columns are 1,2,3 (top to
; bottom) and rows are 1,2,3 (left to right).

Get_Cell(ByRef Column, ByRef Row)
{
CoordMode, Mouse, Screen
MouseGetPos,X1,Y1,id
WinGet,Win,MinMax,ahk_id %id%
If Win
return
; Get the initial window position and size.
WinGetPos, WinX1, WinY1, WinW, WinH, ahk_id %id%

Div := 3 ; e.g. Div=4 makes outer cells 1/4 as wide as the window.
PW := Abs(WinW/Div)
PH := Abs(WinH/Div)
If ((WinX1 < X1) and (X1 < WinX1 + PW))
Column := 1
If ((WinX1 + PW < X1) and (X1 < WinX1 + WinW - PW))
Column := 2
If ((WinX1 + WinW - PW < X1) and (X1 < WinX1 + WinW))
Column := 3

If ((WinY1 < Y1) and (Y1 < WinY1 + PH))
Row := 1
If ((WinY1 + PH < Y1) and (Y1 < WinY1 + WinH - PH))
Row := 2
If ((WinY1 + WinH - PH < Y1) and (Y1 < WinY1 + WinH))
Row := 3
Return
}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Called when two windows first couple together
Coupled:
Master := ID1 ; Resizer uses this to see if it's dealing with a Master window.
Slave := winid
Coupled := 1
; Promote partner to number two window. Avoids having a third window sandwiched
; between Master and partner. Also makes both windows blink when coupled.
WinActivate , ahk_id %Slave%
WinActivate , ahk_id %Master%
#Persistent
ToolTip , Linking On
SetTimer , TooltipOff , 1000
SoundBeep, 800, 25
SoundBeep, 1200, 25
Return

; Called when two windows uncouple
UnCouple:
If (Coupled = 0)
Return
Master := ""
Slave := ""
Coupled := 0
#Persistent
ToolTip , Linking Off
SetTimer , TooltipOff , 1000
SoundBeep, 1200, 25
SoundBeep, 800, 25
Return

; Clear tooltip
TooltipOff:
SetTimer , TooltipOff , Off
ToolTip
Return
;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Resizer:
; Get the initial mouse position, target window info, monitor limits
CoordMode, Mouse, Screen
MouseGetPos,KDE_X1,KDE_Y1,KDE_id
WinGetPos,KDE_WinX1,KDE_WinY1, DimMx, DimMy,ahk_id %KDE_id%
My_mon := GetMonitorAt(KDE_WinX1+DimMx/2, KDE_WinY1+DimMy/2) ; From WindowPad by Lexikos
Gosub , CalcMonStats ; gets My_monRight, etc
WinActivate , ahk_id %KDE_id%

; Set flag to show window is part of a coupled pair
OneOfPair := (Coupled = 1) and ((KDE_id = Master) or (KDE_id = Slave)) ? 1:0
; Uncouple if either window has been minimised, maximised, or closed.
WinGet,SGone,MinMax,ahk_id %Slave%
WinGet,MGone,MinMax,ahk_id %Master%
If (OneOfPair = 1) and ((SGone <> 0) or (MGone <> 0)) {
OneOfPair := 0
Gosub , UnCouple
}
; Abort if selected window is full-screen or minimised
WinGet,KDE_Win,MinMax,ahk_id %KDE_id%
IfNotEqual , KDE_Win, 0 , Return

; If one of a couple, make sure both are in consecutive z-order.
; Avoids a third window being sandwiched between a coupled pair. Also
; indicates paired state by making both task bars blink when selected by
; this routine.
If (Coupled = 1) and (KDE_id = Master) {
WinActivate , ahk_id %Slave%
WinActivate , ahk_id %Master%
}
If (Coupled = 1) and (KDE_id = Slave) {
WinActivate , ahk_id %Master%
WinActivate , ahk_id %Slave%
}
; If clicking in what was previously the Slave, swap Master and Slave
If (Coupled = 1) and (Slave = KDE_id)
Swap(Master, Slave)

WinGetPos,KDE_P_x,KDE_P_y, DimPx, DimPy,ahk_id %Slave% ; used during limit checks

; Find which of the nine cells in the target window received the click.
Get_Cell(Column,Row)
If (Column=2) and (Row=2) ; Centre cell, so do move without resizing.
{
Loop
{
GetKeyState,KDE_Button,%DragButton% ,P
If KDE_Button = U ; Abort if button was released.
break
MouseGetPos,KDE_X2,KDE_Y2 ; current mouse position.
KDE_X2 -= KDE_X1 ; offset from initial position.
KDE_Y2 -= KDE_Y1
newx := (KDE_WinX1 + KDE_X2) ; Apply offset to window
newy := (KDE_WinY1 + KDE_Y2)

; If Partner exists, stop dragging before it leaves valid monitor
If (OneOfPair = 1)
{
Mon_P := GetMonitorAt(KDE_P_x + KDE_X2+ DimPx/2, KDE_P_y+ KDE_Y2+ DimPy/2, 0)
IfEqual , Mon_P, 0, Continue
WinMove,ahk_id %Slave%,, KDE_P_x + KDE_X2, KDE_P_y + KDE_Y2
}
WinMove,ahk_id %KDE_id%,, newx, newy
}
return
}
Else ; Do resizing
{
; Use the quadrants, North-West, NE, SE, and SW to set the basic
; directions in which the Master window can be dragged. These are
; refined later according to which of the eight peripheral cells was clicked.

WestEast := KDE_X1 < KDE_WinX1+ DimMx/2 ? 1:-1 ; ie, Left-Right
NorthSouth := KDE_Y1 < KDE_WinY1+ DimMy/2 ? 1:-1 ;ie, Up-Down

; Set max width (height) parameter to leave space for Slave if present, and
; Set 'Master Near' flag to show if Master or Slave nearest to top-left of monitor.
MaxDim2x := MaxDimx ; used in limit calculation for Master
MaxDim2y := MaxDimy
If (OneOfPair = 1) and (LatVert = 1) {
MaxDim2x := MaxDimx-MinDimx
MasterNear := (KDE_Winx1+ DimMx/2) > (KDE_P_x + DimPx/2) ? 1:-1
}
If (OneOfPair = 1) and (LatVert = -1) {
MaxDim2y := MaxDimy-MinDimy
MasterNear := (KDE_Winy1+ DimMy/2) > (KDE_P_y + DimPy/2) ? 1:-1
}
Loop
{
GetKeyState,KDE_Button,%DragButton% ,P
If KDE_Button = U ; Abort if button was released.
break
MouseGetPos,KDE_X2,KDE_Y2 ; Get current mouse position.

KDE_X2 -= KDE_X1 ; offset from initial mouse position.
KDE_Y2 -= KDE_Y1
KDE_X1 := (KDE_X2 + KDE_X1) ; Reset initial position for next iteration.
KDE_Y1 := (KDE_Y2 + KDE_Y1)

; Implement drag mode choice (quadrants or cells).
If (DragMode = 1) and (Column=2) ; Forces lateral-only dragging for Col2
KDE_X2 := 0
If (DragMode = 1) and (Row=2) ; Forces vertical-only dragging for Row2
KDE_Y2 := 0

; Calculate shifts based on quadrant
DeltaMX := (WestEast+1)/2*KDE_X2
DeltaMY := (NorthSouth+1)/2*KDE_Y2
DeltaDimMx := -WestEast*KDE_X2
DeltaDimMy := -NorthSouth*KDE_Y2

; Apply limits on maximum size
StretchLimit("y", "NorthSouth")
StretchLimit("x", "WestEast")

KDE_WinX1 += DeltaMX
KDE_WinY1 += DeltaMY
DimMx += DeltaDimMx
DimMy += DeltaDimMy

; Apply limits on minimum size
ShrinkLimit("x", "WestEast")
ShrinkLimit("y", "NorthSouth")

; Calculate dimensions of Slave if it exists
IfNotEqual ,OneOfPair ,0
{
; LatVert previously set to +1 for lateral coupling and -1 for vertical coupling,
If (latvert = 1) { ; lateral coupling
abs := "x"
ord := "y"
}
If (latvert = -1) { ; vertical coupling
abs := "y"
ord := "x"
}
KDE_P_%abs% := KDE_Win%abs%1+ DimM%abs% - MaxDim%abs%*(1+MasterNear)/2
KDE_P_%ord% := KDE_Win%ord%1
DimP%abs% := MaxDim%abs% - DimM%abs%
DimP%ord% := DimM%ord%

; Abort if centre of Slave would be on non-existent monitor (Mon_P = 0)
Mon_P := GetMonitorAt(KDE_P_x+ DimPx/2, KDE_P_y+ DimPy/2, 0)
IfEqual , Mon_P, 0, Continue
WinMove ,ahk_id %Slave%,, KDE_P_x , KDE_P_y, DimPx, DimPy
}
WinMove,ahk_id %KDE_id%,, KDE_WinX1 , KDE_WinY1, DimMx, DimMy
}
return
}
;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;
; Apply maximum width or height limits
StretchLimit(axis, Dir)
{
If (DimM%axis% > MaxDim2%axis%) and (DeltaDimM%axis% >0) {
DeltaM%axis% := -(%Dir%-1)/2*DeltaDimM%axis% +(%Dir%+1)/2*DeltaM%axis%
DeltaDimM%axis% :=0
}
}

; Apply minimum width or height limits
ShrinkLimit(axis, Dir)
{
If (DimM%axis% < MinDim%axis%) and (DeltaDimM%axis% <0) {
DimM%axis% -= DeltaDimM%axis%
KDE_Win%axis%1 += -(%Dir%-1)/2*DeltaDimM%axis%
}
}

; Exchange Master and Slave titles
Swap(ByRef Master, ByRef Slave)
{
temp := Master
Master := Slave
Slave := temp
}
;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;
; Get the index of the monitor containing the specified x and y co-ordinates.
; (From WindowPad by Lexikos)

GetMonitorAt(x, y, default=1)
{
SysGet, m, MonitorCount
; Iterate through all monitors.
Loop, %m%
{ ; Check if the window is on this monitor.
SysGet, Mon, Monitor, %A_Index%
if (x >= MonLeft && x <= MonRight && y >= MonTop && y <= MonBottom)
return A_Index
}

return default
}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; This function is from http://www.autohotkey.com/forum/topic22178.html
IsOverTitleBar(x, y, hWnd) {
SendMessage, 0x84,, (x & 0xFFFF) | (y & 0xFFFF) << 16,, ahk_id %hWnd%
if ErrorLevel in 2,3,8,9,20,21
return true
else
return false
}
;;;;;;;;;;;;;;;;;;;;;;;;


Unambiguous
  • Members
  • 16 posts
  • Last active: Mar 10 2011 06:42 PM
  • Joined: 18 May 2005
Good job with the script, I like the idea and the way that you implement clipping. I am looking for a way to implement this with a couple other hacks I have with my window manager, such as gravity effects on ALT + LBUTTON dragging, and clipping at borders to inhibit movement outside of monitor.

DAT
  • Members
  • 58 posts
  • Last active: Jul 17 2014 08:55 AM
  • Joined: 01 Dec 2008
Thanks, and I'm glad you like it.

Yours is the first and only response to Dock'n Drag in over a year. I thought the post had dropped into a bottomless pit... Even better, I was using it (to compare two scripts side-by-side) at the very moment notification of your post came in.

So thanks, Unambiguous, you've made my day :D

jimbone
  • Guests
  • Last active:
  • Joined: --
i like it and will use it at work. however, im looking for an autohotkey that auto stacks or side by sides windows (vista lingo).. then, your tool will be complete..

Ace Coder
  • Members
  • 362 posts
  • Last active: Mar 13 2012 11:44 PM
  • Joined: 26 Oct 2009
Idk why this script didn't get the attention it deserves but KUDOS!
Great script, i'll be using it frequently i'm sure. Thank you.

DAT
  • Members
  • 58 posts
  • Last active: Jul 17 2014 08:55 AM
  • Joined: 01 Dec 2008
@ Ace Coder

Thanks for the kind remarks :-)

@ jimbone

Glad to know you're using Dock n'Drag. But regarding the enhancements you mention, are you using Lexikos's WindowPad? It does pretty much all I need for manipulating windows (except for D n'D of course ;-)), and it's especially useful when you're using more than one monitor.

Maestr0
  • Members
  • 652 posts
  • Last active: Aug 17 2019 06:07 PM
  • Joined: 18 Oct 2008
wow, very nice work!

I'm just missing a systray icon for verification that the script is running and maybe that a currently active window is linked, other than that, brilliantly done.

MasterFocus
  • Moderators
  • 4323 posts
  • Last active: Jan 28 2016 01:38 AM
  • Joined: 08 Apr 2009

I'm just missing a systray icon for verification that the script is running

Remove #NoTrayIcon.

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Antonio França -- git.io -- github.com -- ahk4.net -- sites.google.com -- ahkscript.org

Member of the AHK community since 08/Apr/2009. Moderator since mid-2012.


Maestr0
  • Members
  • 652 posts
  • Last active: Aug 17 2019 06:07 PM
  • Joined: 18 Oct 2008

I'm just missing a systray icon for verification that the script is running

Remove #NoTrayIcon.


heh, thanks, I knew that :D

Skrell
  • Guests
  • Last active:
  • Joined: --
i have ALWAYS wanted this functionality..but i cannot get the docking feature to work :( Is it possible this script won't work with Autohotkey_L ?

Skrell
  • Guests
  • Last active:
  • Joined: --
nevermind i got it!

Skrell
  • Guests
  • Last active:
  • Joined: --
the ONLY thing i don't like right now is that the 2 docked windows have to be the same size. I wish they windows would stay their original sizes and just dock. :)

hoppfrosch
  • Members
  • 399 posts
  • Last active: Feb 26 2016 05:31 AM
  • Joined: 25 Jan 2006

the ONLY thing i don't like right now is that the 2 docked windows have to be the same size. I wish they windows would stay their original sizes and just dock. :)


Also have a look at DockA-Module in Majkinetors marvelous Forms Framework ...

_________________________

;     (.)~(.)   
;    (-------)                                    
;---ooO-----Ooo---------------------------------------------------
;    Hoppfrosch  - AHK 1.1.00 Unicode 32bit on Win7 Ultimate
;-----------------------------------------------------------------                        
;    ( )   ( )                            
;    /|\   /|\


sumon
  • Moderators
  • 1317 posts
  • Last active: Dec 05 2016 10:14 PM
  • Joined: 18 May 2010
I would also like to get this working, the furthest I got (after modifying the modifier, since CapsLock seems unintuitive to use as a modifier, to me) was making !RightClick (dock) work, it played a sound and moved two windows paralelly. When moving a window afterward, the other didn't follow.

I also got an error message shortly after that "C:\Program Files\GPSoftware\Directory Opus\dopusrt.exe" couldn't be run, obviously since I don't have that. Did I miss any requirement?

AHK_L uni, Win7 x64.

Skrell
  • Guests
  • Last active:
  • Joined: --
how does the Forms Framework help me?