From: alavilli prasad <alavilli@cse.uiuc.edu>
To: libes@nist.gov
Cc: alavilli@cse.uiuc.edu
Subject: tkbiff with tix
Date: Thu, 12 Nov 1998 18:21:49 -0600 (CST)

Hello,

Some time ago I have taken your tkbiff program and extended it to use tix
and arrange a better mail prompt to the user. All of the original functionality
reamains.  I have now updated that to version 2.6 of tkbiff.

Once you receive a mail from someone, if that user is known
in your ~/.tkbiff file, you get his image with the subject on the side.
If the user is unknown then you get the name in the box.
You have worked with sounds for different senders and I play with user images. 
Left click in the box to see the message; right click to remove that
message from active view; and middle click would make the whole listbox
disappear from the screen and it reappears as soon as a new message is
received..... and so on.

Anyway, here is a portion of my screen showing how it all looks:

                 http://www.csar.uiuc.edu/~alavilli/tkbiff.gif

The color code is: red for NEW; green for RO and blue for O messages.

I have enjoyed tkbiff very much and hope this is a good extension.
I use it everyday though!
I actually don't really know any tcl/tk but just liked tkbiff so much
that I somehow hacked along; so please pardon my poor programming style.

Your comments and any bug fixes are welcome.

I run my tkbiff with tixwish.  Here is my .tkbiff file:
One needs to configure that part of update_display below with actual file
names for user images.

#######################################################################
# .tkbiff file for customizing Don Libes' tkbiff
#
# You may not need to, but feel free to change ANYTHING in this file.
# The GUI is entirely in this configuration file, thus you can change
# any part of the GUI.  Indeed, that's the nice thing about tkbiff.
# The GUI is configurable in every way.
#
# Note: the default look-and-feel are set to what I like.  To get more
# information on the defaults, just run tkbiff and press ? in the window
# that pops up.  It is self-explanatory.
#######################################################################

#######################################################################
# Action to execute upon receipt of new mail.
proc announce_new_msg {id} {
    global msg

    #puts "you have new mail from $msg($id,from)"
    bell; bell

    # For convenience, extract msg attributes, lowercase to simplify tests,
    # and put pieces into simple variables for easy access.

    set from	[string tolower $msg($id,from)]
    set to	[string tolower $msg($id,to)]
    set cc	[string tolower $msg($id,cc)]
    #set subject	      $msg($id,subject)
    set subject	[string tolower $msg($id,subject)]
    # Other fields that can be examined are: sender, replyto, and body.

    # Now the tests are simple.  The long "if" may look dumb, but
    # there's little point in making it table-driven.  By leaving
    # the tests explicit, we get the flexibility to do much more
    # creative or complex decision making.
    # Plus it's very easy to read!

    if {[string match "*expect*" $subject]} {
	play "Yeah"
    } elseif {[string match "*ora.com*" $from]} {
	play Sledge-Flute
    } elseif {[string match "*mailer-daemon*" $from]} {
	play bogus
    } elseif {[string match "*bark*" $from]} {
	play bark
    } elseif {[string match "*bodarky*" $from]} {
	play doh
    } elseif {[string match "*bloom*" $from]} {
	play BT-Royal-Ugly-Dudes
    } elseif {[string match "*clark*" $from]} {
	play Back-Off-Man
    } elseif {[string match "*debtron*" $from]} {
	play AL-Lets-Rock
    } elseif {[string match "*densock*" $from]} {
	play bringout
    } elseif {[string match "*fowler*" $from]} {
	play hes-dead-jim
    } elseif {[string match "*shoffman*" $from]} {
	play chirp
    } elseif {[string match "*ken manheimer*" $from]} {
	play Aludium-Q36
    } elseif {[string match "*don libes*" $from]} {
	play What-would-you-do-with-a-brain
    } elseif {[string match "*luce*" $from]} {
	play PB-Swords
    } elseif {[string match "*mulroney*" $from]} {
	play stupid-for-nothing
    } elseif {[string match "*kc*" $from]} {
	play clint_eastwood
    } elseif {[string match "*paisley*" $from]} {
	play Look-Up-in-the-Sky
    } elseif {[string match "*potts*" $from]} {
	play space-madness
    } elseif {[string match "*steve ray*" $from]} {
	play STTNG-redalert
    } elseif {[string match "*ressler*" $from]} {
	play crash
    } elseif {[string match "*sauder*" $from]} {
	play justwhat
    } elseif {[string match "*selden*" $from]} {
	play Darth-Dont-be-too-proud
    } elseif {[string match "*susan libes*" $from]} {
	play Be-nice
    } elseif {[string match "*sol libes*" $from]} {
	play Where-humor
    } elseif {[string match "*express-users*" $to$cc]} {
	play horse-poop
    } else {
	play TNG-beep2
    }
}

# Prepare messages for display and announcement
# Currently, I just use this to suppress some msgs from display/announcement
proc prep_new_msg {id} {
    global msg

    set from	[string tolower $msg($id,from)]
    set to	[string tolower $msg($id,to)]
    set subject	$msg($id,subject)

    # I want to be alerted about anything personally addressed to me.  
    ##if [string match "*libes*" $to] return
    # prasad: show all messages
    return

    if {[string match *library* $from]} {
	# Skip all messages from our library server (except my own).
	###suppress_msg $id
    } elseif {[string match "*mailer-daemon*" $from]} {
	# As postmaster, I see all bounced headers.
	# Suppress them except for my own.
	###suppress_msg $id
    } elseif {[string match "*majordomo*" $from]} {
	# Skip complaints from majordomo.
	###suppress_msg $id
    } elseif {[string match "*postmaster*" $from] &&
	      [string match "*postmaster*" $to]} {
	# Shouldn't normally be needed except that I'm getting
	# deluged by these from epfl.ch.
	###suppress_msg $id
    } elseif {[string match "*seds report*" $subject]} {
	###suppress_msg $id
    }
}

######################################################################
# initializations that should only be done once - when program is started
######################################################################
proc init {} {
    uplevel #0 {

	# Sleep this many seconds before checking again.  Leave this short
	# for good response.  Don't worry about it taking a lot of time -
	# the checking is inexpensive.
	set sleep 		4

	# parameters for main window which displays sender-subject pairs
	set width		12	;# after this, truncate
	set maxheight		4	;# after this, add a scrollbar
	set main_font		
-adobe-times-bold-r-normal--14-140-75-75-p-77-hp-roman8
	set main_font_alt	
-adobe-times-bold-r-normal--12-120-75-75-p-67-iso8859-1

	# parameters for body windows
	set body_font		*-Courier-Bold-R-Normal-*-120-*
	set body_height_max	60	;# after this, add a scrollbar
	set body_width		80	;# after this, wrap

	# parameters for help window
	set help_font	*-Helvetica-Bold-R-Normal--*-120-*

	# if you don't want message body windows to go away when you hide
	# them in the main window, set to 0.
	set behavior(destroy_body_when_hiding)	1

	# if you don't want message body windows to go away when the mail
	# is no longer in your spool file, set to 0.
	set behavior(destroy_body_when_gone)	1

	# scroll new message into main window automatically
	# (assuming you aren't looking elsewhere).
	set behavior(autoscroll)	1

	# raise main window when new mail arrives
	set behavior(autoraise)		1

	# deiconify main window when new mail arrives
	set behavior(autodeiconify)	1

	# if you don't want to display certain messages, change any of
	# the following values to 0.
	set display(unread) 1	;# for people who write back to the spool file
	set display(read)   0	;# for people who write back to the spool file
	set display(new)    1

	# The audio driver (tkbiff_audio) has a default list of dirs in which
	# to search for audios.  You can override that by redefining the env
	# variable AUDIOPATH.  Example:
	#    set env(AUDIOPATH) "~/audio:/depot/sundry/lib/audio:"
	# Colon at end (or beginning) will check current directory.

	# enable sound support
	set behavior(sound) 0

	# my machine at home has no sound, sigh
	if {[string first "isdn055" $env(DISPLAY)]!=-1} {
	    set behavior(sound) 0
	}

	# Define things for the play proc which handles audios.
	set audio(default_volume) 50		;# range is 0-100

	# See tkbiff_audio(1) for more info.

	# Remote program is used if tkbiff runs on one host (i.e.,
	# where mail spool file is) but displays on another.  

	# Need full path since rsh et al have such primitive paths.
	# If user doesn't specify it, use 'which' and hope that remote
	# path is same as local path.
	if ![info exists audio(remotecmd)] {
	    if [catch {set audio(remotecmd) [which tkbiff_audio]}] {
		puts "tkbiff: tkbiff_audio not found - please finish installing 
tkbiff (or add the tkbiff directory to your path)."
		set audio(remotecmd) UnknownRemotePath/tkbiff_audio
	    }
	}

	# I like bisque (a peach-like color).  Comment this out and
	# you will get Motif-standard gray.
	# Or just choose your own colors.
	if {[winfo depth .] > 1} {
		#tk_bisque
		tk_setPalette bisque2    ;#prasad
	}

	# If you like seeing cute 3D borders around everything, set the
	# following to a non-zero value.  2 looks fine to me, but I simply
	# don't want to waste the space.  (Actually, I like borders around
	# scrollbars - so that is forced later despite this setting.)
	set borders 0

	# And if you like space *around* the borders as well (bleah!!!),
	# set the following to a non-zero value.
	option add *highlightThickness 0

	# initialize widgets and other things that are probably
	# not of interest to most people
	_init			

	#############################################################
	# define keystroke and mouse button actions
	#############################################################

        #         permanent/temporary-------------------\
        #         show headers-----------------------\  |
        #                                             V V
	bind .main <1>			{show_body %y 1 1}
	##bind .main <2>			{show_body %y 0 0}
	bind .main <2>			{hide_msgx %y}
	bind .main <Shift-1>		{show_body %y 1 1}
	bind .main <Shift-2>		{show_body %y 1 0}

	##bind .main <ButtonRelease-2>	{catch {destroy $w}}
	bind .main <3>			{hide_msg  %y}

	#bind all <Return>		{check_for_new_mail}
	bind all <Return>		{update_display}

	bind all <u>			{toggle_display unread}
	bind all <r>	 		{toggle_display read}
	bind all <n>			{toggle_display new}

	bind all <f>			{alt_font %K}

	bind all <h>			{help}
	bind all <question>		{help}

	bind all 
			{toggle_behavior sound}
	bind all <a>			{toggle_behavior autoscroll}
	bind all <t>			{toggle_behavior autoraise}
	bind all <i>			{toggle_behavior autodeiconify}

	# support "more"-like scrolling
	bind all <space> 		{scroll %W 1}
	bind all <Delete> 		{scroll %W -1}
	bind all <BackSpace> 		{scroll %W -1}
    }
}

# procedure to play an audio clip
#    arg 1: sound name
#    arg 2: volume level (optional)
proc play {sound {volume default}} {
    global behavior audio

    if {!$behavior(sound)} return

    if {$volume == "default"} {
	set volume $audio(default_volume)
    }

    if [catch "exec $audio(cmd) -v $volume $sound" msg] {
	puts "couldn't play $sound using $audio(cmd)"
	puts "$msg"
    }
}

########################################################################
#
#  Most people will not be interested in changing things below here.
#
########################################################################

proc _init {} {
    uplevel #0 {
	set gui_version "1.8.0 (4/4/98)"

	wm minsize . 1 1
	wm maxsize . 999 999
	wm title . "Mail Box"
	wm geometry . -0+0
	wm iconname . tkbiff
	##wm title . "tkbiff - Press ? for help"

	option add *elementBorderWidth 2
	option add *borderWidth $borders
	option add *relief sunken

	if !$borders {
	    # force borders around scrollbars
	    option add *Scrollbar.borderWidth 2
	}

	set position 1

	##set expected_height 1
	##listbox .main -width $width -height 1		

	set expected_height 4
        #with that long box (50) below a reasonable number of mails
        #will end up in a single column (the -columns 1 option does not
        #itself work for some reason!)
	tixTList .main -height 3 -width $width -orient horizontal -bg bisque2 
-fg firebrick
	#.main configure -columns 1

	##.main config -font $main_font -yscroll ".sb set" -setgrid 1 \
		##-selectborderwidth 1
	##scrollbar .sb -command ".main yview"

	.main config -font $main_font -yscroll ".sb set" \
		-selectborderwidth 1
	scrollbar .sb -command ".main yview"

	set scrollbar_visible 0
	pack .main -expand yes -fill both -side right

	update
	# actual height of window
	set height 1			

	bind .main <Configure> {
	    # record new width
	    scan [wm geometry .] "%%dx%%d" width height

            #width and height are coming out 80x50; 
            #so explicitly set again here
            set width 100
	    set height 52

	    set old_maxheight $maxheight
	    if {$height != $expected_height} {
		set maxheight $height
		#puts "setting new maxheight $height"
	    }

	    if {$maxheight != $old_maxheight} update_help_text
	    update_display
	}

	# skip class bindings
	bindtags .main {.main . all}

	# focus .
	bind .main <FocusIn>		{focus .}

	# generate pretty things for help window
	if {-1 == [string first / $rcfile]} {
	    set display(rcfile) ~/$rcfile
	} else {
	    set display(rcfile) $rcfile
	}
	if [info exists whoami] {
	    set display(whoami) $whoami
	} else {
	    set display(whoami) "derived"
	}

	# To which host should audios be directed?
	# We can guess by looking at where the X display is
	# however sometimes this is wrong (e.g., ssh uses a proxy on the
	# same host), so we only guess if the user doesn't explicitly say.
	if ![info exists audio(host)] {
	    regexp "(.*):" [winfo screen .] dummy Xhost
	    set hostname [info hostname]
	    set domainname ""
	    catch {set domainname [exec domainname]}
	    if {"$Xhost" != "$hostname.$domainname"
	    && "$Xhost" != "$hostname"
	    && "$Xhost" != "unix"
	    && "$Xhost" != ""} {
		set audio(host) $Xhost
	    }
	}

	# If user didn't select remote shell, guess.
	if {![info exists audio(rsh)]} {
	    # try ssh, then remsh, then rsh
	    # (HPs have both remsh and rsh but rsh doesn't seem to work right)
	    if [catch {set audio(rsh) [which ssh]}] {
		if [catch {set audio(rsh) [which remsh]}] {
		    set audio(rsh) rsh
		}
	    }
	}
	if {[info exists audio(host)]} {
	    set audio(cmd) "tkbiff_audio -r $audio(rsh) -c $audio(remotecmd) -h 
$audio(host)"
	    set display(audiohost) $audio(host)
	    set display(audiorsh) $audio(rsh)
	    set display(audiorcmd) $audio(remotecmd)
	} else {
	    set audio(cmd) tkbiff_audio
	    set display(audiohost) "not used"
	    set display(audiorsh) "not used"
	    set display(audiorcmd) "not used"
	}
    }
}

proc usage {} {
    uplevel #0 {
	while {[llength $argv]} {
	    set flag [lindex $argv 0]
	    switch -- $flag \
		    "-audiohost" {
		set audio(host) [lindex $argv 1]
		set argv [lrange $argv 2 end]
	    } "-audiorsh" {
		set audio(rsh) [lindex $argv 1]
		set argv [lrange $argv 2 end]
	    } "-audiorcmd" {
		set audio(remotecmd) [lindex $argv 1]
		set argv [lrange $argv 2 end]
	    } default {
		puts {usage: tkbiff [-cf configfile] [-mbox file] [-user 
username] [-audiohost host] [-audiorsh rsh|ssh]}
		exit
	    }
	}
    }
}

proc hide_msgx {y} {
	wm withdraw .
	return
}

proc hide_msg {y} {
    global msg show_list behavior

    if {![info exists show_list]} {
	wm iconify .
	return
    }

    # translate from y position to message id
    ##set id [lindex $show_list [.main nearest $y]]
    set id [lindex $show_list [.main nearest 0 $y]]

    if {$behavior(destroy_body_when_hiding)} {
	set w [id_to_widget $id]
	catch {destroy $w}
    }

    set msg($id,status) HIDE
    update_display
}

proc suppress_msg {id} {
    global msg
    set msg($id,status) HIDE
}

proc toggle_display {type} {
    global display

    set display($type) [expr !$display($type)]
    update_help_text
    update_display
}

proc toggle_behavior {type} {
    global behavior

    set behavior($type) [expr !$behavior($type)]
    update_help_text
}

proc normal_font {keysym} {
    global main_font

    .main configure -font $main_font
    bind all $keysym {alt_font %K}
}

proc alt_font {keysym} {
    global main_font_alt

    .main configure -font $main_font_alt
    bind all $keysym {normal_font %K}
}

proc position_window {w} {
    global position

    # some people have interactive placement which really loses
    # so force placement somewhere
    set position [expr ($position+20)%300]
    wm geometry $w +$position+$position
}

# translate an id to a widget name
proc id_to_widget {id} {
    # strip punctuation from id so it is usable as a widget name
    regsub -all {[][<>.@ :$-]} $id "" w
    # adding a prefix solves two problems:
    # 1) Tk does not permit uppercase widget names
    # 2) some messages arrive with screwed up message ids, like < >
    #    which would get rewritten to . without a prefix
    return .msgid_$w
}

# Compute physical number of lines required to display a string.
# String must NOT include terminating newline. 
proc count_lines {s} {
    global body_width body_height_max

    set lines 0
    # long lines will wrap, so compute physical lines required.
    # no handling is made for special characters (in particular, control
    # chars can consume up to 4 chars and tabs can consume up to 8)
    # however the likelihood of them actually causing problems is low
    # enough that its not worth worrying about.  And even in the worst
    # case, the user can scroll the window up using the pan bindings.
    # Can't fit more than 75 lines on any screen, so don't bother counting
    # lines after that.
    set max_lines 75
    foreach line [split $s \n] {
	# treat empty lines as if they had one char to simplify
	# subsequent linecount computation
	if 0==[set chars [string length $line]] {
	    set chars 1
	}
	incr lines [expr 1+($chars-1)/$body_width]
	
	if {$lines > $max_lines} break
    }
    return $lines

    # Below here is an old version, no longer used.  It's dead-on
    # but too slow and I'm willing to give up some accuracy.

    # if long enough, don't even bother being accurate
    if {[string length $s]>$body_width*$body_height_max} {
	return $body_height_max
    }

    set row 1
    set col 0

    foreach char [split $s ""] {
	scan $char %c c
	if {$c >= 040 && $c <= 0176} {
	    incr col
	} elseif {$c == 012} {
	    set col 0
	    incr row
	    continue
	} elseif {$c == 011} {
	    incr col [expr 8 - $col%8]
	} elseif {$c >= 07 && $c <= 015} {
	    incr col 2
	} else {
	    incr col 4
	}
	if {$col > $body_width} {
	    incr row
	    set col [expr $col-$body_width]
	}
	if {$row > $body_height_max} break
    }
    return $row
}

# when messages go away, delete all the gui-related bookkeeping
proc destroy_guimsg {id} {
    # if message was suppressed, this will fail immediately - that's normal
    catch {
	unset guimsg($id,headers)
	unset guimsg($id,headers,lines)
	unset guimsg($id,subject)
	unset guimsg($id,subject,lines)
	unset guimsg($id,body,lines)
	unset guimsg($id,min,lines)
	unset guimsg([id_to_widget $id],id)
    }
}

# Create preformatted message for display purposes.
# This is done once per message so that the act of popping it up is quick.
# The bottleneck here is computing the number of lines in the message.
# The original size that comes with the message is logical lines, but we need
# physical lines, specific to the width of the text widget.  When lines wrap,
# logical lines can take multiple physical lines.  So this procedure also
# computes how many lines will be required.  We want to show that many lines
# if we have the space to do so.
proc create_guimsg {id} {
    global msg guimsg

    if {[string match $msg($id,status) HIDE]} return

    set w [id_to_widget $id]

    if [string compare "" $msg($id,resentfrom)] {
	append headers "Resent-From: $msg($id,resentfrom)\n"
    }
    if [string compare "" $msg($id,to)] {
	append headers "To: $msg($id,to)\n"
    }
    if [string compare "" $msg($id,cc)] {
	append headers "Cc: $msg($id,cc)\n"
    }
    if [string compare "" $msg($id,date)] {
	append headers "Date: $msg($id,date)\n"
    }
    set guimsg($id,headers) [string trim $headers \n]
    set guimsg($id,headers,lines) [count_lines $guimsg($id,headers)]
    if {$guimsg($id,headers,lines)} {
	append guimsg($id,headers) "\n"
    }

    set guimsg($id,subject) "Subject: $msg($id,subject)"
    set guimsg($id,subject,lines) [count_lines $guimsg($id,subject)]
    set guimsg($id,body,lines) [count_lines $msg($id,body)]
}

proc show_body {y show_headers permanent} {
    global bodies w msg guimsg body_width body_height_max
    global body_font show_list display

    if {![info exists show_list]} return

    # translate from y position to message id
    set id [lindex $show_list [.main nearest 0 $y]]

    set w [id_to_widget $id]

    if [winfo exists $w] {
	if $permanent {
	    destroy $w
	} else {
	    raise $w
	}
	return
    }

    toplevel $w
    position_window $w
    wm title $w "From: $msg($id,from)"
    wm iconname $w $msg($id,from,short)

    set lines [expr $guimsg($id,body,lines) + $guimsg($id,subject,lines)]

    set body {}
    if $show_headers {
	append body $guimsg($id,headers)
	incr lines $guimsg($id,headers,lines)
    }

    # number of lines in header+message is at least this and may be more
    set guimsg($id,min,lines) $lines
    # remember id so that we can get it from events using only widget name
    set guimsg($w,id) $id

    append body "$guimsg($id,subject)\n$msg($id,body)"

    if {$lines > $body_height_max} {
	set winheight $body_height_max
    } else {
	set winheight $lines
    }

    # Constrain width because we don't handle varying widths.
    wm minsize $w $body_width 1
    wm maxsize $w $body_width 999
    text $w.text -width $body_width -height $winheight -font $body_font \
	    -setgrid 1

    scrollbar $w.sb -command "$w.text yview"
    if {[winfo depth .] > 1} {
	$w.sb config -activebackground #ffaeb9
    }
    $w.text config -yscroll "$w.sb set"

    # scrollbar would normally be packed during configure event
    # but it looks so funny (seeing the window move over) that we
    # pack it here initially
    if {$winheight < $lines} {
	pack $w.sb -expand yes -fill y -side left
    }

    pack $w.text -fill both -expand yes -side right

    $w.text insert end $body
    $w.text configure -state disabled	;# avoid giving focus
    bind $w <Destroy> {catch {destroy %W}}

    # puts "message rows >= [msg_line_count $msg($id,body)]"

    # Do raise last to avoid early flush.
    # Do update before raise, otherwise raise causes brief display
    # of a vanilla top-level.  Have no idea why this is.
    # (Wasn't necessary with Tk7.4.  Needed only by 7.5.)
    # Alas, the update means that a 2nd show_body can be triggered
    # at this time, destroying the window so that when we come back
    # it is gone.  Thus, we must check the raise.  Sigh.
    update
    if [catch {raise $w}] return

    bind $w.text <Configure> {
	set w [file rootname %W]

	# record new width
	scan [wm geometry $w] "%%dx%%d" winwidth winheight

	set id $guimsg($w,id)

	# if new height is big enough, add scrollbar if not already
	# if new height is small enough, remove sb if not already

	if {$guimsg($id,min,lines) > $winheight} {
	    if ![winfo ismapped $w.sb] {
		pack $w.sb -expand yes -fill y -side left
	    }
	} else {
	    if [winfo ismapped $w.sb] {
		pack unpack $w.sb
	    }
	}
    }

    # Remember windows that are temporarily displayed so we can scroll
    # them even though events are being delivered to main window.
    if {!$permanent} {
	set display(temporary) $w
    } else {
	catch {unset display(temporary)}
    }
}

proc update_display {} {
    global msg all_list maxheight scrollbar_visible width behavior
    global height expected_height display show_list

    # remember how window is scrolled
    set goaltop [.main nearest 0 0]		;# first visible line

    #set bottom_was_showing [expr [.main size] - $goaltop <= $height]
    # prasad: always put new message on gui
    set bottom_was_showing 1

    .main delete 0 end
    set show_count 0		;# number of showable messages
    # after any filtering
    catch {unset show_list}

    set font1 -adobe-times-bold-r-normal--14-140-75-75-p-77-hp-roman8
    set font2 -adobe-times-bold-r-normal--12-120-75-75-p-67-iso8859-1

    foreach id $all_list {
       if {$msg($id,status) == "O"   && $display(unread) ||
	    $msg($id,status) == "RO"  && $display(read) ||
	    $msg($id,status) == "NEW" && $display(new)} {
	    incr show_count
	    lappend show_list $id  
            #following needed because new msgs are now put on top
            #linsert does not seem to work!
	    #if {$show_count==1} { 
	       #lappend show_list $id
	    #} else {
	       #set show_list [concat $id $show_list]
	    #}

            set from	[string tolower $msg($id,from)]
            set to	[string tolower $msg($id,to)]
            set subject	$msg($id,subject)
            set sender	[string tolower $msg($id,sender)]
            set replyto	[string tolower $msg($id,replyto)]

            #this block sets nice/familiar names in $fromwhom
	    if {[string match "*alavilli*" $from]} {
	        set fromwhom "Prasad A.V.S."
	    } else {
	      set fromwhom $msg($id,from,short)
	    }

	    if {$msg($id,status) == "RO"} { 
                set colbg blue
                set colfg white
	    } elseif {$msg($id,status) == "O"} { 
	        set colbg green
	        set colfg black
	    } elseif {$msg($id,status) == "NEW"} { 
	        set colbg red
	        set colfg yellow
	    }

            #set xoff 0
	    set mtext $fromwhom
	    #for null subjects, $fromwhom is put
	    set sl [string length $subject]
	    if {$subject==""} { 
	       set subject $fromwhom 
	    } elseif {$sl < 6} {
	       append subject ":$fromwhom"
	    }

            #people first
            if {[string match "*Kumar*" $fromwhom]} {
	      set picture [image create pixmap -file /.dt/icons/kumar.xpm]
	      set mtext [string range $subject 0 16]
	    } elseif {[string match "*Malli*" $fromwhom]} {
	      set picture [image create pixmap -file /.dt/icons/malli.xpm]
	      set mtext [string range $subject 0 16]
	    } elseif {[string match "*Prasad A.V.S.*" $fromwhom]} {
	      set picture [image create pixmap -file /.dt/icons/roja.xpm]
	      set mtext [string range $subject 0 16]
	    } elseif {[string match "*martin*" $from]} {
	      set picture [image create pixmap -file /.dt/icons/martin.xpm]
	      set mtext [string range $subject 0 16]
	    } elseif {[string match "*tafti*" $from]} {
	      set picture [image create pixmap -file /.dt/icons/danesh.xpm]
	      set mtext [string range $subject 0 16]
	    } elseif {[string match "*moser*" $from]} {
	      set picture [image create pixmap -file /.dt/icons/moser.xpm]
	      set mtext [string range $subject 0 16]
	    } elseif {[string match "*heath*" $from]} {
	      set picture [image create pixmap -file /.dt/icons/heath.xpm]
	      set mtext [string range $subject 0 16]
	    } elseif {[string match "*mailer-daemon*" $from]} {
	      set picture [image create pixmap -file /.dt/icons/mailerr.xpm]
	      set mtext [string range $subject 0 16]

	    #then groups
	    } elseif {[string match "*m-w.com*" $from]} {
	      set picture [image create pixmap -file /.dt/icons/mw.xpm]
	      set mtext [string range $subject 0 16]
	    } elseif {[string match "*numeca*" $from]} {
	      set picture [image create pixmap -file /.dt/icons/numeca.xpm]
	    } elseif {[string match "*stro*" $from]} {
	      set picture [image create pixmap -file /.dt/icons/stro.xpm]
	    } elseif {[string match "*vub*" $from]} {
	      set picture [image create pixmap -file /.dt/icons/vub.xpm]
	    } elseif {[string match "*ncsa*" $from]} {
	      set picture [image create pixmap -file /.dt/icons/ncsa.xpm]
	    } elseif {[string match "*csar*" $from]} {
	      set picture [image create pixmap -file /.dt/icons/rocket.xpm]

	    #then unknown guys
	    } else {
	      set picture [image create pixmap -file /.dt/icons/who.xpm]
            }

            set img [image create compound -showbackground true -bg $colbg -fg 
$colfg -window .main]
            $img add line
            $img add image -image $picture
            #$img add space -width $xoff
            $img add text -text $mtext -justify left -wraplength 56
            .main insert end -itemtype image -image $img
          }
    }

    if {$show_count > 0} {
	if {$show_count <= $maxheight} {
	    set height $show_count
	} else {
	    set height $maxheight
	}

	#.main configure -width $width -height $height
	set hx [expr $height*52]
	wm geometry . ${width}x$hx
	#wm geometry . ""
	set expected_height $height
    } else {
	.main configure -bg bisque2
	.main configure -fg firebrick
	#.main configure -bitmap hourglass
	set picture [image create pixmap -file /.dt/icons/mbx.xpm]
        set img [image create compound -window .main]
        $img add line
	$img add space -width 2
        $img add image -image $picture
	$img add space -width 4
        $img add text -text "Mail Box" -font $font2 -fg firebrick
        .main insert 0 -itemtype image -image $img

	#.main configure -width $width -height 1
	wm geometry . ${width}x52
	#wm geometry . ""
	set expected_height 1
	#withdraw window when it carries no mail
	wm withdraw .     
    }

    if {$show_count > $maxheight} {
	if !$scrollbar_visible {
	    pack .sb -expand yes -fill y
	    set scrollbar_visible 1
	}
    } else {
	if $scrollbar_visible {
	    pack forget .sb
	    set scrollbar_visible 0
	}
    }

    if {$behavior(autoscroll) && $bottom_was_showing} {
	##.main yview [expr $show_count - $height]
    } else {
	##.main yview $goaltop
    }

    update
}

proc announce_new_msgs {} {
    global behavior new_list msg first_time recently_seen_ids

    if ![info exists first_time] {
	set first_time 1
	init
    }

    foreach id $new_list {
	prep_new_msg $id
	create_guimsg $id
    }

    update_display

    # at startup, do not announce messages already waiting
    if $first_time {
	set first_time 0
	return
    }

    set first_announce 1

    foreach id $new_list {
	if {[string match $msg($id,status) HIDE]} continue

	# if message was last seen just a short time ago
	# mailer probably temporarily rewrote file so don't
	# reannounce
	if {[info exists recently_seen_ids($id)]} continue

	# raise/deiconify before audios.  Since audios are slow
	# relatively-speaking, this lets the updated main window
	# be visible immediately.

	if {$first_announce} {
	    set first_announce 0
	    if {$behavior(autoraise)} {raise .}
	    if {$behavior(autodeiconify)} {wm deiconify .}
	    update
	}

	announce_new_msg $id
    }
}

proc renounce_msg {id} {
    global behavior

    if {$behavior(destroy_body_when_gone)} {
	set w [id_to_widget $id]
	catch {destroy $w}
    }

    destroy_guimsg $id
}

proc renounce_msgs {ids} {
    foreach id $ids {
	renounce_msg $id
    }
}

proc scroll {w dir} {
    global display

    # if win == ".", this means user has either requested main window be
    # scrolled (in which case we have to massage its name a little since
    # it's already got a "." at the end) OR we should scroll a msg window
    # that's already displayed.  The latter can happen if it's a temporary
    # msg window.
    if {$w == "."} {
	set w ""
	catch {set w $display(temporary)}
    }
    # ignore attempts to scroll non-scrollable windows (like help window)
    catch {tkScrollByPages $w.sb v $dir}
}

proc which {prog} {
    global env

    foreach dir [split $env(PATH) :] {
	if [file executable $dir/$prog] {
	    return $dir/$prog
	}
    }
    error "$prog: not found"
}

proc busy {} {
    # change to busy cursor
    catch {
	.main config -cursor watch
	update
    }
}

proc idle {} {
    # restore to idle cursor
    .main config -cursor ""
    update
}

proc display_or_suppress {s} {
    global display

    regexp "." $s key
    return "'$key' toggles display of $s messages (currently being\
	    [expr $display($s)?"displayed":"suppressed"])"
}

proc help {} {
    global help_font

    if [winfo exists .help] {
	destroy .help
	return
    }

    toplevel .help
    wm title .help "tkbiff help"
    wm iconname .help "tkbiff help"

    catch {wm resizable .help 0 0}

    position_window .help

    message .help.text -relief flat -font $help_font
    update_help_text

    button .help.ok -text "ok" -command {destroy .help} -bd 5 -relief raised
    pack .help.text
    pack .help.ok -fill x -padx 2 -pady 2
}

proc update_help_text {} {
    global maxheight display behavior mbox sleep audio
    global tkbiff_version gui_version

    # catch if help is not displayed
    catch {
	.help.text configure -text \
"tkbiff - by Don Libes, NIST - Version $tkbiff_version - GUI Version 
$gui_version.

tkbiff is yet another program to report new mail. \
Its claim to fame is a high degree of configurability. \
You can configure it to display messages, show pictures, play sounds,\
or order pizza.  Everything associated with the user interface can be changed\
- colors, key bindings, this message, everything! \
The configuration is usually stored in ~/.tkbiff. \
The remainder of this message describes the default configuration.

The default configuration is intended to allow very fast message\
browsing while taking very little screen real estate.

Fast browsing is provided by the following bindings:
Mouse button 1 pops up a message until button 1 is pressed again (or button 2 is 
released).
Mouse button 2 pops up a message until button 2 is released.
Mouse button 3 deletes a message from the display (or iconifies the window if no 
messages are shown).
By default, To:, Cc:, Resent-From:, and Date: are not displayed. \
Display these by holding down <Shift> while popping up the message.

'f' toggles between fonts. \
To conserve screen space, a small font is used initially.

Additional space is saved by using a scrollbar if enough messages\
arrive (currently $maxheight).  Change this by resizing the window -\
the new size\
will be used as the new limit. \
If autoscrolling is on and the last message header is already visible,\
the display will automatically scroll so that new messages become visible.
'a' toggles autoscroll (currently [expr $behavior(autoscroll)?"on":"off"]).

'i' toggles autodeiconify (currently [expr 
$behavior(autodeiconify)?"on":"off"]). \
If autodeiconify is on when new mail arrives, the main window is deiconified.
't' toggles autoraise (currently [expr $behavior(autoraise)?"on":"off"]). \
If autoraise is on when new mail arrives, the main window is raised.

Some mail clients use the \"Status:\" line in mail messages.  The\
following bindings make use of that:
'u' toggles display of unread messages (currently being\
		[expr $display(unread)?"displayed":"suppressed"]).
'r' toggles display of read messages (currently being\
		[expr $display(read)?"displayed":"suppressed"]).
'n' toggles display of new messages (currently being\
		[expr $display(new)?"displayed":"suppressed"]).

Sounds are nice when you're not looking at the screen but not\
so nice when you're having a meeting in your office.
's' toggles sound generation (currently [
  expr $behavior(sound)?"on, sound directed to [
    expr {[info exists audio(host)]?"remote host $audio(host)":"local host"}
  ]":"off"
]).

<Return> rereads your configuration file and checks for new mail - \
useful for testing new configurations rapidly.

'?' or 'h' toggles display of this help window. \
The status of any non-obvious toggles can be viewed in this help window. \
The toggles or any other parameters may be changed permanently by editing\
the configuration file.

Command-line options include the usual X and Tk things such as -display and\
-geometry. \
Also supported are -cf, -mbox, and -user to explicitly identify\
a different configuration file (currently $display(rcfile)),\
mail file (currently $mbox), user name (currently $display(whoami)),\
and -audiohost (currently $display(audiohost)), -audiorsh (currently\
$display(audiorsh)), and -audiorcmd (currently $display(audiorcmd))\
to direct sounds to a specific host.  See the man page for more\
information."
    }
}

       ---------------------------------------------------------
     Prasad V. S. Alavilli,            E.mail: alavilli@csar.uiuc.edu
  Center for Simulation of Advanced Rockets, ---------------------
3241 Digital Computer Laboratory, MC-258    Tel.: (217)-333-8553
1304 W. Springfield Avenue,                 Fax : (217)-333-1910
  Univ. of Illinois at Urbana-Champaign,     ---------------------
     Urbana, IL 61801              HTTP://www.csar.uiuc.edu/~alavilli
       ---------------------------------------------------------