I've tried doing the following for every character:
set scroll_at_bottom \
[expr { [lindex [.myscrollbar get] 1] == 1 }]
.mytext insert end $nextchar
if { $scroll_at_bottom } {
.mytext see end
}
The problem lies in the fact that when the text widget wraps a line,
the visible text is no longer at the bottom, so the next time a
character comes in, $scroll_at_bottom is false.
Any suggestions would be greatly appreciated.
Try this:
text .t -yscrollcommand [list .vsb set]
scrollbar .vsb -orient vertical -command [list .t yview]
pack .vsb -side left -fill y
pack .t -side right -fill both -expand y
proc addText {args} {
set yview [.t yview]
if {[lindex $yview 1] >= 1.0} {
set scroll 1
} else {
set scroll 0
}
foreach {text tags} $args {
.t insert end $text $tags
}
if {$scroll} {.t see end}
}
Why do you think it is a lot of extra processing?
On my laptop, a call to '.text see' takes all of about 250 usec when
scrolling is necessary, about 40 usec when no scrolling is necessary.
Doing an if statement to determine if you should call '.text see' will
probably take about that long so you aren't saving any cycles.
> I only was to do the
> ".mytext.see end" when a line wraps or when an end of line occurs. Is
> there an easy way to determine if a line will wrap if a character gets
> added to the end of the last line in the text widget?
>
There is probably not an easy way to determine if the text will wrap,
other than to implement the same algorithm that the text widget uses.
That is, you could get the bbox of the last character, determine the
width of the character(s) to be inserted, determine if the new
characters will extend the bbox beyond the margin, find the word
boundary (if word wrapping is on), etc.
*that* sounds like a lot of processing. I'm still a bit puzzled why you
think doing all that in tcl will be less processing than letting the
text widget do all that work in C using its internal knowledge of the
state of the widget.
You don't want to do this, as it requires either low-level hackery (to
make [text] generate virtual event <<Wrapped>>) or checking [yview] after
insertion, that is pretty much as fast operation as [see end] would be.
Just [see end] and optimize something more CPU intensive.
--
-Kaitzschu
s="TCL ";while true;do echo -en "\r$s";s=${s:1:${#s}}${s:0:1};sleep .1;done
I'm wondering if the following might work:
proc addChar {char} {
set yview [.t yview]
if {[lindex $yview 1] >= 1.0} {
set scroll 1
} else {
set scroll 0
}
.t insert end $char
# only "see" end if we were previously
# at the bottom, but we aren't now
set yview [.t yview]
if {$scroll && [lindex $yview 1] < 1.0} {
.t see end
}
}
Only you can tell us... :-)
How are you reading data? one byte at a time? Is it possible to read a
line at a time instead?
So, do we assume it's the yview command that is causing grief? There are
other ways to skin that cat. For instance, change the scrollbar command
to call a proc, and have it set a global flag when it scrolls to the
bottom. Then, just check the global flag rather than calling yview all
the time.
Another solution might be to only do the see/yview command every Nth
character and each time a \n is received. I agree that doing yview/see
on every byte is a bit much.
I still think I'm out of luck.
Are you putting a large amount of data (1000s of lines) in the text
widget? The text widget doesn't scale very well to unlimited rollback.
Most people who do terminal applications also do periodic
.path.to.text delete 1.0 end-500l
(change 500 to a number reasonable in your application) and see much,
much better performance.
--
73 de ke9tv/2, Kevin
Oh wow. That's much easier than the calculations I did to accomplish
that. I wish I'd thought of the "end-..." syntax. :-)
--
Darren New / San Diego, CA, USA (PST)
"You're going to be late for your meeting."
"That's OK. I can be late."
"What's it about?"
"Someone else's problems."
This is how I do it...
My receive event handler plugs received characters
into the global rxbuf.
This has the advantage of only deleting whole lines
from the text box. (note: the text box is
normally disable so you can't edit the
received data).
proc update_display { } {
global rxBuf
global status
if { [ string length $rxBuf ] > 0 } {
if { $status(logToDisk) } {
catch {
puts $status(realTimeLogFile) $rxBuf nonewline
}
}
.text config -state normal
.text insert end $rxBuf rxTag
set rxBuf ""
scan [ .text index end ] "%d." lineCount
# Limit buffer to 10000 lines to stop it consuming
# all available memory
if { $lineCount > 100000 } {
# Remove the start of the buff
set del [ expr { $lineCount - 100000 } ]
.text delete 1.0 $del.0
}
.text config -state disabled
}
}
Regards
Palu
I did something similar to this once, using a delayed check and
a global flag...
set check_pending 0
set check_requested 0
After every character do:
if { $::check_pending } {
set ::check_requested 1
} else {
scroll_check
after 20 { service_requests }
set ::check_pending 1
}
where:
proc service_requests { } {
if { $::check_requested } { scroll_check }
}
and where scroll_check should set
set ::check_pending 0
set ::check_requested 0
in addition to perfoming the check for scrolling.
The effect of this is that a scroll check will be done immediately
if one has not preceded within 20ms, and a check is sure to be made
within 20ms after any character. Set as high a delay as is acceptable.
This can have the danger of increasing screen flicker.
(Just typed, not tested, so there are probably some typos)
--
Donald Arseneau as...@triumf.ca
My application is not using a threaded version of TCL.
Everything is driven by the event loop.. so there
is no way for there to be a race.
I run the update_display from two places:
When I receive a new line character from
the serial port
and
I have a tkwait on the variable rxBuf.
For my application (simple terminal application for
interacting with line based embedded devices) this
works perfectly.
Regards
Paul