[Keith Vetter] : 2008-04-09 : Every have a problem of trying to figure out why [grid] layout is not looking right? This little package is a tool to help you debug grid layout problems. You invoke it with '''::GridDebugger::Dialog ?master?'''. It puts up a dialog and lets you choose a master widget which has gridded slaves. It then displays a mockup of cells corresponding to the gridded layout of the master (not to scale). On this mockup you can: * mouse into a cell to display the slave widget for that cell along with row span, column span and sticky. * click a cell to toggle highlighting '''on the master''' the actual grid bounding box for that cell. How it does the highlighting is kind of interesting. The command '''grid bbox ...''' yields size and position info. I then create a label of that size which I [place] at the correct location. ---- ====== ##+########################################################################## # # GridDebugger.tcl -- Dialog to investigate grid layout # by Keith Vetter, April 2008 # package require Tcl 8.5 package require Tk namespace eval ::GridDebugger { variable S ;# Our state variable D ;# Display copy array unset S array unset D set S(W) .__gaidDialog set S(last) "" set S(colors) [list red orange yellow green blue cyan purple violet] set D(master) "" set D(size) "? x ?" image create photo ::img::griddebugger -width 0 -height 0 } ##+########################################################################## # # ::GridDebugger::Dialog -- our debugging dialog # proc ::GridDebugger::Dialog {{master ""}} { variable S set W $S(W) set S(C) $W.c destroy $W toplevel $W wm title $W "Grid Debugger" wm protocol $W WM_DELETE_WINDOW ::GridDebugger::Exit label $W.lm -text "Master:" -anchor e $W.lm configure -font "[font actual [$W.lm cget -font]] -weight bold" set font [$W.lm cget -font] ::ttk::combobox $W.master \ -state readonly -textvariable ::GridDebugger::D(master) \ -postcommand ::GridDebugger::PostCommand \ -width 20 bind $W.master <> ::GridDebugger::NewMaster label $W.lsize -text "Size:" -anchor e -font $font label $W.size -textvariable ::GridDebugger::D(size) -anchor c -font $font grid $W.lm $W.master grid $W.lsize $W.size -sticky ew frame $S(W).f -bd 2 -relief ridge canvas $S(C) -width 500 -height 300 -highlightthickness 0 label $S(W).who -textvariable ::GridDebugger::D(who) -bd 2 -relief ridge \ -font $font pack $S(W).who -in $S(W).f -side bottom -fill x pack $S(C) -in $S(W).f -side left -fill both -expand 1 bind $S(C) [list ::GridDebugger::ReCenter %W %h %w] ::ttk::button $S(W).exit -text Exit -command ::GridDebugger::Exit grid $S(W).f - - -sticky news -padx 5 grid $S(W).exit - - -pady 10 grid columnconfigure $W 2 -weight 1 grid rowconfigure $W 2 -weight 1 ::GridDebugger::NewMaster $master } ##+########################################################################## # # Recenter -- keeps 0,0 at the center of the canvas during resizing # proc ::GridDebugger::ReCenter {W h w} { ;# Called by configure event set h2 [expr {$h / 2}] ; set w2 [expr {$w / 2}] $W config -scrollregion [list -$w2 -$h2 $w2 $h2] ::GridDebugger::Resize } ##+########################################################################## # # ::GridDebugger::PostCommand -- fills in our combobox with all # widgets that have gridded slaves # proc ::GridDebugger::PostCommand {args} { variable S $S(W).master config -values [::GridDebugger::AllGridMasters $S(W)] } ##+########################################################################## # # ::GridDebugger::GridBbox -- highlights a grid box on master by 'place'ing # a label on top of it. # proc ::GridDebugger::GridBbox {master row col {clr yellow}} { foreach {x y w h} [grid bbox $master $col $row] break set top [winfo toplevel $master] set tag "$row,$col" set W [expr {$top eq "." ? ".__gaid$tag" : "$top.__gaid$tag"}] destroy $W label $W -image ::img::griddebugger -width $w -height $h -bg $clr $W config -padx 0 -pady 0 -bd 0 place $W -in $master -x $x -y $y -anchor nw bind $W <1> [list ::GridDebugger::UnClick $W $row $col] return $W } ##+########################################################################## # # ::GridDebugger::NewMaster -- called when a new master widget is selected # proc ::GridDebugger::NewMaster {{new ""}} { variable S variable D if {$new eq ""} { set new $D(master) } if {! [winfo exists $new]} { ::GridDebugger::Cleanup; return } foreach {cols rows} [grid size $new] break set this "$new/$rows/$cols" if {$this eq $S(last)} return ::GridDebugger::Cleanup ;# Remove old stuff if {$rows == 0 || $cols == 0} {return} set D(master) $new set S(master) $new set D(size) "$rows x $cols" ::GridDebugger::Checkerboard $S(C) $rows $cols set S(last) $this } ##+########################################################################## # # ::GridDebugger::Checkerboard -- draws our mockup of the gridded master # proc ::GridDebugger::Checkerboard {c rows cols} { $c delete all if {[winfo ismapped $c]} { set maxX [winfo width $c] ; set maxY [winfo height $c] } else { set maxX [winfo reqwidth $c] ; set maxY [winfo reqheight $c] } set dx [expr {($maxX-10) / $cols}] set dy [expr {($maxY-10) / $rows}] set dx [set dy [expr {min($dx,$dy)}]] set row2 [expr {$rows/2.0}] set col2 [expr {$cols/2.0}] for {set row 0} {$row < $rows} {incr row} { for {set col 0} {$col < $cols} {incr col} { set x0 [expr {($col - $col2) * $dx}] set y0 [expr {($row - $row2) * $dy}] set x1 [expr {$x0 + $dx}] set y1 [expr {$y0 + $dy}] set tag cell,$row,$col $c create rect $x0 $y0 $x1 $y1 -tag $tag -fill white -outline black $c bind $tag <1> [list ::GridDebugger::Click $c $tag] $c bind $tag [list ::GridDebugger::GetSlaveInfo $row $col] } } } ##+########################################################################## # # ::GridDebugger::Resize -- handles resizing the window # proc ::GridDebugger::Resize {} { variable S set old $S(master) ::GridDebugger::Cleanup ;# Blow everything away ::GridDebugger::NewMaster $old } ##+########################################################################## # # ::GridDebugger::Click -- called when clicking on a cell in our # checkerboard. Toggles the cell and toggles highlighting the grid # cell on the master. # proc ::GridDebugger::Click {C tag} { variable S if {! [winfo exists $S(master)] || ! [winfo ismapped $S(master)]} { ::GridDebugger::Cleanup return } foreach {. row col} [split $tag ","] break set old [$C itemcget $tag -fill] if {$old eq "white"} { ;# Turn on set S(colors) [lassign $S(colors) clr] lappend S(colors) $clr $C itemconfigure $tag -fill $clr set w [::GridDebugger::GridBbox $S(master) $row $col $clr] set S(grid,$row,$col) $w } else { $C itemconfigure $tag -fill white destroy $S(grid,$row,$col) unset S(grid,$row,$col) } } ##+########################################################################## # # ::GridDebugger::UnClick -- forces a checkerboard cell to be off # proc ::GridDebugger::UnClick {W row col} { variable S destroy $W set tag cell,$row,$col $S(C) itemconfigure $tag -fill white unset S(grid,$row,$col) } ##+########################################################################## # # ::GridDebugger::AllGridMasters -- returns all widgets w/ gridded slaves # proc ::GridDebugger::AllGridMasters {{exclude ""}} { variable S set q . set all {} while {$q ne {}} { set q [lassign $q who] if {! [winfo ismapped $who]} continue ;# Not visible if {$who in $exclude} continue ;# Probably our debugger dialog lappend q {*}[winfo child $who] if {[grid slaves $who] ne {}} { lappend all $who } } return $all } ##+########################################################################## # # ::GridDebugger::Cleanup -- returns everything back to normal # proc ::GridDebugger::Cleanup {} { variable S variable D foreach arr [array names S grid,*] { foreach {. row col} [split $arr ","] break ::GridDebugger::UnClick $S($arr) $row $col } $S(C) delete all set D(size) "? x ?" set D(master) "" set S(master) "" set S(last) "" } ##+########################################################################## # # ::GridDebugger::Exit -- cleans up and exits our dialog # proc ::GridDebugger::Exit {} { variable S ::GridDebugger::Cleanup destroy $S(W) } ##+########################################################################## # # ::GridDebugger::GetSlaveInfo -- displays info about # the slave at row/col. # proc ::GridDebugger::GetSlaveInfo {row col} { variable S variable D set D(who) [grid slaves $S(master) -row $row -column $col] if {$D(who) ne ""} { array set info [grid info $D(who)] append D(who) " : $info(-rowspan)/$info(-columnspan) $info(-sticky)" } else { set D(who) "" } } ################################################################ # # Demo code # toplevel .top wm geom . +10+10 wm geom .top +300+10 for {set row 0} {$row < 3} {incr row} { for {set col 0} {$col < 6} {incr col} { set w .l$row$col label $w -text "$row,$col" -bd 1 -relief solid -width 5 grid $w -row $row -column $col } } label .top.a1 -text "Three columns" -bd 2 -relief ridge label .top.b1 -text "Three\nrows" -bd 2 -relief ridge label .top.b2 -text "ABC" -bd 2 -relief ridge label .top.b3 -text "ABC" -bd 2 -relief ridge label .top.c3 -text "XYZ" -bd 2 -relief ridge label .top.d2 -text "MNO" -bd 2 -relief ridge label .top.d3 -text "MNO" -bd 2 -relief ridge label .top.e1 -text "Three columns" -bd 2 -relief ridge grid .top.a1 - - -sticky ew grid .top.b1 .top.b2 .top.b3 -sticky ns grid ^ x .top.c3 grid ^ .top.d2 .top.d3 grid .top.e1 - - ::GridDebugger::Dialog .top return ====== <> Debugging | Dev. Tools | GUI