Canvas widget has wrong size after resize - tcl

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.

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

Updating parameters in a function being called by Octave's fsolve

I am working on modeling the motion of a single actuated leg in Octave. The leg has 3 points: a stationary hip (point A), a foot (point B) that moves along a known path, and a knee (point C) whose location and angle I am trying to solve for.
Using the code below I can successfully solve for the knee's XYZ position and relevant angles for a single value of the parameters s0 and Theta_H.
Now I want to be able to loop through multiple s0 and Theta_H values and run the solver. My problem is that I can't figure out how to pass new values for those variables into the equations function.
The reason this is tricky is that the function format necessary to use Octave's fsolve prevents entering inputs other than the unknowns into the function. I've tried updating a global variable as an indexer but to do that I would need to clear all workspace variables which causes other problems.
Any ideas on how to update the parameters in this function while still being able to input it into fsolve would be really appreciated!
The code below calls the solver:
global AC = 150; % length of the thigh limb
global CB = 150; % length of the shin limb
global lspan = 75; % width span of the foot touch down wrt the hip
global bob = 10; % height of the hip joint off the ground during a step
inits = [ .75; 2.35; 37; 0; 125]; % initial guesses at horizontal step position
% x(1): hip joint - guessing a 45 deg (.75 rad) angle for hip joint
% x(2): knee joint - guessing a 135 deg (2.35 rad) angle (wrt to vert)
% x(3): X position of the knee joint - guessing middle of the leg span in mm
% x(4): Y position of the knee joint - know it is 0 mm at the horizontal step position
% x(5): Z position of the knee joint - guessing the height to be ~80% of the height of a limb
[x, fval, info] = fsolve(#Rug_Bug_Leg, inits); % when running fsolve for the first time often have to remove the output suppress
The code below shows the function containing the system of equations to be solved by Octave's fsolve function:
function y = Rug_Bug_Leg(x)
global AC;
global CB;
global lspan;
global bob;
s0 = 0; % fore/aft (Y) position of the foot during the step. Trying to iterate this
Theta_H = 0; % hip angle during the step. Trying to iterate this
y = zeros(6,1); % zeros for left side of each equation
% First set of equations, Joint C wrt to Joint A
y(1) = -1*x(3)+AC*sin(x(1))*cos(Theta_H);
y(2) = -1*x(4)+AC*sin(x(1))*sin(Theta_H);
y(3) = -1*bob - x(5)+AC*cos(x(1));
% Second set of equations, Joint B wrt to Joint C
y(4) = x(3)-lspan +CB*sin(x(2))*cos(Theta_H);
y(5) = x(4) - s0 +sin(x(2))*sin(Theta_H);
y(6) = x(5) + bob + CB*cos(x(2));
end function
You can definitely do that!
All you need to do is create a function that returns a function.
First have your Rug_Bug_Leg function take s0 and Theta_H as inputs:
function y = Rug_Bug_Leg(x, s0, Theta_H)
% ...
endfunction
Then, you can write a "wrapper" function around Rug_Bug_Leg like this:
rbl = #(s0, Theta_H) #(x) Rug_Bug_Leg(x, s0, Theta_H)
Now, if you call rbl with some values (s0,Theta_H), it will return a function that takes x as input and returns Rug_Bug_Leg(x,s0,Theta_H).
For instance, rbl(0,0) returns the function:
#(x) Rug_Bug_Leg(x,0,0)
Here's a sample usage:
for s0=1:10
for Theta_H=1:10
[x, fval, info] = fsolve( rbl(s0,Theta_H), inits );
endfor
endfor

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.

Gridded geometry in Windows doesn't restrict resizing

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!

Is this "pixel shift" a bug in tcl/tk canvas?

Look at result of this script:
canvas .c -bg white
grid .c
set x1 20
set x2 22
set y2 105
for {set f 0} {$f<50} {incr f} {
set y1 [expr {$y2-0.05*$f}]
.c create rectangle $x1 $y1 $x2 $y2 -fill black
incr x1 2
incr x2 2
}
On Windows XP I see that at left side of figure bottom margin is one pixel lower than at right side. But it shouldn't happen as y2 is the same (105) for all rectangles. What do you think?
I think it has to do with the effort of TK
to draw a rectangle of a least 1 pixel in size.
In the code I can see, that y2 is incremeted by
1 if it's equal to y1 after rounding to short integer.
Logging your creation statements one can see, that the pixel jump
occurs between f=10 and f=11. That is the point where
y1 and y2 become unequal and no adjust takes place:
f=10 .c create rectangle 40 104.5 42 105 -fill black
rounded: y1=105 y2=105
adjusted: y1=105 y2=106
f=11 .c create rectangle 42 104.45 44 105 -fill black
rounded: y1=104 y2=105
no adjustment
That explains the pixel jump.
IMO you should file a bug on this.