#I wish this were standard...
proc setlist {vnames vvals} {
set i -1
foreach n $vnames {
uplevel 1 [list set $n [lindex $vvals [incr i]]]
}
return [lrange $vvals 0 $i]
}
#Attempt to get the *real* geometry of a window
proc true_geom {w} {
update
catch {
if {[regexp -- {-geometry +([0-9x+-]+)} [exec xwininfo -id [wm frame $w]] - geom]} {
return $geom
}
}
#attempt to guess decoration sizes if xwininfo call fails...
setlist {dx dy x0 y0} [parse_geom [wm geom $w]]
set rx [winfo rootx $w] ; set ry [winfo rooty $w]
set left [expr $rx - $x0] ; set top [expr $ry - $y0]
#*assume* that the bottom and right edges match the left edge in size
return [expr $dx + 2*$left]x[expr $dy + $top + $left]+[expr $x0 - $left]+[expr $y0 - $top]
}
#extract geometry info from a geometry string
proc parse_geom {geom} {
set screenheight [winfo screenheight .]
set screenwidth [winfo screenwidth .]
regexp {^([0-9]+)x([0-9]+)([+-])([+-]?[0-9]+)([+-])([+-]?[0-9]+)} $geom - dx dy xs xpos ys ypos
if {$xs == {-}} {
set xpos [expr $screenwidth - $xpos]
}
if {$ys == {-}} {
set ypos [expr $screenwidth - $ypos]
}
return [list $dx $dy $xpos $ypos]
}
#adjust t's geometry so that tside fits against the windows in args
proc adjust_geom {t tside args} {
update
foreach w [linsert $args 0 $t] {
set wm_geom [wm geom $w] ; set winfo_geom [winfo geom $w]
#wm geom $w $wm_geom
#if {$wm_geom != $winfo_geom} { wm geom $w $winfo_geom } ;#force internal geometry update
setlist {dx dy xpos ypos} [parse_geom [wm geometry $w]]
set x0 [expr $xpos] ; set x1 [expr $x0 + $dx]
set y0 [expr $ypos] ; set y1 [expr $y0 + $dy]
set geom($w) [list $x0 $y0 $x1 $y1 $dx $dy]
#calculate decoration sizes
setlist {dX dY Xpos Ypos} [parse_geom [true_geom $w]]
set left [expr $x0 - $Xpos]
set right [expr ($Xpos + $dX) - $x1]
set top [expr $y0 - $Ypos]
set bottom [expr ($Ypos + $dY) - $y1]
lappend geom($w) $left $top $right $bottom
}
setlist {gx0 gy0 gx1 gy1 dx dy gleft gtop gright gbottom} $geom([lindex $args 0])
foreach w [lrange $args 1 end] {
setlist {x0 y0 x1 y1 dx dy left top right bottom} $geom($w)
if {$x0 < $gx0} { set gx0 $x0 }
if {$y0 < $gy0} { set gy0 $y0 }
if {$x1 > $gx1} { set gx1 $x1 }
if {$y1 > $gy1} { set gy1 $y1 }
if {$left > $gleft} {set gleft $left}
if {$top > $gtop} {set gtop $top}
if {$right > $gright} {set gright $right}
if {$bottom > $gbottom} {set gbottom $bottom}
}
set gdx [expr $gx1 - $gx0] ; set gdy [expr $gy1 - $gy0]
setlist {tx0 ty0 tx1 ty1 tdx tdy tleft ttop tright tbottom} $geom($t)
switch -regexp $tside {
n|N|t|T {
wm geometry $t [set gdx]x$tdy+[set gx0]+[expr $gy1 + $ttop + $gbottom]
}
s|S|b|B {
wm geometry $t [set gdx]x$tdy+[set gx0]+[expr $gy0 - $tdy - $tbottom + $gtop]
}
e|E|r|R {
wm geometry $t [set tdx]x$gdy+[expr $gx0 - $tdx - $tright + $gleft]+$gy0
}
w|W|l|L {
wm geometry $t [set tdx]x$gdy+[expr $gx1 + $tleft + $gright]+$gy0
}
}
update
}
#the config event handler
proc winconfig {w} {
#only respond to first of a Configure sequence...
bind $w <Configure> {}
update
set geom [wm geom $w]
if {$::Config($w) != $geom} {
puts "$w - $geom"
setlist {ldx ldy lxpos lypos} [parse_geom $::Config($w)]
setlist {dx dy xpos ypos} [parse_geom $geom]
#ignore moves outside of the visible screen, as they are probably
#window manager related (e.g. virtual screen changes)
if {$xpos != $lxpos || $ypos != $lypos} {
if {($xpos + $dx) < 0 || $xpos > [winfo screenwidth $w] ||
($ypos + $dy) < 0 || $ypos > [winfo screenheight $w]} {
bind $w <Configure> "winconfig $w"
return
}
}
if {$ldx == $dx && $ldy == $dy} {
win_move $w [expr $xpos - $lxpos] [expr $ypos - $lypos]
} else {
set lx1 [expr $lxpos + $ldx] ; set ly1 [expr $lypos + $ldy]
set x1 [expr $xpos + $dx] ; set y1 [expr $ypos + $dy]
win_resize $w [expr $xpos - $lxpos] [expr $ypos - $lypos] [expr $x1 - $lx1] [expr $y1 - $ly1]
}
set ::Config($w) $geom
}
#re-bind Configure
bind $w <Configure> "winconfig $w"
update
}
#move a window and its group
proc win_move {w deltax deltay} {
puts "moved $w - $deltax $deltay"
if {[info exists ::MoveGroup($w)]} {
foreach w $::MoveGroup($w) {
set conf [bind $w <Configure>]
bind $w <Configure> {}
setlist {dx dy xpos ypos} [parse_geom [wm geom $w]]
incr xpos $deltax ; incr ypos $deltay
wm geom $w [set dx]x$dy+$xpos+$ypos
bind $w <Configure> $conf
}
}
}
#resize a window and its group
proc win_resize {w dx0 dy0 dx1 dy1} {
puts "resized $w - $dx0 $dy0 $dx1 $dy1"
if {$dx0 != 0} {
if {[info exists ::ResizeGroup(w\ $w)]} {
foreach {ts tw} $::ResizeGroup(w\ $w) {
if {![info exists resize($tw)]} {set resize($tw) {0 0 0 0}}
if {$ts == {e}} {set idx 2} else {set idx 0}
set resize($tw) [lreplace $resize($tw) $idx $idx $dx0]
}
}
}
if {$dx1 != 0} {
if {[info exists ::ResizeGroup(e\ $w)]} {
foreach {ts tw} $::ResizeGroup(e\ $w) {
if {![info exists resize($tw)]} {set resize($tw) {0 0 0 0}}
if {$ts == {e}} {set idx 2} else {set idx 0}
set resize($tw) [lreplace $resize($tw) $idx $idx $dx1]
}
}
}
if {$dy0 != 0} {
if {[info exists ::ResizeGroup(n\ $w)]} {
foreach {ts tw} $::ResizeGroup(n\ $w) {
if {![info exists resize($tw)]} {set resize($tw) {0 0 0 0}}
if {$ts == {n}} {set idx 1} else {set idx 3}
set resize($tw) [lreplace $resize($tw) $idx $idx $dy0]
}
}
}
if {$dy1 != 0} {
if {[info exists ::ResizeGroup(s\ $w)]} {
foreach {ts tw} $::ResizeGroup(s\ $w) {
if {![info exists resize($tw)]} {set resize($tw) {0 0 0 0}}
if {$ts == {n}} {set idx 1} else {set idx 3}
set resize($tw) [lreplace $resize($tw) $idx $idx $dy1]
}
}
}
foreach tw [array names resize] {
setlist {twx0 twy0 twx1 twy1} $resize($tw)
set conf [bind $w <Configure>]
bind $w <Configure> {}
setlist {dx dy x0 y0} [parse_geom [wm geom $tw]]
set x1 [expr $x0 + $dx + $twx1] ; set y1 [expr $y0 + $dy + $twy1]
incr x0 $twx0 ; incr y0 $twy0
wm geom $tw [expr $x1 - $x0]x[expr $y1 - $y0]+$x0+$y0
bind $w <Configure> $conf
}
}
#an example
proc example {} {
catch {destroy .a .b .c}
set a [toplevel .a -bg blue]
set b [toplevel .b -height 75 -bg yellow]
set c [toplevel .c -width 75 -bg orange]
adjust_geom $b N $a
adjust_geom $c W $a $b
foreach w {.a .b .c} {
set ::Config($w) [wm geom $w]
}
bind .a <Configure> "::winconfig .a"
set ::MoveGroup(.a) {.b .c}
set ::ResizeGroup(n\ .a) {n .c}
set ::ResizeGroup(s\ .a) {n .b s .b s .c}
set ::ResizeGroup(e\ .a) {e .c w .c e .b}
set ::ResizeGroup(w\ .a) {w .b}
}It works well under Windows XP, and it works okay under some UNIX window manager's too. The information that is returned from a window manager can be quite flaky, unfortunately.tje - August 25, 2003FW: As for applications of this idea, with a little modification it could be applied to windows that "stick" to one another but can also be separated. The most aggressive use of this approach I've seen is the [Winamp] [1] / [xmms] [2] interface.[xmav000]: I was looking for a solution that goes a bit deeper. The windows "docking" as FW descriped would be one part. I would like to have several chat windows that usually have a buddy list and some other frames. The feature I would like to provide is to minimize the windows down to the frames needed for the chat (e.g. text + entry) and if i have several of those minimized chats I would like them to be able to dock together. just like in winamp as FW mentioned. I think the titlebar should not be displayed or at least in a group of windows only one of them should have a title. I think there is a lot to do to make it possible to create something like winamp, or?

