Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

text widget must follow end of text only if associated scrollbar is at bottom

10 views
Skip to first unread message

bru...@hp.com

unread,
Aug 17, 2005, 4:00:43 PM8/17/05
to
I've created a text widget using Tcl/Tk 8.3.5 (can't use a different
version) with wrapping enabled. The text widget has an associated
scrollbar. Text gets continuously appended to the end. I'd like to be
able have the text widget keep the last character appended within view
as long as the scroll bar is at the bottom. If the scroll bar is not
at the bottom (e.g. the bottom of the text is not within view), then
the text widget would not change the visible text until the user
dragged the scrollbar to the bottom of the text (or the bottom of the
text became visible some other way).

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.

Bryan Oakley

unread,
Aug 17, 2005, 4:19:19 PM8/17/05
to
bru...@hp.com wrote:
> ...I'd like to be

> able have the text widget keep the last character appended within view
> as long as the scroll bar is at the bottom. If the scroll bar is not
> at the bottom (e.g. the bottom of the text is not within view), then
> the text widget would not change the visible text until the user
> dragged the scrollbar to the bottom of the text (or the bottom of the
> text became visible some other way).

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}
}

bru...@hp.com

unread,
Aug 17, 2005, 4:23:15 PM8/17/05
to
I'd also like to avoid doing the ".mytext see end" every time a new
character comes in (assuming the bottom of text is already in view),
since this is a lot of extra processing. 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?

Bryan Oakley

unread,
Aug 17, 2005, 5:00:15 PM8/17/05
to
bru...@hp.com wrote:
> I'd also like to avoid doing the ".mytext see end" every time a new
> character comes in (assuming the bottom of text is already in view),
> since this is a lot of extra processing.

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.

Kaitzschu

unread,
Aug 17, 2005, 5:05:28 PM8/17/05
to

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

bru...@hp.com

unread,
Aug 17, 2005, 5:40:12 PM8/17/05
to
The reason I asked for help was that wish uses more than 90% of my CPU
when my application is getting continuous data. If I take out the
".text see" and ".myscrollbar get", the amount of CPU wish takes is
very small. In order to make my application less of a hog, I've had to
turn off wrapping so that I only need to call ".text see" after I
insert a newline character.

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
}
}

Bryan Oakley

unread,
Aug 17, 2005, 6:20:57 PM8/17/05
to
bru...@hp.com wrote:
> The reason I asked for help was that wish uses more than 90% of my CPU
> when my application is getting continuous data. If I take out the
> ".text see" and ".myscrollbar get", the amount of CPU wish takes is
> very small. In order to make my application less of a hog, I've had to
> turn off wrapping so that I only need to call ".text see" after I
> insert a newline character.
>
> I'm wondering if the following might work:
> [snip]

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?

bru...@hp.com

unread,
Aug 17, 2005, 6:32:33 PM8/17/05
to
I'm reading the date a character at a time from a 115200 serial
connection. I can't read a line at a time because there are times
where the data for a single line comes in very slowly (e.g. a character
based progress bar), and the user needs to see the characters as they
arrive.
I tried running using the addChar function from above, and when the
data is pouring in, my CPU still goes up above 90% (on an Intel P4 2.8
GHz with Linux). As an experiment, I tried just doing a .text yview on
every incoming character (no ".text see"), and the CPU was still above
90%. The only way my CPU isn't struggling is when I remove both the
".text see" and ".text yview", so I think I'm out of luck until there's
a "-stayatendwhenatbottom" option for the text widget.

Bryan Oakley

unread,
Aug 17, 2005, 7:06:04 PM8/17/05
to
bru...@hp.com wrote:
> I tried running using the addChar function from above, and when the
> data is pouring in, my CPU still goes up above 90% (on an Intel P4 2.8
> GHz with Linux). As an experiment, I tried just doing a .text yview on
> every incoming character (no ".text see"), and the CPU was still above
> 90%.

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.

bru...@hp.com

unread,
Aug 17, 2005, 7:56:34 PM8/17/05
to
If I had a global flag set by a scrollbar change, I'd still need
to check the text widget's yview after I inserted the character to see
if the line wrapped and I need to "see end"
If I do the see/yview command every Nth character, but the (N-1)th
character caused the line to wrap, then when I got to the Nth
character, my yview would no longer be at the bottom.

I still think I'm out of luck.

bru...@hp.com

unread,
Aug 17, 2005, 8:00:11 PM8/17/05
to
In case I didn't make it clear, if I call either the yview or see on
every character, the CPU chokes.

Kevin Kenny

unread,
Aug 18, 2005, 9:28:17 AM8/18/05
to
bru...@hp.com wrote:
> The reason I asked for help was that wish uses more than 90% of my CPU
> when my application is getting continuous data. If I take out the
> ".text see" and ".myscrollbar get", the amount of CPU wish takes is
> very small. In order to make my application less of a hog, I've had to
> turn off wrapping so that I only need to call ".text see" after I
> insert a newline character.

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

bru...@hp.com

unread,
Aug 18, 2005, 11:27:19 AM8/18/05
to
I'm currently limiting the number of lines to 20000, doing the deleting
every time I encounter a '\n'. I tried backing it off to 500 lines,
and making the line deletion occur periodically (every 0.5 sec), but my
CPU is still choking on wish because of the ".text yview" and/or ".text
see".

Darren New

unread,
Aug 18, 2005, 12:03:00 PM8/18/05
to
Kevin Kenny wrote:
> Most people who do terminal applications also do periodic
> .path.to.text delete 1.0 end-500l

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."

Quokka

unread,
Aug 18, 2005, 11:50:26 PM8/18/05
to
Darren New wrote:
> Kevin Kenny wrote:
>
>> Most people who do terminal applications also do periodic
>> .path.to.text delete 1.0 end-500l
>
>
> Oh wow. That's much easier than the calculations I did to accomplish
> that. I wish I'd thought of the "end-..." syntax. :-)
>

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

bru...@hp.com

unread,
Aug 19, 2005, 1:14:41 PM8/19/05
to
Does your update_display function run periodically? If so, how to you
keep characters from getting added to rxBuf between when you do the
"puts $status(realTimeLogFile) $rxBuf nonewline" or ".text insert end
$rxBuf rxTag" and when you do the "set rxBuf """? Isn't there a race
condition here where you could be deleting characters that haven't been
logged or displayed in your text box?

Donald Arseneau

unread,
Aug 19, 2005, 4:19:49 PM8/19/05
to
bru...@hp.com writes:

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

Quokka

unread,
Aug 21, 2005, 9:05:35 PM8/21/05
to

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


0 new messages