Gridded geometry in Windows doesn't restrict resizing - tcl

Gridded toplevel windows in Windows XP doesn't seem to restrict the user from resizing in multiples of a number. It works in X, but not in Windows. I can resize to any pixel size.
Doesn't the Windows windows manager support it? Can i do it manually, maybe by binding some commands to the resize event of the toplevel?

You're correct that it doesn't really work on Windows (this is also true for Mac OS X/Aqua) and it is because the window manager itself doesn't support the feature. You have to synthesize it with some scripts. However the real complication is that a typical gridded window is more than just the one gridded window; there's some extra space around it which can make the updated size go into the next grid size up and that makes the window expand a bit and resize again… and again and again…
Thus, we need a two-stage initialization, first measuring the real size of the extra space we have to allow for and only then applying the enforcement code.
package require Tk
proc initGrid {window w h} {
# Do nothing for subwindows
if {$window ne [winfo toplevel $window]} return
lassign [wm grid $window] xcount ycount xstep ystep
set wExtra [expr {$w - $xcount*$xstep}]
set hExtra [expr {$h - $ycount*$ystep}]
bind $window <Configure> [list resizeGrid $wExtra $hExtra %W %w %h]
}
proc resizeGrid {wExtra hExtra window w h} {
# Do nothing for subwindows
if {$window ne [winfo toplevel $window]} return
lassign [wm grid $window] xcount ycount xstep ystep
if {$w-$wExtra != $xcount*$xstep || $h-$hExtra != $ycount*$ystep} {
set w [expr {($w - $wExtra)/$xstep}]
set h [expr {($h - $hExtra)/$ystep}]
wm grid $window $w $h $xstep $ystep
}
}
pack [text .t -setgrid 1] -fill both -expand 1
bind . <Configure> {initGrid %W %w %h}
As you can see, this isn't particularly simple to get right!

Related

How to center tcl/tk window respecting workarea size?

I trying to place window at center on my desktop. But I could not find the way how to find height/width of work area (i.e. respect taskbar or other system's areas).
My first attempt was:
wm withdraw $window
update idletasks
set width [winfo reqwidth $window]
set height [winfo reqheight $window]
set x [expr { ([winfo screenwidth $window] - $width) / 2}]
set y [expr { ([winfo screenheight $window] - $height) / 2}]
wm geometry $window ${width}x${height}+${x}+${y}
wm deiconify $window
But it doesn't work correctly because winfo screenwidth/height returns values based on screen resolution, but not on desktop work area.
My another attempt was from https://stackoverflow.com/a/36387629/1980049
wm withdraw $window
update idletasks
set width [winfo reqwidth $window]
set height [winfo reqheight $window]
toplevel [set testWin ".__test_screen_size__[incr UID]"]
wm withdraw $testWin
wm state $testWin zoomed
update idletasks
set x [expr { ([winfo width $testWin] - $width) / 2 }]
set y [expr { ([winfo height $testWin] - $height) / 2 }]
destroy $testWin
wm geometry $window ${width}x${height}+${x}+${y}
wm deiconify $window
It works as expected and correct x/y values were obtained. But the $testWin blinked on screen, so this method also not suitable. Without update idletasks it doesn't work also.
I think you can use:
winfo screenmmheight .
Returns a decimal string giving the height of window's screen, in millimeters.
winfo screenmmwidth .
Returns a decimal string giving the width of window's screen, in millimeters.
package require Tk
set window_1_width 350 ; set window_1_height 250
set x_1 [ expr {([ winfo vrootwidth . ] - $window_1_width ) / 2 }]
set y_1 [ expr {([ winfo vrootheight . ] - $window_1_height ) / 2 }]
wm geometry . ${window_1_width}x${window_1_height}+$x_1+$y_1
tested on windows 7  tcl/tk 8.6

TK "grid" and "grid remove"

I have TK application with several frames containing different widgets.
To hide a frame (and its content) I use grid remove $frame".
How can I achieve the resizing of the TK toplevel to the minimum needed geometry?
grid [ttk::frame .f ] -sticky nwse
grid [ttk::frame .f.f] -sticky nwse
foreach el {a b c d e f} {
grid [label .f.f.$el -text "ELEM $el" ] -sticky nwse
}
update
after 2500
grid remove .f.f
What is necessary after "grid remove .f.f" to have a toplevel in a size without the labels?
While grid remove takes the widget out of the grid, it doesn't forget the information about how to lay that widget out (in case you want to add it back in at the same location, an occasionally useful operation). To really forget it entirely, you should use grid forget which removes the widget and drops its layout information.
What about:
wm geometry $toplevel {}
Regards,
jima
I am suprised wht wm geometry $toplevel {}. It redraws. Great.
But it does not solve my issue, but understand now the situation.
If a the toplevelFrame (the first and only frame in a toplevel) is removed from the grid, the toplevel is "empty" and not redrawn.
If the toplevel as at least to widgets/frames which are in the grid I can remove one and the redrawing is done.
In my example .e is a helper frame. No size no content.
package require Tk
grid [frame .e ]
grid [frame .f ] -sticky nwse
foreach el {a b c e f g h} {
grid [label .f.$el -text "1ELEM $el" ] -sticky nwse
}
update
after 1000
grid remove .f

Tcl/tk - Get window height and width and set relative text height in grid

I want to create a window with two text boxes one on top of another with first occupying 25% of height and next occupying 75% of height.
I tried to calculate relative height/width of toplevel win and pass into text command but didn't work (I am guessing because the units of dimension returned by wm geometry is not the same as when passed to text command)
Following is my code:
toplevel .t
wm geometry .t 1500x800+10+10
update
proc topAspect {args} {
regexp {(\d+)} $args -> relAspect
regexp {([^\d|%]+)} $args -> aspect
regexp {(.*)x(.*)[+-](.*)[+-](.*)} [wm geometry .t] -> width height x y
puts "width->$width height->$height x->$x y->$y"
switch -regexp [string tolower $aspect] {
x {
return [expr $x + $relAspect]
}
y {
return [expr $y + $relAspect]
}
w {
return [expr $width * $relAspect / 100]
}
h {
return [expr $height * $relAspect / 100]
}
default {
log::log error "Unsupported relative aspect $aspect cannot be determined for top level window"
}
}
}
text .t.text1 -height [topAspect -width 25%] -width [topAspect -width 99%]
grid .t.text1 -sticky news
text .t.text2 -height [topAspect -width 75%] -width [topAspect -width 99%]
grid .t.text2 -sticky news
When I tried following - it did give me some decent GUI:
text .t.text1 -height 20 -width [topAspect -width 99%]
grid .t.text1 -sticky news
text .t.text2 -height 20 -width [topAspect -width 99%]
grid .t.text2 -sticky news
But i want to use the relative options. How to make it work?
The easiest way to solve this is to use the grid geometry manager with weights in the right ratio and a uniform group. It will even work fine when you resize the window; Tk knows the policy itself and maintains it for you. (Internally, grid is a fairly sophisticated constraint solver; you can do some really complicated stuff with it.)
toplevel .t
grid [text .t.text1 -bg red] -sticky news
grid [text .t.text2 -bg green] -sticky news
# The group name is just an arbitrary non-empty string.
# So long as it is the same on the two rows it will work as desired.
# The weights give a ratio of 1:3, i.e., 25% to one and 75% to the other.
grid rowconfigure .t .t.text1 -weight 1 -uniform group1
grid rowconfigure .t .t.text2 -weight 3 -uniform group1
(If you're using Tk 8.5, you'll need to specify the rows to rowconfigure by number instead of the often-more-convenient name of a widget in the row.)
Yes, the -height and -width options for the text widget are given in character units, not screen units. You can fix that by further dividing by font width and height (I set them to constant values below). Remember that this is integer division!
Oooh, all those regexes… I’ve cleaned up a bit, you can take it or leave it.
proc topAspect {aspect relAspect} {
set relAspect [string trimright $relAspect %]
scan [wm geometry .t] "%dx%d%d%d" width height x y
set fontWidth 15
set fontHeight 15
switch -regexp [string tolower $aspect] {
x {
return [expr {$x + $relAspect}]
}
y {
return [expr {$y + $relAspect}]
}
w {
return [expr {($width * $relAspect / 100) / $fontWidth}]
}
h {
return [expr {($height * $relAspect / 100) / $fontHeight}]
}
default {
log::log error "Unsupported relative aspect $aspect cannot be determined for top level window"
}
}
}
Also, you used -width as an argument to topAspect for both -height and -width: I presume that was a mistake.
text .t.text1 -height [topAspect -height 25%] -width [topAspect -width 99%]
grid .t.text1 -sticky news
text .t.text2 -height [topAspect -height 75%] -width [topAspect -width 99%]
grid .t.text2 -sticky news
Otherwise, I recommend Donal Fellows’s solution.
Documentation:
* (operator),
+ (operator),
/ (operator),
expr,
for,
grid,
proc,
return,
scan,
set,
string,
switch,
text (widget),
wm,
Syntax of Tcl regular expressions
Place worked best in this case - even on resizing the following held out the proportions well:
place .t.text1 -in .t -relheight .25 -relwidth .98 -relx .003 -rely .003
place .t.text2 -in .t -relheight .75 -relwidth .98 -relx .003 -rely .254
Is there any pitfall that anyone sees in this approach as compared to grid.
Thank you

tcl formatting floating point with fixed precision

# the unit of period is picosecond
set period 625000.0
set period_sec [format %3.6g [expr $period * 1e-12]]
puts $period_sec
result: 6.25e-07
Is there a way to force tcl to get results like 625e-09
Assuming that you want to format it to the nearest exponent, you could use a proc which formats it like this:
proc fix_sci {n} {
# Not a sci-fmt number with negative exponent
if {![string match "*e-*" $n]} {return $n}
# The set of exponents
set a 9
set b 12
# Grab the number (I called it 'front') and the exponent (called 'exp')
regexp -- {(-?[0-9.]+)e-0*([0-9]+)} $n - front exp
# Check which set of exponent is closer to the exponent of the number
if {[expr {abs($exp-$a)}] < [expr {abs($exp-$b)}]} {
# If it's the first, get the difference and adjust 'front'
set dif [expr {$exp-$a}]
set front [expr {$front/(10.0**$dif)}]
set exp $a
} else {
# If it's the first, get the difference and adjust 'front'
set dif [expr {$exp-$b}]
set front [expr {$front/(10.0**$dif)}]
set exp $b
}
# Return the formatted numbers, front in max 3 digits and exponent in 2 digits
return [format %3ge-%.2d $front $exp]
}
Note that your original code returns 6.25e-007 (3 digits in the exponent).
If you need to change the rule or rounding the exponent, you will have to change the if part (i.e. [expr {abs($exp-$a)}] < [expr {abs($exp-$b)}]). For example $exp >= $a could be used to format if the exponent is 9 or below.
ideone demo of above code for 'closest' exponent.
For Tcl versions before 8.5, use pow(10.0,$dif) instead of 10.0**$dif
I do not think there is anything in the format command that will help you directly. However, if you consider a slight variation on the format code, then it may be a lot easier to get what you want (with a bit of string manipulation):
format %#3.6g $number
gives a number like: 6.25000e-007
This can be parsed more easily:
Extract the exponent
Determine the number of positions to shift the decimal point
Shift it and replace the exponent
It is not entirely straightforward, I am afraid, but it should be doable. Wiki page http://wiki.tcl.tk/5000 may give you some inspiration.

Canvas widget has wrong size after resize

I have a canvas widget which shows a gradient. This is done by drawing lines from its top to bottom each with a slightly different color. To achieve this, in the function that draws the line I check the height of the canvas and draw lines according to it. The problem is, that the first time its drawn, or when the widget is resized (when it's resized, I call the drawing function) the result I get from the command winfo height $legendCanvas is wrong and the drawing is bad, only when I recall the function again, it gets the right value and the drawing results are good. I've tried adding update idletasks at the start of the method, it doesn't work.
The relevant canvas is called legendCanvas
itcl::body siReportAttackersMatrix::setThreshold {{val ""}} {
update idletasks
# some unrelated code here
# ...
#redraw the legend
$legendCanvas delete line all
set range [expr {$maxVal*1.0-$minVal}]
set step [expr {$range/[winfo height $legendCanvas]}]
for {set y 0} {$y < [winfo height $legendCanvas]} {incr y} {
# some unrelated code that calculated the color
set id [$legendCanvas create line 0 $y [winfo width $legendCanvas] $y -fill $color]
}
set textX [expr {[winfo width $legendCanvas]/2}]
set id [$legendCanvas create text $textX 0 -anchor n -text [expr {int($maxVal * 1000)}]]
set id [$legendCanvas create text $textX [winfo height $legendCanvas] -anchor s -text [expr {int($minVal * 1000)}]]
foreach fraction [list 2 4 [expr {4/3.0}]] {
set textY [expr {int([winfo height $legendCanvas]*1.0/$fraction)}]
set textValue [expr {int(($maxVal-$minVal)*(1-1.0/$fraction)*1000)}]
set id [$legendCanvas create text $textX $textY -anchor center -text $textValue]
}
}
in order to conserve space I've removed code that is irellevent to the problem, like calculating the color, some more functions that the method does and bindings on the different items in the canvas
Screen pics of the results:
On creation (on the left), After recalling the method(on the right):
On resize (on the left), After recalling the method (on the right):
The simplest way of fixing this is to recompute the gradient whenever that canvas widget receives a <Configure> event. In particular, the %h and %w substitutions in the <Configure> event tell you what the size of the widget is being set to, though the basic Tk infrastructure will also save those values into the widget record (where winfo height and winfo width can retrieve them).
# Something like this; you might want to tweak the binding
bind $legendCanvas <Configure> { doRescale %W %w %h }
You're advised to have a procedure (or method) that just handles this; other operations that require the rescaling (such as the initial setup code) can just call it as necessary.