global mf mfp

# the following need to be set by server before sourcing this file
#   mfp(debug)
#   mfp(tkmaillib) 
#   mfp(version)
#   mfp(setfile)
#   mfp(user)
#   mfp(homedir)

# $Header: /u/ra/raines/src/tk/tkmail2/viewer.tcl,v 1.4 1995/06/28 17:30:51 raines Exp $
###########################################################################
# 
# 
#    TkMail -- A Tk/Tcl interface to Mail
# 	    		by Paul Raines (raines@slac.stanford.edu)
# 
###########################################################################
#  -*- Mode: tcl-mode -*- 

# TYPES OF SETTINGS
#	N  Number
#	S  Short string (edit in entry widget)
#	L  Long string (edit in text widget)
#	K  Key sequence for bind command
#	B  Boolean
#	T  Tcl command
#	F  File which must exist
#	f  File with does not have to exist
#	D  Directory which must exist
#	U  Unix command
#	C  Text tag configure line
#	k  Code string or keyword (will be trimmed and lowercased)
#       a  Font configurable specification
#	1  A list where each item is a single entity
#	2  A list where each item is a pair of entities
#       c  A choice of short string options
# default user settings dynamically configurable
proc mfv:default-set {} {
  global mf mfp mfd env mfch

  # MAIL SETTINGS
  set mfd(mail-deliver) {U The UNIX command to deliver mail (reads standard input for address and text) }
  set mf(mail-deliver) "/usr/lib/sendmail -bm -odb -t"
  set mfd(mail-mbox) {f Pathname to your main mail box to incorporate to from mail spool }
  set mf(mail-mbox) $mfp(homedir)/mbox
  set mfd(mail-system) {f Pathname of file your mail is spooled to by your mail handler }
  set mf(mail-system) /usr/spool/mail/$mfp(user)
  set mfd(mail-directory) {D Pathname to directory contain your main cache of folders. Used for creating menus and for root of <Sender> files. }
  set mf(mail-directory) $mfp(homedir)/Mail
  set mfd(mail-tmpdir) {D Pathname of directory for temporary files }
  set mf(mail-tmpdir) /usr/tmp
  set mfd(mail-interval) {N Number of milliseconds between new mail and append checks }
  set mf(mail-interval) 10000
  set mfd(mail-autosave) {N Number of milliseconds between autosaves to \#folder\# file. Use number < 1 to disable. }
  set mf(mail-autosave) 600000
  set mfd(mail-auto-incorp) {B Whether to incorporate mail automatically when detected }
  set mf(mail-auto-incorp) 0
  set mfd(mail-debug) {B print usually ignored error messages to stderr}
  set mf(mail-debug) 0
  set mfd(mail-read-ask) {B Whether to ask to continue when reading in large messages }
  set mf(mail-read-ask) 0
  set mfd(mail-read-max) {N Maximum number of lines before asking to how much to fetch }
  set mf(mail-read-max) 1000
  set mfd(mail-alias-file) {f Name of file to read aliases from. Do a "Reread Alias File" after changing. Also set mail-alias-type correctly. }
  set mf(mail-alias-file) $mfp(homedir)/.mailrc
  set mfd(mail-alias-type) {c Type of alias file format - "bsd" or "elm". Do a "Reread Alias File" after changing. Also set mail-alias-file correctly. }
  set mfch(mail-alias-type) {bsd elm}
  set mf(mail-alias-type) {bsd}
  set mfd(mail-alias-case) {B Whether aliases are case sensitive}
  set mf(mail-alias-case) 1
  set mfd(mail-remove-empty) {B Whether to delete zero-length folders when closing them}
  set mf(mail-remove-empty) 1
  set mfd(mail-archive-folder) {f Name of default folder for archiving messages}
  set mf(mail-archive-folder) {@mfv:sender-default-hook 1}

  # VIEWER SETTTINGS
  set mfd(viewer-print) {U Printing command where %F is a placeholder for file to print, %D the mesg date, %S the subject, %W the From: field }
  set mf(viewer-print) "lpr %F"
  set mfd(viewer-bitmap-nomail) {F Bitmap to display when there is no new mail }
  set mf(viewer-bitmap-nomail) "/usr/include/X11/bitmaps/flagdown"
  set mfd(viewer-bitmap-mail) {F Bitmap to display when there is new mail }
  set mf(viewer-bitmap-mail) "/usr/include/X11/bitmaps/flagup"
  set mfd(viewer-beep-new) {T Tcl command to eval for new mail (in case you have a better one like blt_bell) }
  set mf(viewer-beep-new) {puts stderr "\007\007" nonewline}
  set mfd(viewer-beep-empty) {T Tcl command to eval for emtpy mailbox }
  set mf(viewer-beep-empty) {}
  set mfd(viewer-beep-error) {T Tcl command to eval for error notifications }
  set mf(viewer-beep-error) {puts stderr "\007\007" nonewline}
  set mfd(viewer-state) {c Set to either 'normal' or 'disabled' to allow message window editing }
  set mfch(viewer-state) {normal disabled}
  set mf(viewer-state) normal
  set mfd(viewer-pipe-dir) {D Pathname of directory to run piped UNIX commands in }
  set mf(viewer-pipe-dir) $mfp(homedir)
  set mfd(viewer-geom) {k Default geometry for mail viewers }
  set mf(viewer-geom) {}

  # HEADER LISTBOX
  set mfd(headlist-sort) {k Field name to sort summary list. Use 'normal' for no sort. Examples: sm-from, fullname, subject}
  set mf(headlist-sort) normal
  set mfd(headlist-reverse) {B Whether sorting should be done in reverse order }
  set mf(headlist-reverse) 0 
  set mfd(headlist-reverse-moveup) {B Whether current mesg should move up after a delete }
  set mf(headlist-reverse-moveup) 1
  set mfd(headlist-height) {N Number messages lines displayed in header listbox }
  set mf(headlist-height) 8
  set mfd(headlist-format) {S format of summary line in header listbox }
  set mf(headlist-format) {%-20.20F  %3m %2d %5h %4l  %-45.45s}

  # ISPELL SETTINGS
  set mfd(ispell-present) {B Whether your system has ispell }
  set mf(ispell-present) 1
  set mfd(ispell-binary) {f Name of ispell binary. Give full path if needed. Blank uses installed default}
  set mf(ispell-binary) {}
  set mfd(ispell-main-dictionary) {f Filename of main dictionary to use. Use "default" for default. }
  set mf(ispell-main-dictionary) {default}
  set mfd(ispell-personal-dictionary) {f Filename of personal dictionary to use. Use "default" for default. }
  set mf(ispell-personal-dictionary) {default}
  set mfd(ispell-addopts) {S Addition options to pass to ispell process.}
  set mf(ispell-addopts) {}

  # COMPOSE WINDOW SETTINGS
  set mfd(compose-icon-bitmap) {F Bitmap to display as icon for compose windows }
  set mf(compose-icon-bitmap) "/usr/include/X11/bitmaps/letters"
  set mfd(compose-geom) {k Default geometry of compose window }
  set mf(compose-geom) {}
  set mfd(compose-show-cc) {B Whether to show the Cc and Bcc fields in compose even if a simple Reply or they are empty}
  set mf(compose-show-cc) 1
  set mfd(compose-save-send) {B Whether to store a copy of the last sent message for possible restore }
  set mf(compose-save-send) 1
  set mfd(compose-alt-editor) {U Alternate editor command. If it is not an X windows editor, you must use xterm (i.e. xterm  -e vi %F). %F is file name placeholder }
  set mf(compose-alt-editor) "emacs %F"
  set mfd(compose-alt-auto) {B Whether to startup alternate editor automatically }
  set mf(compose-alt-auto) 0
  set mfd(compose-alternates) {1 Alternate email addresses to strip from Cc and Bcc }
  set mf(compose-alternates) ""
  set mfd(compose-addr-postfix) {S Possible postfix to add to addresses that don't include a @machine part. You must include the '@' character in the string }
  set mf(compose-addr-postfix) ""
  set mfd(compose-quick-forward) {B Whether the forward menu item and button should just ask for an address to forward to or should bring up a compose window }
  set mf(compose-quick-forward) 0
  set mfd(compose-require-subject) {B Whether a subject should be required or not on outgoing messages }
  set mf(compose-require-subject) 1
  set mfd(compose-from-field) {S Text to include on From: line. Your sendmail must be configured to allow this. }
  set mf(compose-from-field) {}
  set mfd(compose-fcc-folder) {f Pathname to file to record outgoing messages in with FCC }
  set mf(compose-fcc-folder) {}
  set mfd(compose-fcc-swap) {B Whether to record outgoing messages in FCC file as originating from the address you sent it to so that you see this address in the header list }
  set mf(compose-fcc-swap) 0
  set mfd(compose-fcc-forward) {B Whether forwarded mail should be recorded in FCC file }
  set mf(compose-fcc-forward) {1}

  # INSERTION OF MESSAGE SETTINGS
  set mfd(insert-prefix) {S String used to prefix included messages and files }
  set mf(insert-prefix) ">> "
  set mfd(insert-cite-format) {S Format for the cite string at top of included messages, i.e "%F says:" }
  set mf(insert-cite-format) {}
  set mfd(insert-forward-format) {S Format of line to place at top of forwarded messages }
  set mf(insert-forward-format) "---------- Forwarded message from %f on %D -----------"
  set mfd(insert-headers) {L Text to automatically put at top of every composition }
  set mf(insert-headers) {}
  set mfd(insert-signature) {f Name of .signature file to put at end of messages }
  set mf(insert-signature) $mfp(homedir)/.signature
  set mfd(insert-auto-sign) {B Whether to automatically append signature file when compose window is created}
  set mf(insert-auto-sign) 1
  set mfd(insert-always-sign) {B Whether to always append signature file before a message is sent if it hasn't been appended already and exists.}
  set mf(insert-always-sign) 1
  set mfd(insert-prefix-sig) {L Text to put before the signature file. Note that a final linefeed is important. }
  set mf(insert-prefix-sig) "--\n"
  set mfd(insert-encoder) {U Program to encode inserted files }
  set mf(insert-encoder) uuencode
  set mfd(insert-strip) {B Whether to automatically strip header of included messages }
  set mf(insert-strip) 1
  set mfd(insert-compress) {U Program to use to compress inserted files }
  set mf(insert-compress) compress
  set mfd(insert-compress-suffix) {S Suffix the compress program appends to compressed files }
  set mf(insert-compress-suffix) Z

  # MAIL HEADER SETTINGS
  set mfd(header-retain) {1 Header fields to retain in display. Overrides Headers to Strip }
  set mf(header-retain) {}
  set mfd(header-strip) {1 Header fields to strip out of viewed messages. }
  set mf(header-strip) {Received Status Message-Id}
  set mfd(header-config) {C Text properties to configure headers with in viewer display. }
  set mf(header-config) "-underline 1"

  # MENU SETTINGS
  set mfd(menu-folders-max) {N Maximum number of folders listed in menus. Do a "Rebuild Folder Menus" after changing. }
  set mf(menu-folders-max) 25
  set mfd(menu-depth-max) {N Maximum depth of pull right menus for folders. Do a "Rebuild Folder Menus" after changing. }
  set mf(menu-depth-max) 5
  set mfd(menu-folders-ignore) {1 Filenames in mf(mail-directory) directory to not put in menus. Shell glob sytax accepted.  Do a "Rebuild Folder Menus" after changing.}
  set mf(menu-folders-ignore) {}
  set mfd(menu-quick-send) {1 List of common addresses for composing to put in menu. Set to @aliases to use your alias list. }
  set mf(menu-quick-send) {}
  set mfd(menu-recent-max) {N Maximum number of folders to put in Recent menus }
  set mf(menu-recent-max) 8
  set mfd(menu-default-new) {B Whether folder selections by name at the bottom of Folder menu should create a new viewer. }
  set mf(menu-default-new) 0

  # BIND
  set mfd(bind-emacs) {B Whether or not to use emacs type bindings. TkMail must be restarted. }
  set mf(bind-emacs) 1
  set mfd(bind-alt-key) {K Key to press in order to access menu accelarators when Alt can't be used. TkMail must be restarted. }
  set mf(bind-alt-key) <Control-c>
  set mfd(bind-use-meta) {B Whether or not to use Meta modifier for bindings. TkMail must be restarted.}
  set mf(bind-use-meta) 1
  set mfd(bind-use-esc) {B Whether or not to use Esc prefix for emacs bindings. TkMail must be restarted.}
  set mf(bind-use-esc) 1

  # MISC
  set mfd(disp-left-scroll) {B Whether to place scrollbars on left side of scrollable windows. Will only affect new windows till restart. }
  set mf(disp-left-scroll) 1
  set mfd(notify-popup) {B Whether to popup a window listing new messages when they arrive }
  set mf(notify-popup) 0
  set mfd(notify-format) {S Format of summary line in notify popup listbox }
  set mf(notify-format) {%-16.16F  %4l  %-45.45s}
  set mfd(disp-default-fixed) {B Whether to use fixed font by default in viewer and compose}
  set mf(disp-default-fixed) 0
  set mfd(option-editor-geom) {k Default geometry of option editor window }
  set mf(option-editor-geom) {}

  # MIME
  set mfd(mime-parse) {B Whether to do MIME parsing }
  set mf(mime-parse) 0
  set mfd(mime-external-default) {S Default command to run on mime parts not handled internally}
  set mf(mime-external-default) {metamail -b -d -q -x -f %A -s %S -m tkmail -c %T}
  set mfd(mime-external-viewers) {2 List of content-type / program pairs}
  set mf(mime-external-viewers) { \
    {Audio {cat %F > /dev/audio}} \
    {Application/Octet-Stream @prompt} \
    {Application/PostScript ghostview} }
  set mfd(mime-font-default) {a Font as {foundry family fntsize} for deriving fonts for mime}
  set mf(mime-font-default) {adobe helvetica 12}
  set mfd(mime-font-fixed) {a Font as {foundry family fntsize} for deriving fixed fonts for mime}
  set mf(mime-font-fixed) {adobe courier 12}
}


###############################################################
# DO NOT EDIT BELOW THIS LINE

# Private program variables
# signal Perl side is free
   set mfp(perlfree) 1
# name of starting folder file
   set mfp(file) ""
# the main toplevel viewer widget
   set mfp(top) .mf0
# the current toplevel widget (any type)
   set mfp(curtop) .mf0
# the current viewer widget
   set mfp(curview) .mf0
# temp text processing widget
   set mfp(tmptxt) .tmptxt
# list of toplevel viewers
   set mfp(toplist) {}
# header list holding & displaying widget
   set mfp(head) head.list
# message holding & displaying widget
   set mfp(mesg) mesg.txt
# header status label
   set mfp(hstat) stat.folder
# message status label
   set mfp(mstat) stat.mesg
# save of text of last sent message
   set mfp(savesendtxt) ""
# settings for file insertion
   set mfp(ins_compress) 0
set mfp(ins_prefix) 0		
set mfp(ins_encode) 0
# list of folders in Recent menus
   set mfp(recentlist) ""
# index of last permanent item in Folder menu
   set mfp(fmenulast) 0
# index of last permanent item in Mail menu
   set mfp(mmenulast) 0
# list of widgets to put watch in for waiton and waitoff
   set mfp(waitlist) {}
# whether mailbox is empty
   set mfp(nomail) 1
# number messages auto-incorped with no mbox opened
   set mfp(auto-incorped) 0
# prefix to Escape for cancels (need by emacs users)
   set mfp(cancel) ""
# additional alternate address to remove beyond user setting
   set mfp(add-alt) {}
# whether to skip print prompting
  set mfp(print-noprompt) 0
# keeps track of last read message in each open folder
  set mfp(last-mesg) {}
# need this one mfopt variable here
  set mfopt(modified) 0

# MIME
# list of attachable types
set mfp(mime-attach-types) {text application image audio video}
set mfp(mime-subtypes,text) {plain enriched}
set mfp(mime-subtypes,application) {octet-stream postscript}
set mfp(mime-subtypes,image) {gif jpeg}
set mfp(mime-subtypes,audio) {basic}
set mfp(mime-subtypes,video) {mpeg}

### CONVENIENCE ROUTINES FOR LATER REMOVAL TO LIBRARY ######

if {[catch "infox version"]} {
  proc lempty { l } { return [expr ![string length $l]] }
  proc keylget { lvar key {rvar 0} } {
    upvar $lvar klist
    set check 0
    if {$rvar != 0} {
      set check 1
      if {[string length $rvar]} {upvar $rvar ret}
    }
    foreach pair $klist {
      if {[lindex $pair 0] == $key} {
	set ret [lindex $pair 1]
	if {$check} { return 1 } else {return $ret}
      }
    }
    if {$check} { 
      return 0 
    } else { error "No key named $key in $lvar" }
  }
  proc keylset { lvar key val } {
    upvar $lvar klist
    set ndx 0
    if {[info exists klist]} {
      foreach pair $klist {
	if {[lindex $pair 0] == $key} {
	  set klist [lreplace $klist $ndx $ndx "$key {$val}"]
	  return {}
	}
	incr ndx
      }
    }
    lappend klist "$key {$val}"
    return {}
  }
}

proc llast {list} {
  return [expr [llength $list]-1]
}

proc quotespecial { str } {
  regsub -all {\"} $str {\"} str
  return $str
}

proc unquotespecial { str } {
  regsub -all {\\\"} $str {"} str
  return $str
}

# get temp file
proc tmpfile { {str tmp} {dir ""} } {
    global env

    if {![string length $dir] || ![file isdirectory $dir]} {
        if {[info exists env(TMPDIR)]} {
            set dir $env(TMPDIR)
        } else {
            set dir /tmp
        }
    }

    if {![file writable $dir]} {
        error "$dir is not a writable directory"
    }

    set cnt 0
    set tfile [format "%s%04d" $str $cnt]
    while {[file exists $dir/$tfile]} {
        incr cnt
        if {$cnt>9999} {return 0}
        set tfile [format "%s%04d" $str $cnt]
    }
    return $dir/$tfile
}

# setup up menus to allow mouse follow and F10 key operation
proc tk_autoMenuBar { frm } {
  
  set pinfo [pack info $frm]
  set mlist ""
  for {set i 0} { $i < [llength $pinfo]} {incr i 2} {
    lappend mlist [lindex $pinfo $i]
  }
  
  eval "tk_menuBar $frm $mlist"
  
}

############################################################
proc mfv:noop { args } {
  # do nothing. Returns <args> as string separated by spaces
  return [join $args " "]
}

proc mfv:stack-trace { {adj 0} } {
  set stack {}
  set lvl [expr [info level]-1-$adj]
  while {$lvl > 0} {
    append stack [info level $lvl]\n
    incr lvl -1
  }
  return $stack
}

proc mfv:see-stack-trace {{w {}}} {
  global mfp
  if [lempty $w] {
    set w [ut:simpletext -title "TkMail: Stack Trace" \
	       -text [mfv:stack-trace 1]]
  } else {
    $w.txt delete 1.0 end
    $w.txt insert end $mfp(stack-trace)
  }
  focus $w.txt
  return 0
}

proc mfv:error-mesg { str {master {}} } {
  # popup a error message using <str> and eval mf(viewer-beep-error.
  # if <master> given, use it to place dialog 
  global mf mfp
  
  eval $mf(viewer-beep-error)
  if {![winfo exists $master] && [winfo exists $mfp(curtop)]} { set master $mfp(curtop) }
  mfv:log-mesg {} "ERROR: $str" 
  mfv:wait-off

  if {[winfo exists $master]} {
    if {[string first "\n" $str] > -1} {
      set mfp(stack-trace) [mfv:stack-trace 1]
      set w [ut:simpletext -title "TkMail: ERROR" -text $str \
		 -buttons {{OK} {{Stack Trace} mfv:see-stack-trace %W}} \
		 -grab 1 -master $master]
      focus $w.txt
      tkwait window $w
    } else {
      if {![ut:getok -title "TkMail: ERROR" -prompt $str -bitmap error \
		-nolabel "Stack Trace"]} {
	mfv:see-stack-trace
      }
    }
  } else {
    puts stderr "ERROR: $str"
  }
}

proc mfv:check-old-settings { } {
  global mf mfp

  set warnmesg {}
  if [info exists mf(mail-record)] {
    append warnmesg "	mf(mail-record) -> mf(compose-fcc-folder)\n"
  }
  if [info exists mf(compose-fcc-default)] {
    append warnmesg "	mf(compose-fcc-default) -> mf(compose-fcc-folder)\n"
  }
  if [info exists mf(compose-fcc-list)] {
    append warnmesg "	mf(compose-fcc-list) -> mf(compose-fcc-folder)\n"
  }
  if [info exists mf(compose-fcc-bysender)] {
    append warnmesg "	mf(compose-fcc-bysender) -> mf(compose-fcc-folder)\n"
  }
  if [info exists mf(compose-fcc-directory)] {
    append warnmesg "	mf(compose-fcc-directory) -> mf(compose-fcc-folder)\n"
  }
  if [info exists mf(mail-record-swap)] {
    append warnmesg "	mf(mail-record-swap) -> mf(compose-fcc-swap)\n"
  }
  if [info exists mf(mail-record-forward)] {
    append warnmesg "	mf(mail-record-forward) -> mf(compose-fcc-forward)\n"
  }
  if [info exists mf(menu-sender-full)] {
    append warnmesg "	mf(menu-sender-full) -> mf(mail-archive-folder)\n"
  }
  if [info exists mf(menu-sender-list)] {
    append warnmesg "	mf(menu-sender-list) -> mf(mail-archive-folder)\n"
  }
  if [string length $warnmesg] {
    mfv:error-mesg "The following old settings need to be updated or removed from your $mfp(setfile) file:\n\n$warnmesg"
  }
}

proc mfv:schedule-check {} {
  # check for new mail and schedule a new mail check
  global mf mfp

  if $mfp(perlfree) {
    if {[set newm [mfdb:mail-check $mf(mail-system)]]} {
      eval $mf(viewer-beep-new)
      mfv:log-mesg {} "$newm new mail messages have arrived!"
      foreach top $mfp(toplist) {
	$top.$mfp(mstat) configure -text "New Mail has arrived!"
      }
      if {$mf(notify-popup)} { mfv:notify-popup $newm }
      if {$mf(mail-auto-incorp)} { mfv:auto-incorp }
    } else {
      eval $mf(viewer-beep-empty)
    }
    if {[mfdb:file-test -s $mf(mail-system)] || $mfp(auto-incorped)} {
      if {[file exists $mf(viewer-bitmap-mail)] && $mfp(nomail)} {
	 wm iconbitmap $mfp(top) "@$mf(viewer-bitmap-mail)"
      }
      set mfp(nomail) 0
    } else {
      if {[file exists $mf(viewer-bitmap-nomail)] && !$mfp(nomail)} {
	  wm iconbitmap $mfp(top) "@$mf(viewer-bitmap-nomail)"
      }
      set mfp(nomail) 1
    }    
  }
  
  # check if person thought it was for seconds and not milliseconds
  if {$mf(mail-interval) < 1000} {set mf(mail-interval) [expr $mf(mail-interval)*1000]}
  after $mf(mail-interval) mfv:schedule-check
}

proc mfv:check-append { folder } {
  # check for append to folder and schedule a new check
  global mf mfp

  if $mfp(perlfree) {
    set vlist {}
    foreach viewer $mfp(toplist) {
      if {[mfdb:same-files $folder [keylget mfp($viewer) file]]} {
	lappend vlist $viewer
      }
    }
    if {[lempty $vlist]} return

    if {[set newm [mfdb:check-append $folder]]} {
      eval $mf(viewer-beep-new)
      mfv:log-mesg {} "$newm messages discovered appended to $folder!"
      foreach viewer $vlist {
	$viewer.$mfp(mstat) configure -text "New Mail has been appended!"
	mfv:update-summary $viewer $viewer $folder
      }
    }
  }
  
  # check if person thought it was for seconds and not milliseconds
  if {$mf(mail-interval) < 1000} {set mf(mail-interval) [expr $mf(mail-interval)*1000]}
  after $mf(mail-interval) "mfv:check-append {$folder}"
}

proc mfv:autosave { } {
  # check for append to folder and schedule a new check
  global mf mfp

  if {$mf(mail-autosave) < 1} return

  if $mfp(perlfree) {
    set savetext [lindex [$mfp(curview).$mfp(mstat) configure -text] 4]
    $mfp(curview).$mfp(mstat) configure -text "Autosaving ..."
    mfv:wait-on

    set flist {}
    foreach viewer $mfp(toplist) {
      if {[lsearch $flist [keylget mfp($viewer) file]] == -1} {
	lappend flist [keylget mfp($viewer) file]
      }
    }

    foreach folder $flist {
      mfdb:folder-backup $folder
    }

    $mfp(curview).$mfp(mstat) configure -text $savetext
    mfv:wait-off
  }

  # check if person thought it was for seconds and not milliseconds
  if {$mf(mail-autosave) < 1000} {set mf(mail-autosave) [expr $mf(mail-autosave)*1000]}
  after $mf(mail-autosave) "mfv:autosave"

}

proc mfv:notify-popup-clear { } {
  set w .mf_notify
  if {[winfo exists $w]} {
    $w.notify.txt configure -state normal
    $w.notify.txt delete 1.0 end
    $w.notify.txt configure -state disabled
    wm withdraw $w
  }
}

proc mfv:notify-popup { newm } {
  global mf mfp mfdb
  set w .mf_notify
  
  mfv:wait-on
  if {[set stat [mfdb:folder-open $mf(mail-system) 1]]} {
    mfv:error-mesg $mfdb(last-error) $top
    mfv:wait-off
    return $stat
  }

  mfdb:option-set headlist-format $mf(notify-format)
  set stat [mfdb:folder-summary $mf(mail-system) lsum $mf(headlist-reverse) normal]
  mfdb:option-set headlist-format $mf(headlist-format)
  mfdb:folder-close $mf(mail-system)
  if {$stat} {
    mfv:error-mesg $mfdb(last-error) $top
    mfv:wait-off
    return $stat
  }

  if { ![winfo exists $w] } {

    toplevel $w -class MailNotify
    wm title $w "TkMail v$mfp(version) New Mail"
    wm minsize $w 200 60
    wm protocol $w WM_DELETE_WINDOW "wm withdraw $w"
    wm withdraw $w

    frame $w.notify
    scrollbar $w.notify.scr -command "$w.notify.txt yview" -relief raised
    text $w.notify.txt -cursor left_ptr -relief sunken -wrap none \
	-yscroll "$w.notify.scr set" -width 45 -height 8
    pack $w.notify.scr -side left -fill y
    pack $w.notify.txt -side left -expand true -fill both

    frame $w.bb
    button $w.bb.b1 -text Incorp \
	-command "global mfp; mfv:mail-incorp \$mfp(curview) 0; mfv:notify-popup-clear"
    button $w.bb.b2 -text Dismiss -command "wm withdraw $w"
    pack $w.bb.b1 $w.bb.b2 -side left -expand true -fill x

    pack $w.notify -side top -expand true -fill both
    pack $w.bb -side top -expand true -fill x

  }

  if {![winfo ismapped $w] && [winfo exists $mfp(curtop)]} {
    set geom [split [winfo geom $mfp(curtop)] x]
    set twidth [lindex $geom 0]
    set geom [split [lindex $geom 1] +]
    wm geom $w \
	+[expr [lindex $geom 1]+$twidth/3]+[expr [lindex $geom 2]+[lindex $geom 0]/2]
    wm deiconify $w; raise $w
  }

  set len [llength $lsum]
  set lsum [lrange $lsum [expr $len-$newm] end]

  $w.notify.txt configure -state normal
  # $w.notify.txt delete 1.0 end
  foreach line $lsum {
    $w.notify.txt insert end "[string range $line 6 end]\n"
  }
  $w.notify.txt configure -state disabled

  mfv:wait-off
  return 0
}

proc mfv:setup-folder { top folder {ndx {}}} {
  # Open up <folder> in viewer <top> and set view to message at <ndx>
  # Close old folder associated with viewer if not shared with another
  global mf mfp mfdb
  
  if {[lempty $folder]} { return 1}
  mfv:wait-on
  
  # get old folder and if a re-read, close it now
  if [keylget mfp($top) file oldfile] {
    keylset mfp(last-mesg) $oldfile [mfv:head-to-num $top [mfv:cursingle $top]]
    if {[mfdb:same-files $folder $oldfile]} {
      mfdb:folder-close $oldfile
      set mfp($top,delmesg) {}
      set oldfile {}
    }
  } else { set oldfile {} }

  # make sure mbox exists
  if {$folder == $mf(mail-mbox) && ![file exists $folder]} {
    if [catch "exec touch $mfp(file)" res] {
      mfv:error-mesg "$folder does not exists and cannot be created"
      mfv:wait-off
      return 255
    }
  }

  if {[set stat [mfdb:folder-open $folder]]} {
    mfv:error-mesg $mfdb(last-error) $top
    mfv:wait-off
    return $stat
  }
  if {$folder == $mf(mail-mbox)} {
    wm iconname $top $mfp(user)
  } else {
    wm iconname $top [file tail $folder]
  }
  pdebug "Folder $folder opened\n"

  # close previous folder in viewer if not already closed above
  if {[string length $oldfile]} {
    mfdb:folder-close $oldfile
    set mfp($top,delmesg) {}
  }

  if {[set stat [mfdb:folder-summary $folder lsum $mfp($top,reverse) $mfp($top,sort)]]} {
    mfv:error-mesg $mfdb(last-error) $top
    mfv:wait-off
    return $stat
  }
  $top.$mfp(head) delete 0 end
  eval "$top.$mfp(head) insert end $lsum"

  # set new viewer specific database items
  set mfp($top,delmesg) {}
  keylset mfp($top) file $folder
  keylset mfp($top) curnum 0
  set mesgnum [$top.$mfp(head) size]
  keylset mfp($top) mesgnum $mesgnum
  mfv:check-append $folder
  
  $top.$mfp(mesg) configure -state normal
  $top.$mfp(mesg) delete 1.0 end
  bind_cleanup $top.$mfp(mesg)
  $top.$mfp(mesg) configure -state $mf(viewer-state)

  set folderstr $folder
  if {[string length $folderstr] > 20 && [string first "/" $folderstr] > -1} {
    set folderstr "...$folderstr"
    while {[string length $folderstr] > 20 && [string first "/" $folderstr] > -1} {
      set folderstr [string range $folderstr 4 end]
      set folderstr [string range $folderstr [string first "/" $folderstr] end]
      set folderstr "...$folderstr"
    }
  }
  $top.$mfp(hstat) configure -text $folderstr
  
  pdebug "$mesgnum messages\n"
  
  if {$mesgnum==0} {
    $top.$mfp(mstat) configure -text "Message 0 out of 0"
    mfv:wait-off
    return 0
  }

  if {[lempty $ndx] && [keylget mfp(last-mesg) $folder lastmesg]} {
    mfv:goto-mesg $top $lastmesg
  } else {
    if {[lempty $ndx]} { set ndx [mfv:head-to-num $top 0] }
    if { $ndx < 1 } {
      mfv:goto-newest $top
    } else {
      mfv:goto-mesg $top $ndx
    }
  }
  catch "$top.$mfp(head) yview -pickplace [mfv:cursingle $top]"  

  mfv:wait-off
  return 0
}

proc mfv:display-mesg { top ndx } {
  # Display message <ndx> from folder associated with viewer <top> 
  # <button> is button associated with selection if any
  global mf mfp mfdb mfdb_hdr

  # release delayed binding of button release
  bind $top.$mfp(head) <Any-ButtonRelease> " "

  set tw $top.$mfp(mesg)
  $tw configure -state normal
  $tw delete 1.0 end
  bind_cleanup $tw
  
  if {![string length $ndx]} {
    $top.$mfp(mstat) configure -text "No messages"
    return
  }

  # verify ndx is in range
  if {$ndx < 1 || $ndx > [keylget mfp($top) mesgnum]} {
    $top.$mfp(mstat) configure \
	-text "Message $ndx out of range"
    $tw configure -state $mf(viewer-state)
    return 0
  }
  
  if {[mfdb:mesg-headers [keylget mfp($top) file] $ndx $tw]} {
    mfv:error-mesg $mfdb(last-error) $top
    return 0
  }

  mfv:mesg-set-sender $top

  keylget mfdb_hdr($tw) mesg-lines msglines
  if {$mf(mail-read-ask) && $mf(mail-read-max) < $msglines} {
    set fromline UNKNOWN
    set subjline UNKNOWN
    catch "keylget mfdb_hdr($tw) from fromline"
    catch "keylget mfdb_hdr($tw) subject subjline"
    $tw insert 1.0 "From: $fromline\n"
    $tw insert 2.0 "Subject: $subjline\n\n"
    $tw insert 4.0 "     Message is $msglines lines long. Click here to view whole message."
    $tw tag add seelong 4.5 "4.5 lineend"
    $tw tag bind seelong <1> \
	"mfv:wait-on; mfv:simple-display-mesg $top.$mfp(mesg) $ndx; mfv:wait-off"
  } else {
    mfv:wait-on
    if {[mfdb:mesg-to-text [keylget mfp($top) file] $ndx $tw 1.0]} {
      mfv:error-mesg $mfdb(last-error) $top
      mfv:wait-off; return 0
    }
    if {[info proc mfv:display-mesg-hook]!=""} {
      mfv:display-mesg-hook $top $tw
    }  
    mfv:wait-off
  }

  # notify user of message read
  $top.$mfp(mstat) configure \
      -text "Message $ndx out of [keylget mfp($top) mesgnum]"
  $tw configure -state $mf(viewer-state)
  
  # remove possible unread status symbol from listbox
  set tndx [expr [mfv:cursingle $top]+1]
  if {$tndx > 0} {
    $top.$mfp(head)_text insert $tndx.2 " "
    $top.$mfp(head)_text delete $tndx.1
  }
  return 1
}

proc mfv:extract-address { field {num 0} } {
  set field [mfv:strip-comment $field 0]
  if {[string first , $field] != -1} {
    set field [lindex [split $field ,] $num]
  }
  if {[regexp {<([^, <>]+)>} $field trash res]} {
    set field $res
  }
  if {[string first ":" $field] != -1} {
    set field [lrange [split $field ":"] 1 end]
  }
  return [string trim $field]
}

proc mfv:get-from { tw } {
  # Return the user addresss from From: field of text widget <tw>
  # Actually tries Return-Path, then Reply-To, then From, then sm-from
  # Assumes mfdb_hdr has been set for <tw>
  global mf mfp mfdb_hdr

  set curfrom ""
  foreach field { reply-to from return-path sm-from } {
    keylget mfdb_hdr($tw) $field curfrom
    if {![lempty $curfrom]} break
  }

  set curfrom [mfv:strip-comment $curfrom 0]
  if {[regexp {<([^, <>]+)>} $curfrom trash res]} {
    set curfrom $res
  }

  return $curfrom
}

proc mfv:viewer-get-field { top fieldlist } {
  global mf mfp mfdb_hdr

  set res {}
  foreach field $fieldlist {
    set field [string tolower $field]
    keylget mfdb_hdr($top.$mfp(mesg)) $field res
    if {![lempty $res]} break
  }
  return $res
}

proc mfv:mesg-set-sender {top} {
  global mf mfp

  if {[string range $mf(mail-archive-folder) 0 0] == "@"} {
    set archproc [string range $mf(mail-archive-folder) 1 end]
    if [catch "set mfp($top,sender) \[$archproc $top\]" res] {
      mfv:error-mesg $res $top
      set mfp($top,sender) {}
    }
  } else {
    set mfp($top,sender) $mf(mail-archive-folder)
  }
  if [lempty $mfp($top,sender)] {
    set mfp($top,sender) [mfv:sender-default-hook 1 $top]
  }
  $top.menu.mesg.m.move entryconfigure 2 -label [file tail $mfp($top,sender)]
  $top.bb.move.m entryconfigure 2 -label [file tail $mfp($top,sender)]
  $top.menu.mesg.m.copy entryconfigure 2 -label [file tail $mfp($top,sender)]
}

proc mfv:sender-default-hook {short top} {
  global mf mfp

  set fromname [mfv:viewer-get-field $top \
		    {reply-to from return-path sm-from}]
  set fromname [string tolower [mfv:extract-address $fromname 0]]

  if {$short} {
    return [lindex [split $fromname "@"] 0]
  }
  return $fromname
}

proc mfv:show-full-headers { top } {
  global mf mfp mfdb

  set tw $top.$mfp(mesg)
  set range [$tw tag nextrange headers 1.0]
  set start [lindex $range 0]
  set stop [lindex $range 1]
  keylget mfp($top) curnum mesg
  $tw configure -state normal
  $tw delete $start $stop
  mfv:wait-on
  if {[mfdb:mesg-full-headers [keylget mfp($top) file] $mesg $tw 1.0]} {
    mfv:error-mesg $mfdb(last-error) $top
    mfv:wait-off; return 0
  }
  mfv:wait-off
  $tw tag add headers 1.0 insert
  $tw configure -state $mf(viewer-state)
  return 1
}

proc mfv:strip-comment { str {keepq 1} } {
  # Strip comments from header fields string <str> according to rfc822 rules
  # if <keepq> true, keep quoted parts
  
  set fstrip ""
  set nest 0
  set mode norm
  for {set j 0} {$j < [string length $str]} {incr j} {
    set char [string index $str $j]
    case $char {
      {"} {
	case $mode {
	  {norm} {
	    set mode quot
	    if {$keepq} {lappend fstrip $char}
	  }
	  {quot} {
	    set mode norm
	    if {$keepq} {lappend fstrip $char}
	  }
	}
      }
      {(} {
	case $mode {
	  {norm comm} {
	    set mode comm
	    incr nest
	  }
	  {quot} {
	    if {$keepq} {lappend fstrip $char}
	  }
	}
      }
      {)} {
	case $mode {
	  {comm} {
	    incr nest -1
	    if {$nest < 0} {mfv:error-mesg "Nesting error stripping >$str<"}
	    if {$nest == 0} {set mode norm}
	  }
	  {quot} {
	    if {$keepq} {lappend fstrip $char}
	  }
	  {norm} {
	    mfv:error-mesg "End comment error stripping >$str<"
	  }
	}
      }
      {[\\]} {
	case $mode {
	  {quot} {
	    incr j
	    if {$keepq} {
	      lappend fstrip $char
	      lappend fstrip [string index $str $j]
	    }
	  }
	  {comm} {
	    incr j
	  }
	  {norm} {
	    lappend fstrip $char
	    mfv:error-mesg "Backslash error stripping >$str<"
	  }
	}
      }
      default {
	case $mode {
	  {norm} {
	    lappend fstrip $char
	  }
	  {quot} {
	    if {$keepq} {lappend fstrip $char}
	  }
	}
      }
    }
  }
  if {$mode != "norm" || $nest != 0} {
    mfv:error-mesg "Quoted string or comment did not end in: $str"
  }
  return [string trim [join $fstrip {}]]
}

proc mfv:cursingle { top } {
  global mfp

  set ndx [$top.$mfp(head) cursingle]
  if {[lempty $ndx]} {
    set ndx [keylget mfp($top) curnum]
    incr ndx -1
    if {$ndx > [$top.$mfp(head) size]} {return -1}
  }
  return $ndx
}

proc mfv:simple-display-mesg {tw ndx} {
  # Simple placement of message <ndx> in viewer <top>
  global mf mfp

  set top [winfo toplevel $tw]
  $tw configure -state normal
  $tw delete 1.0 end
  mfv:wait-on
  if {[mfdb:mesg-to-text [keylget mfp($top) file] $ndx $tw 1.0]} {
    mfv:error-mesg $mfdb(last-error) $top
    mfv:wait-off; return 0
  }

  if {[info proc mfv:display-mesg-hook]!=""} {
    mfv:display-mesg-hook $top $tw
  }  

  mfv:wait-off
  $tw configure -state $mf(viewer-state)
}

proc mfv:reload-mesg { top } {
  global mf mfp
  
  set newcur [mfv:head-to-num $top [mfv:cursingle $top]]
  if {$newcur < 1} { 
    set newcur [keylget mfp($top) curnum]
    if {$newcur < 1} {return 0}
  }
  mfv:display-mesg $top $newcur
  keylset mfp($top) curnum $newcur
  catch "$top.$mfp(head) yview -pickplace [mfv:cursingle $top]"
  focus $top.$mfp(mesg)
  return 1
}

proc mfv:deselect-mesg { top tndx } {
  global mf mfp

  set reset 0
  if {$tndx == [$top.$mfp(head) cursingle]} {set reset 1}
  $top.$mfp(head) select clear $tndx

  if {$reset} {
    set newcur [$top.$mfp(head) cursingle]
    if {![string length $newcur]} { set newcur $tndx }
    mfv:select-mesg $top from $newcur 0 1 keep
  }
}

proc mfv:select-mesg { top mode tndx {dwait 0} {force 0} {keep {}}} {
  # Select message at index <tndx> in the header list of <top> according to <mode>
  # if <dwait>, wait till mouse-button is released
  global mf mfp
  
  eval "$top.$mfp(head) select $mode $tndx $keep"
  if {$mode != "from"} { return 1}

  set newcur [mfv:head-to-num $top [mfv:cursingle $top]]
  if {!$newcur} {
    $top.$mfp(head) select from $tndx
    set newcur [mfv:head-to-num $top [mfv:cursingle $top]]
    if {!$newcur} {return 0}
  }
  
  # primary selection has changed
  if {$newcur != [keylget mfp($top) curnum] || $force} {
    if {$dwait} {
      bind $top.$mfp(head) <Any-ButtonRelease> "mfv:display-mesg $top $newcur"
    } else {
      mfv:display-mesg $top $newcur
    }
    keylset mfp($top) curnum $newcur
    $top.$mfp(head) yview -pickplace $tndx
  }
  focus $top.$mfp(mesg)
  return 1
}

proc mfv:head-to-num { top tndx } {
  # Translate header list index <tndx> of <top> to message number
  global mf mfp
  
  if {[lempty $tndx]} {return 0}
  
  if {[regexp {[0-9][0-9]*} \
      [$top.$mfp(head) get $tndx] ndx]} {
    return [string trim $ndx]
  } else {
    return 0
  }
  
}

proc mfv:wait-on { } {
  # Set cursor to watch bitmap for windows in mfp(waitlist)
  global mf mfp
  
  set cnt 0
  foreach w $mfp(waitlist) {
    if {[winfo ismapped [winfo toplevel $w]]} {
      if {[set tcur [lindex [$w configure -cursor] 4]] != "watch"} {
	set mfp($w,cursor) $tcur
	$w configure -cursor watch
	incr cnt
      }
    }
  }
  if {$cnt} {update idletasks}
}

proc mfv:wait-off { } {
  # Set cursor to default for windows in mfp(waitlist)
  global mf mfp

  set cnt 0
  foreach w $mfp(waitlist) {
    if {[winfo ismapped [winfo toplevel $w]]} { incr cnt }
    if {$mfp($w,cursor) != ""} {
      $w configure -cursor $mfp($w,cursor)
    }
  }
  if {$cnt} {update idletasks}
}

# parse user alias file
proc mfv:parse-alias-file { {top ""} } {
  # parse user's alias file mf(mail-alias-file) according to mf(mail-alias-type)
  global mf mfp env
  
  set mfp(aliasnames) ""
  set mfp(aliasdesc) ""
  set mfp(aliasaddr) ""
  
  if {[lempty $mf(mail-alias-file)]} { return 0}

  foreach afile $mf(mail-alias-file) {
    if {[mfdb:file-test -e $afile]} {

      case $mf(mail-alias-type) {
	{bsd} { mfv:parse-bsd-aliases $afile $top }
	{elm} { mfv:parse-elm-aliases $afile $top }
	default {
	  mfv:error-mesg \
	      "Unknown alias file type: $mf(mail-alias-type)!" $top
	  return 0
	}
      }
    }
  }

  return 0
}

proc mfv:parse-bsd-aliases { afile {top ""} } {
  # parse BSD style alias file <afile>
  global mf mfp mfdb env
  
  if {![file exists $afile]} return
  if {[mfdb:file-to-var $afile filetext]} {
    mfv:error-mesg $mfdb(last-error) $top
    return 0
  }
  
  # concat continued lines (ones that end with backslash)
  set filetext [join $filetext \n]
  regsub -all "\\\\\[ \t\]*\n\[ \t\]*" $filetext { } filetext
  foreach tline [split $filetext \n] {
    set line [string trim $tline]
    set tmp [lindex $line 0]
    case $tmp {
      {a alias g group} {
	lappend mfp(aliasnames) [lindex $line 1]
	lappend mfp(aliasdesc) {}
	set realaddr [string trim [lrange $line 2 end]]
	if [regexp {^['"](.*)['"]$} $realaddr trash stripped] {
	  lappend mfp(aliasaddr) $stripped
	} else {
	  lappend mfp(aliasaddr) $realaddr
	}
      }
      {alt alternates} {
	#set mf(compose-alternates) [lrange $line 2 end]
      }
    }
  }
  return 1
}

# parse an elm alias
proc mfv:parse-elm-aliases { afile {top ""} } {
  # parse Elm style alias file <afile>
  global mf mfp env
  
  if {![file exists $afile]} return
  if {[mfdb:file-to-var $afile filetext]} {
    mfv:error-mesg $mfdb(last-error) $top
    return 0
  }
  
  # concat continued lines (ones that start with space or tab)
  set filetext [join $filetext \n]
  regsub -all "\n\[ \t\]+" $filetext { } filetext
  foreach tline [split $filetext \n] {
    set line [string trim $tline]
    if {[string index $line 0] == "#"} continue
    # strip space around equals signs:
    if {[regsub -all { *= *} $line {=} line]} {
      set topfields [split $line {=}]
      # Position 0 -- aliases
      # Position 1 -- description
      # Position 2 -- email addresses
      regsub -all {[, ]+} [lindex $topfields 0] { } anamelist
      foreach aname $anamelist {
	lappend mfp(aliasnames) $aname
	lappend mfp(aliasdesc) [lindex $topfields 1]
	lappend mfp(aliasaddr) [join [lrange $topfields 2 end] =]
      }
    }
  }
  return 1
}

proc mfv:menu-create { m } {
  # create new menu <m> only if it doesn't already exist
  if {[winfo exists $m]} {
    $m delete 0 last
  } else {menu $m}
}

proc mfv:bind-file-complete { w } {
  bind $w <Key-Tab> {
    set f [%W get]; %W delete 0 end
    %W insert end [j:expand_filename $f]
  }
}

proc mfv:get-filename { args } {
  global mf mfp

 j:parse_args { \
   {prompt "File: "} \
   {callback ""} \
   {cbargs ""} \
   {cancelvalue ""} \
   {master ""} \
   {dir ""} }

  if {![winfo exists $master] && [winfo exists $mfp(curtop)]} { set master $mfp(curtop) }
  return [ut:fsbox -master $master -grab 1 -prompt $prompt \
	     -title "TkMail v$mfp(version) FileSelector" \
	      -quick "{Mail $mf(mail-directory)}" -cancelvalue $cancelvalue \
	      -callback $callback -cbargs $cbargs -dir $dir]
}

proc mfv:folder-menu-sender { op top } {
  # Run procedure <op> using <top> and <Sender> folder name as arguments
  global mf mfp

  if {$mfp($top,sender) != ""} {
    if {[regexp {^/} $mfp($top,sender)] || [regexp {^\./} $mfp($top,sender)]} {
      if {![file exists $mfp($top,sender)]} {mfv:add-recent $mfp($top,sender)}
      $op $top $mfp($top,sender)
    } else {
      if {![file exists $mf(mail-directory)/$mfp($top,sender)]} {
	mfv:add-recent $mf(mail-directory)/$mfp($top,sender)
      }
      $op $top $mf(mail-directory)/$mfp($top,sender)
    }
  } else {
    mfv:error-mesg "Could not determine filename to save to or no mail directory." $top
  }
}

proc mfv:build-folder-menus { {vlist ""} } {
  # Rebuild folder menus for viewers in <vlist>. Default is all.
  global mf mfp

  if {[lempty $vlist]} {
    set vlist $mfp(toplist)
  }
  set keepdir [pwd]
  
  mfv:wait-on
  
  foreach top $vlist {
    # setup menus of folders in user's folder directory
    if {$mfp(fmenulast) != [$top.menu.folder.m index last]} {
      $top.menu.folder.m delete [expr $mfp(fmenulast)+1] last
    }
    mfv:menu-create $top.menu.mesg.m.copy
    mfv:menu-create $top.menu.mesg.m.move
    mfv:menu-create $top.bb.move.m

    if {$mf(menu-recent-max) > 0} {
      mfv:menu-create $top.menu.folder.m.recent
      mfv:menu-create $top.menu.mesg.m.copy.recent
      mfv:menu-create $top.menu.mesg.m.move.recent
      mfv:menu-create $top.bb.move.m.recent

      $top.menu.folder.m add cascade -label {Recent} \
	  -menu $top.menu.folder.m.recent
      $top.menu.mesg.m.copy add cascade -label {Recent} \
	  -menu $top.menu.mesg.m.copy.recent
      $top.menu.mesg.m.move add cascade -label {Recent} \
	  -menu $top.menu.mesg.m.move.recent
      $top.bb.move.m add cascade -label {Recent} \
	  -menu $top.bb.move.m.recent
    }

    $top.menu.mesg.m.move add command -label {Other . . .} \
	-command "mfv:explicit-move $top \[mfv:get-filename -master $top\]"
    $top.bb.move.m add command -label {Other . . .} \
	-command "mfv:explicit-move $top \[mfv:get-filename -master $top\]"
    $top.menu.mesg.m.copy add command -label {Other . . .} \
	-command "mfv:explicit-copy $top \[mfv:get-filename -master $top\]"

    if {[mfdb:file-test -d $mf(mail-directory)]} {

      $top.menu.mesg.m.move add command -label {<Sender>} \
	  -command "mfv:folder-menu-sender mfv:mesg-move $top"
      $top.menu.mesg.m.move add separator
      $top.bb.move.m add command -label {<Sender>} \
	  -command "mfv:folder-menu-sender mfv:mesg-move $top"
      $top.bb.move.m add separator
      $top.menu.mesg.m.copy add command -label {<Sender>} \
	  -command "mfv:folder-menu-sender mfv:mesg-copy $top"
      $top.menu.mesg.m.copy add separator

      mfv:set-folder-menus $top $mf(mail-directory) "" -1
    }

    # append mf(menu-quick-send) contents to Mesg menu
    if {$mfp(mmenulast) != [$top.menu.mail.m index last]} {
      $top.menu.mail.m delete [expr $mfp(mmenulast)+1] last
    }
    if {[llength $mf(menu-quick-send)]} {
      $top.menu.mail.m add separator
      if {$mf(menu-quick-send) == "@aliases"} {
	foreach addr $mfp(aliasnames) {
	  $top.menu.mail.m add command -label $addr \
	      -command "mfv:compose -viewer $top -sendto {$addr}"
	}
      } else {
	foreach addr $mf(menu-quick-send) {
	  $top.menu.mail.m add command -label $addr \
	      -command "mfv:compose -viewer $top -sendto {$addr}"
	}
      }
    }
    mfv:add-recent-to-top $top
  }
  
  mfv:wait-off
  cd $keepdir
}

proc mfv:set-folder-menus { top dir extmenu depth } {
  # Build a folder menu for viewer <top> from directory <dir> as part
  # of menu <extmenu> at <depth>
  global mf mfp
  
  pdebug "Setting up menu for $dir\n"
  incr depth

  cd $dir
  set foldfiles [lsort [glob -nocomplain *]]
  set chopped 0
  set mcnt 0
  set icnt 0

  foreach mfold $foldfiles {
    
    set skipit 0
    if {[string match *.lock $mfold]} continue
    if {[string match *.bak $mfold]} continue
    if {[string match {#*#} $mfold]} continue
    foreach ifold $mf(menu-folders-ignore) {
      if {[string match $ifold $dir/$mfold]} { set skipit 1; break }
    }
    if {$skipit} continue

    if {[string index $mfold 0] == "."} {
      continue
    } elseif {[mfdb:file-test -f $dir/$mfold]} {
      $top.menu.folder.m$extmenu add command -label $mfold \
	  -command "global mf; mfv:explicit-open $top {$dir/$mfold} {} \$mf(menu-default-new) 0"
      $top.menu.mesg.m.copy$extmenu add command -label $mfold \
	  -command "mfv:mesg-copy $top {$dir/$mfold}"
      $top.menu.mesg.m.move$extmenu add command -label $mfold \
	  -command "mfv:mesg-move $top {$dir/$mfold}"
      $top.bb.move.m$extmenu add command -label $mfold \
	  -command "mfv:mesg-move $top {$dir/$mfold}"
      
    } elseif {[mfdb:file-test -d $dir/$mfold] && $depth < $mf(menu-depth-max) } {
      
      mfv:menu-create $top.menu.folder.m${extmenu}.f$mcnt
      $top.menu.folder.m$extmenu add cascade \
	  -label $mfold \
	  -menu $top.menu.folder.m${extmenu}.f$mcnt
      
      mfv:menu-create $top.menu.mesg.m.copy${extmenu}.f$mcnt
      $top.menu.mesg.m.copy$extmenu add cascade \
	  -label $mfold \
	  -menu $top.menu.mesg.m.copy${extmenu}.f$mcnt
      
      mfv:menu-create $top.menu.mesg.m.move${extmenu}.f$mcnt
      $top.menu.mesg.m.move$extmenu add cascade \
	  -label $mfold \
	  -menu $top.menu.mesg.m.move${extmenu}.f$mcnt
      
      mfv:menu-create $top.bb.move.m${extmenu}.f$mcnt
      $top.bb.move.m$extmenu add cascade \
	  -label $mfold \
	  -menu $top.bb.move.m${extmenu}.f$mcnt
      
      mfv:set-folder-menus $top $dir/$mfold ${extmenu}.f$mcnt $depth

      incr mcnt
    }
    incr icnt
    if {$icnt > $mf(menu-folders-max)} {
      set chopped 1
      break
    }
  }
  
  if {$chopped} {
    $top.menu.folder.m$extmenu add command -label "+++ chopped +++" \
	-command "mfv:explicit-open $top \[mfv:get-filename -master $top -dir $mf(mail-directory)\]"
    $top.menu.mesg.m.copy$extmenu add command -label "+++ chopped +++" \
	-command "mfv:explicit-copy $top \[mfv:get-filename -master $top -dir $mf(mail-directory)\]"
    $top.menu.mesg.m.move$extmenu add command -label "+++ chopped +++" \
	-command "mfv:explicit-move $top \[mfv:get-filename -master $top -dir $mf(mail-directory)\]"
    $top.bb.move.m$extmenu add command -label "+++ chopped +++" \
	-command "mfv:explicit-move $top \[mfv:get-filename -master $top -dir $mf(mail-directory)\]"
  }
  
}
proc mfv:fullpath { file } {
  global mfdb

  set pwd [pwd]
  set dir [file dirname $file]
  if [catch {cd $dir} res] {
    set mfdb(last-error) $res
    return {}
  }
  set file [pwd]/[file tail $file]
  cd $pwd
  return $file
}

proc mfv:add-recent { file } {
  # Add <file> to the Recent folder menu item for all viewers
  global mf mfp

  set file [mfv:fullpath $file]

  if {![winfo exists $mfp(top).menu.folder.m.recent] || \
      $file == $mf(mail-mbox) || \
      [file dirname $file] == $mf(mail-directory)} {return 0}

  if {[set ndx [lsearch $mfp(recentlist) $file]] > -1} {
    set mfp(recentlist) [lreplace $mfp(recentlist) $ndx $ndx]
  }
  
  set mfp(recentlist) [lrange [linsert $mfp(recentlist) 0 $file] \
      0 [expr $mf(menu-recent-max)-1]]
  
  foreach top $mfp(toplist) {
    mfv:add-recent-to-top $top
  }
}

proc mfv:add-recent-to-top { top } {
  # Add <file> to Recent folder menu of viewer <top>
  global mf mfp
  
  $top.menu.folder.m.recent delete 0 last
  $top.menu.mesg.m.copy.recent delete 0 last
  $top.menu.mesg.m.move.recent delete 0 last
  $top.bb.move.m.recent delete 0 last
  
  foreach folder $mfp(recentlist) {
    set mfold [file tail $folder]
    $top.menu.folder.m.recent add command -label $mfold \
	-command "mfv:add-recent {$folder}; mfv:setup-folder $top {$folder}"
    $top.menu.mesg.m.copy.recent add command -label $mfold \
	-command "mfv:add-recent {$folder}; mfv:mesg-copy $top {$folder}"
    $top.menu.mesg.m.move.recent add command -label $mfold \
	-command "mfv:add-recent {$folder}; mfv:mesg-move $top {$folder}"
    $top.bb.move.m.recent add command -label $mfold \
	-command "mfv:add-recent {$folder}; mfv:mesg-move $top {$folder}"
  }
}

set mfp(logwindow) ".NONE"
proc mfv:log-mesg { top str } {
  # Log message <str> showing pre-colon part in status label of <top>
  global mf mfp
  
  if { ![winfo exists $mfp(logwindow)] } {
    set mfp(logwindow) [ut:simpletext -title "TkMail: Message Log" \
			    -keep 1 -text $str -master $top]
    wm withdraw $mfp(logwindow)
    pdebug "Created log window $mfp(logwindow)\n"
  } else {
    $mfp(logwindow).txt configure -state normal
    $mfp(logwindow).txt insert end "$str\n"
    $mfp(logwindow).txt configure -state disabled
  }
  if {[winfo exists $top.$mfp(mstat)]} {
    $top.$mfp(mstat) configure -text [lindex [split $str :] 0]
  }
  
}

proc mfv:clear-text-mem { tw } {
  # Cleanup memory associated with text widget <tw>
  global mf mfp mfdb_hdr

  if {[info exists mfdb_hdr($tw)]} {unset mfdb_hdr($tw)}
  bind_cleanup $tw
  return 1
}

proc mfv:close-viewer { top } {
  # Close viewer <top> possible closing the folder associated with it if
  # no other viewers are using it
  global mf mfp mfdb mfdb_hdr

  if {[set ndx [lsearch -exact $mfp(toplist) $top]] > -1} {
    set mfp(toplist) [lreplace $mfp(toplist) $ndx $ndx]
    set filename [keylget mfp($top) file]
    keylset mfp(last-mesg) $filename [mfv:head-to-num $top [mfv:cursingle $top]]
    # make sure no other viewers are using filename before closing
    set dodel 1
    foreach viewer $mfp(toplist) {
      if {[mfdb:same-files $filename [keylget mfp($viewer) file]]} {set dodel 0}
    }
    if {$dodel} {mfdb:folder-close $filename}
    unset mfp($top)
    unset mfp($top,delmesg)
    unset mfp($top,fixed)
    unset mfp($top,sort)
    unset mfp($top,reverse)
    unset mfp($top,sender)
    mfv:clear-text-mem $top.$mfp(mesg)
    while {[set ndx [lsearch -glob $mfp(waitlist) $top.*]] != -1} {
      set mfp(waitlist) [lreplace $mfp(waitlist) $ndx $ndx]
    }
    if {[winfo exists ${top}_search]} {
      unset mfp($top,search,case)
      unset mfp($top,search,regexp)
      unset mfp($top,search,back)
      unset mfp($top,search,where)
      destroy ${top}_search
    }
  }
  if {$mfp(curview) == $top} {set mfp(curtop) $mfp(top)}
  if {$mfp(curtop) == $top} {set mfp(curtop) $mfp(curview)}
  destroy $top
}

proc mfv:new-viewer { filename ismain {iconic 0} {msgndx {}}} {
  # Open up new viewer for folder <filename>. If <isman>, this is master viewer
  global mf mfp mfdb env

  set cnt 0
  while {[winfo exists .mf${cnt}]} {incr cnt}
  set top .mf${cnt}
  lappend mfp(toplist) $top
  if {$ismain} { set mfp(top) $top }
  set mfp($top,fixed) $mf(disp-default-fixed)
  set mfp($top,sort) $mf(headlist-sort)
  set mfp($top,reverse) $mf(headlist-reverse)
  set mfp($top,sender) {}

  toplevel $top -class MailView
  wm iconname $top "TkMail"
  wm title $top "TkMail v$mfp(version)"
  wm minsize $top 400 400
  wm protocol $top WM_DELETE_WINDOW mfv:quit
  wm withdraw $top
  bind $top <FocusIn> {global mfp; set mfp(curtop) %W; set mfp(curview) %W}

  frame $top.menu -relief raised
  menubutton $top.menu.folder -text {Folder} -menu $top.menu.folder.m -underline 0
  menubutton $top.menu.edit -text {Edit} -menu $top.menu.edit.m -underline 0
  menubutton $top.menu.mesg -text {Mesg} -menu $top.menu.mesg.m -underline 3
  menubutton $top.menu.mail -text {Mail} -menu $top.menu.mail.m -underline 0
  menubutton $top.menu.view  -text {Viewer} -menu $top.menu.view.m -underline 0
  menubutton $top.menu.opt  -text {Options} -menu $top.menu.opt.m -underline 0
  menubutton $top.menu.help -text {Help} -menu $top.menu.help.m -underline 0
  
  menu $top.menu.folder.m
  $top.menu.folder.m add command -label {Open . . .} -accelerator {[o]} -underline 0 \
      -command "mfv:explicit-open $top \[mfv:get-filename -master $top\] {} 0 1"
  $top.menu.folder.m add command -label {Close} -accelerator {[w]} -underline 0 \
      -command "mfv:close-viewer $top"
  if {$ismain} { $top.menu.folder.m disable Close* }
  $top.menu.folder.m add command -label {Quit} -accelerator {[q]} -underline 0 \
      -command "mfv:quit"
  $top.menu.folder.m add separator
  $top.menu.folder.m add command -label {Main Box} -accelerator {[b]} -underline 5 \
      -command "mfv:setup-folder $top \$mf(mail-mbox)"
  $top.menu.folder.m add command -label {Incorporate New Mail} -accelerator {[i]} \
      -underline 0 -command "mfv:mail-incorp $top 0"
  $top.menu.folder.m add command -label {Process Deletes} -underline 0 \
      -command "mfv:proc-deletes $top 0"
  $top.menu.folder.m add command -label {Save Sorted} -underline 0 \
      -command "mfv:proc-deletes $top 1"
  $top.menu.folder.m add command -label {Force Autosave Now} -underline 12 \
      -command "global mfp; mfdb:folder-backup \[keylget mfp($top) file\]"
  $top.menu.folder.m add command -label {Rebuild Folder Menus} -underline 8 \
      -command "mfv:build-folder-menus; mfv:mesg-set-sender $top"
  $top.menu.folder.m add command -label {Reread Alias File} -underline 7 \
      -command "mfv:parse-alias-file $top"
  $top.menu.folder.m add separator
  set mfp(fmenulast) [$top.menu.folder.m index last]
  
  menu $top.menu.edit.m
  $top.menu.edit.m add command -label {Cut} -underline 2 \
      -command "bt:delete-region-or-sel $top.mesg.txt"
  $top.menu.edit.m add command -label {Copy} -underline 0 \
      -command "bt:copy-region-or-sel $top.mesg.txt"
  $top.menu.edit.m add command -label {Paste} -underline 0 \
      -command "bt:yank $top.mesg.txt"
  $top.menu.edit.m add command -label {Select All} -underline 2 \
      -command "bt:mark-whole-buffer $top.mesg.txt"
  $top.menu.edit.m add separator
  $top.menu.edit.m add command -label {Search . . .} -underline 0 \
      -command "mfv:search-prompt $top"
  $top.menu.edit.m add command -label {Search Again} -underline 7 \
      -command "mfv:search $top ${top}_search"
  $top.menu.edit.m add separator
  $top.menu.edit.m add command -label {Save X Selection . . .} -underline 2 \
      -command "mfv:save $top \[mfv:get-filename -master $top\] xsel"
  $top.menu.edit.m add command -label {Print X Selection . . .} -underline 6 \
      -command "mfv:print $top xsel"
  $top.menu.edit.m add command -label {TCL Evaluate X Sel} -underline 4 \
      -command "mfv:tcl-eval-sel"
  $top.menu.edit.m add command -label {UNIX Pipe X Sel . . .} -underline 0 \
      -command "mfv:pipe $top xsel $top.mesg.txt 0"
  
  if {!$mf(bind-emacs)} {
    $top.menu.edit.m entryconfigure {Cut} -accelerator {[x]}
    $top.menu.edit.m entryconfigure {Copy} -accelerator {[c]}
    $top.menu.edit.m entryconfigure {Paste} -accelerator {[v]}
    $top.menu.edit.m entryconfigure {Search . . .} -accelerator {[f]}
    $top.menu.edit.m entryconfigure {Search Again} -accelerator {[g]}
  }

  menu $top.menu.mesg.m
  $top.menu.mesg.m add command -label {Next} -accelerator {[Down]} \
      -command "mfv:select-next $top" -underline 0
  $top.menu.mesg.m add command -label {Prev} -accelerator {[Up]} \
      -command "mfv:select-prev $top" -underline 0
  $top.menu.mesg.m add command -label {Reread} \
      -command "mfv:reload-mesg $top" -underline 0
  $top.menu.mesg.m add command -label {Show Full Headers} \
      -command "mfv:show-full-headers $top" -underline 6
  $top.menu.mesg.m add command -label {Select All} \
      -command "$top.$mfp(head) select all" -underline 1
  $top.menu.mesg.m add separator
  $top.menu.mesg.m add cascade -label {Copy} \
      -menu $top.menu.mesg.m.copy -underline 0
  $top.menu.mesg.m add cascade -label {Move} \
      -menu $top.menu.mesg.m.move -underline 0
  $top.menu.mesg.m add command -label {Save . . .} -accelerator {[s]} -underline 0 \
      -command "mfv:explicit-save $top \[mfv:get-filename -master $top\] mesg"
  $top.menu.mesg.m add command -label {Print . . .} -accelerator {[p]} \
      -command "mfv:print $top mesg" -underline 4
  $top.menu.mesg.m add command -label {UNIX Pipe . . .} -underline 3 \
      -command "mfv:pipe $top mesg $top.mesg.txt 0"
  $top.menu.mesg.m add separator
  $top.menu.mesg.m add command -label {Delete} -accelerator {[d]} \
      -command "mfv:mesg-delete $top" -underline 0
  $top.menu.mesg.m add command -label {Delete All} \
      -command "$top.$mfp(head) select all; mfv:mesg-delete $top" -underline 7
  $top.menu.mesg.m add command -label {Undelete ...} -accelerator {[u]} -underline 0 \
      -command "mfv:mesg-undelete $top \[ut:getstr -prompt {Message numbers to undelete:}\]"
  $top.menu.mesg.m add command -label {Undelete All} \
      -command "mfv:mesg-undelete $top" -underline 9
  $top.menu.mesg.m add command -label {Undelete Last} \
      -command "mfv:mesg-undelete-last $top" -underline 9
  $top.menu.mesg.m add separator
  $top.menu.mesg.m add command -label {Detach} -command "mfv:detach-mesg $top" -underline 5
  $top.menu.mesg.m add command -label {Split} -underline 3 \
      -command "mfv:split-mesg-view $top"
  $top.menu.mesg.m add command -label {Unsplit} \
      -command "mfv:unsplit-mesg-view $top"
  $top.menu.mesg.m add command -label {Quick Decode} -underline 0 \
      -command "mfv:quick-decode $top.mesg.txt"

  menu $top.menu.mail.m
  $top.menu.mail.m add command -label {Compose} -accelerator {[m]} \
      -command "mfv:compose -viewer $top" -underline 0
  $top.menu.mail.m add command -label {Reply} -accelerator {[r]} \
      -command "mfv:reply $top 0 0" -underline 0
  $top.menu.mail.m add command -label {Reply All} -accelerator {[t]} \
      -command "mfv:reply $top 0 1" -underline 6
  $top.menu.mail.m add command -label {Forward} -accelerator {[k]} \
      -command "mfv:forward $top 3" -underline 0
  $top.menu.mail.m add separator
  $top.menu.mail.m add command -label {Alias Current} -underline 7 \
      -command "mfv:alias-current $top"
  $top.menu.mail.m add command -label {Restore Last} -underline 8 \
      -command "set mfc \[mfv:compose -viewer $top\] 
		\$mfc.comp.txt delete 1.0 end
		\$mfc.comp.txt insert 1.0 \$mfp(savesendtxt)"
  $top.menu.mail.m add separator
  $top.menu.mail.m add command -label {TkMail Support} -underline 7 -command {
        global mf mfp
        $mfp(tmptxt) delete 1.0 end
        $mfp(tmptxt) insert end "TkMail version: $mfp(version)\n\n"
        catch "$mfp(tmptxt) insert end \"Machine/OS: [exec uname -a]\n\""
        catch "$mfp(tmptxt) insert end \"Tk Version: $tk_version\n\n\""
        catch "$mfp(tmptxt) insert end \"Version: $mfp(version)\n\n\""
	foreach name [lsort [array names mf]] {
            set val [eval "set mf($name)"]
            $mfp(tmptxt) insert end "  mf($name) {$val}\n"
        }
        $mfp(tmptxt) insert end "------------------------------------\n"
        $mfp(tmptxt) insert end "NOTE: Please insert your $mfp(setfile) file unless you think it isn't relevant.\n\n"
        mfv:compose -sendto raines@slac.stanford.edu \
	    -subject "TkMail Beta Support" -incmesg 2
    }
  set mfp(mmenulast) [$top.menu.mail.m index last]
  
  menu $top.menu.view.m

  $top.menu.view.m add command -label {New . . .} -accelerator {[n]} -underline 0 \
      -command "mfv:explicit-open $top \[mfv:get-filename -master $top\] {} 1 1"

  $top.menu.view.m add separator
  $top.menu.view.m add radiobutton -label "Sort Normal" -underline 5 \
      -command "mfv:reset-headlist $top" -variable mfp($top,sort) -value normal
  $top.menu.view.m add radiobutton -label "Sort From Addr" -underline 5 \
      -command "mfv:reset-headlist $top" -variable mfp($top,sort) -value sm-from
  $top.menu.view.m add radiobutton -label "Sort Full Name" -underline 6 \
      -command "mfv:reset-headlist $top" -variable mfp($top,sort) -value fullname
  $top.menu.view.m add radiobutton -label "Sort Subject" -underline 7 \
      -command "mfv:reset-headlist $top" -variable mfp($top,sort) -value subject
  $top.menu.view.m add radiobutton -label "Sort Time Received" -underline 5 \
      -command "mfv:reset-headlist $top" -variable mfp($top,sort) -value time-received

  $top.menu.view.m add separator
  $top.menu.view.m add checkbutton -label "Fixed-spaced font" -underline 2 \
      -variable mfp($top,fixed) \
      -command "mfv:toggle-fixed-font $top $top.mesg.txt"
  $top.menu.view.m add checkbutton -label "Reverse Order" -underline 0 \
      -variable mfp($top,reverse) -command "mfv:reset-headlist $top"
  $top.menu.view.m add checkbutton -label "Reverse Video" -underline 2 \
      -variable mfp($top,revvid) \
      -command "mfv:toggle-video $top.$mfp(head)_text $top.$mfp(mesg)"

  menu $top.menu.opt.m

  $top.menu.opt.m add command -label "Edit Global Settings . . ." -underline 5 \
      -command "mfv:opt-master"
  $top.menu.opt.m add command -label "Edit Aliases . . ." -underline 5 \
      -command "mfv:edit-alias-file $top"
  
  $top.menu.opt.m add separator
  $top.menu.opt.m add checkbutton -label "Auto-Incorporate" -underline 5 \
      -variable mf(mail-auto-incorp) -command "global mfopt; set mfopt(modified) 1"
  $top.menu.opt.m add checkbutton -label "Popup Notice of New Mail" -underline 0 \
      -variable mf(notify-popup) -command "global mfopt; set mfopt(modified) 1"
  $top.menu.opt.m add checkbutton -label "New Viewer on Folder Select" -underline 4 \
      -variable mf(menu-default-new) -command "global mfopt; set mfopt(modified) 1"
  $top.menu.opt.m add checkbutton -label "Ask to Continue on Long Mesg" -underline 2 \
      -variable mf(mail-read-ask) -command "global mfopt; set mfopt(modified) 1"
  $top.menu.opt.m add checkbutton -label "Strip Header on Insert" -underline 6 \
      -variable mf(insert-strip) -command "global mfopt; set mfopt(modified) 1"
  $top.menu.opt.m add checkbutton -label "Parse MIME messages" -underline 6 \
      -variable mf(mime-parse) \
      -command "global mf; mfdb:option-set mime-parse \$mf(mime-parse); mfv:reload-mesg $top"

  # SETUP HELP MENU
  menu $top.menu.help.m
  $top.menu.help.m add command -label {Intro} -command "mfv:display-help $top TOP" \
      -accelerator {[h]} -underline 0
  
  set mfp(readme) [list "GENERAL USAGE" "ALIASES" \
      "MOUSE BINDINGS" "KEY BINDINGS" "READER MENU" "COMPOSE MENU" \
      "PRINTING" "HEADER FIELD STRIPPING" "SUMMARY LISTBOX FORMAT" \
      "CC, BCC, and FCC" "SIGNATURE" "MIME" "USER SETTINGS" "WIDGET CONFIGURATION" \
      "USEFUL METHODS" "BUGS" "TODO" "COPYRIGHT" "DISCLAIMER"]
  
  foreach topic $mfp(readme) {
    $top.menu.help.m add command -label [string tolower $topic] \
	-command "mfv:display-help $top \{$topic\}" -underline 0
  }
  $top.menu.help.m add separator
  $top.menu.help.m add command -label {Show Log} -underline 5 \
      -command "mfv:show-log $top"
  proc mfv:show-log { top } {
    global mf mfp
    if {[winfo exists $mfp(logwindow)]} {
      wm geometry $mfp(logwindow) +[winfo rootx $top]+40
      wm deiconify $mfp(logwindow)
      raise $mfp(logwindow)
    }
  }
  
  # PACK MENU
  pack $top.menu.folder $top.menu.edit $top.menu.mesg \
      $top.menu.mail $top.menu.view $top.menu.opt -side left
  pack $top.menu.help -side right

  if $mfp(print-noprompt) {
      $top.menu.mesg.m entryconfigure {Print*} -label Print
      $top.edit.mesg.m entryconfigure {Print X*} -label {Print X Selection}
  }

  pdebug "  Menu\n"

  # HEADLIST STATUS LINE
  frame $top.stat
  label $top.stat.folder -relief raised -anchor w -width 22
  label $top.stat.mesg -relief raised -width 36
  pack $top.stat.folder -side left
  pack $top.stat.mesg -side left -expand true -fill x

  # HEADLIST LISTBOX
  frame $top.head
  scrollbar $top.head.yscroll -command "$top.$mfp(head) yview" \
      -relief raised
  disjointlistbox $top.$mfp(head) -yscroll "$top.head.yscroll set" \
      -wrap none -cursor left_ptr -relief sunken

  bind $top.$mfp(head) <Any-KeyPress> " "
  bind $top.$mfp(head) <Button-1> \
      "mfv:select-mesg $top from \[$top.$mfp(head) nearest %y\] 1"
  bind $top.$mfp(head) <B1-Motion> \
      "mfv:select-mesg $top to \[$top.$mfp(head) nearest %y\] 1"
  bind $top.$mfp(head) <Button-3> \
      "mfv:select-mesg $top at \[$top.$mfp(head) nearest %y\] 1"
  bind $top.$mfp(head) <B3-Motion> \
      "mfv:select-mesg $top at \[$top.$mfp(head) nearest %y\] 1"
  bind $top.$mfp(head) <Control-3> \
      "mfv:select-mesg $top from \[$top.$mfp(head) nearest %y\] 1 0 keep"
  bind $top.$mfp(head) <Shift-3> \
      "mfv:deselect-mesg $top \[$top.$mfp(head) nearest %y\]"
  bind $top.$mfp(head) <Control-1> \
      "set ndx \[$top.$mfp(head) nearest %y\]; mfv:select-mesg $top toggle \$ndx 1; 
       global mfp; set mfp(select-mode) \[$top.$mfp(head) isselected \$ndx\]"
  bind $top.$mfp(head) <Control-B1-Motion> \
      "global mfp; if \$mfp(select-mode) {set mode at} {set mode clear};
       mfv:select-mesg $top \$mode \[$top.$mfp(head) nearest %y\] 1"
  bind $top.$mfp(head) <Shift-1> \
      "mfv:select-mesg $top to \[$top.$mfp(head) nearest %y\] 1"
  bind $top.$mfp(head) <Shift-B1-Motion> \
      "mfv:select-mesg $top to \[$top.$mfp(head) nearest %y\] 1"
  
  #set mfp(b2-time) 0
  #set mfp(b2-y) 0
  #bind $top.$mfp(head) <Button-2> {
  #  global mfp
  #  %W scan mark %x %y
  #  set mfp(b2-time) %t
  #  set mfp(b2-y) %y
  #}
  #bind $top.$mfp(head) <ButtonRelease-2> {
  #  global mfp
  #  if {[expr %t-$mfp(b2-time)]<1000} {
  #    mfv:select-mesg clear [$mfp(top).$mfp(head) nearest $mfp(b2-y)] 0
  #  }
  #}
  
  # BUTTONBAR
  frame $top.bb
  button $top.bb.incorp -text "Incorp" \
      -command "$top.menu.folder.m invoke Incorp*"
  button $top.bb.save -text "Save" -command "$top.menu.mesg.m invoke Save*"
  menubutton $top.bb.move -text {Move} -menu $top.bb.move.m \
      -relief raised
  button $top.bb.del -text "Delete" -command "$top.menu.mesg.m invoke Delete*"
  button $top.bb.comp -text "Compose" -command "$top.menu.mail.m invoke Compose*"
  button $top.bb.reply -text "Reply" -command "$top.menu.mail.m invoke Reply*"
  button $top.bb.detach -text "Split" -command "$top.menu.mesg.m invoke Split*"
  if {$ismain} {
    button $top.bb.quit -text "Quit" -command "$top.menu.folder.m invoke Quit*"
  } else {
    button $top.bb.quit -text "Close" -command "$top.menu.folder.m invoke Close*"
  }

  bind Button <2> {tk_butDown %W}
  bind Button <ButtonRelease-2> {tk_butUp3 %W}
  bind Button <3> {tk_butDown %W}
  bind Button <ButtonRelease-3> {tk_butUp3 %W}
  proc tk_butUp3 w {
    global tk_priv
    $w config -relief $tk_priv(relief)
  }
  
  # include message with no prefix, no address
  bind $top.bb.comp <ButtonRelease-2> "tk_butUp3 %W; mfv:forward $top 3"
  
  # include message with no prefix, with address
  bind $top.bb.reply <ButtonRelease-2> "tk_butUp3 %W; mfv:reply $top 3"
  
  # detach current message
  bind $top.bb.detach <ButtonRelease-2> "tk_butUp3 %W; $top.menu.mesg.m invoke Detach*"
  
  # incorporate new mail, opening up new viewer for mbox if needed
  bind $top.bb.incorp <ButtonRelease-3> "tk_butUp3 %W; mfv:mail-incorp $top 1"
  
  # include message with prefix, no address
  bind $top.bb.comp <ButtonRelease-3> "tk_butUp3 %W; mfv:forward $top 1"
  
  # include message with prefix, with address
  bind $top.bb.reply <ButtonRelease-3> "tk_butUp3 %W; mfv:reply $top 1"
  
  # unsplit current viewer
  bind $top.bb.detach <ButtonRelease-3> "tk_butUp3 %W; $top.menu.mesg.m invoke Unsplit*"
  
  pack $top.bb.incorp $top.bb.save $top.bb.move $top.bb.del \
      $top.bb.comp $top.bb.reply $top.bb.detach $top.bb.quit \
      -side left -expand true -fill x
  
  pdebug "  ButtonBar\n"

  frame $top.mesg
  scrollbar $top.mesg.yscroll -command "$top.mesg.txt yview" \
      -relief raised
  text $top.mesg.txt -yscroll "$top.mesg.yscroll set" \
      -relief sunken -bd 2 
  $top.mesg.txt tag configure seelong -borderwidth 2 -relief raised \
      -background [lindex [$top.bb.quit configure -background] 4] \
      -font [lindex [$top.bb.quit configure -font] 4]

  mfv:toggle-fixed-font $top $top.mesg.txt

  if {$mf(bind-emacs)} {
    bind $top.mesg.txt [mfv:menu-key o] "$top.menu.folder.m invoke {Open*}"
    bind $top.mesg.txt [mfv:menu-key n] "$top.menu.view.m invoke {New*}"
    bind $top.mesg.txt [mfv:menu-key w] "$top.menu.folder.m invoke {Close*}"
    bind $top.mesg.txt [mfv:menu-key q] "$top.menu.folder.m invoke {Quit}"
    bind $top.mesg.txt [mfv:menu-key b] "$top.menu.folder.m invoke {Main*}"
    bind $top.mesg.txt [mfv:menu-key i] "$top.menu.folder.m invoke {Incor*}"
    bind $top.mesg.txt [mfv:menu-key Down] "$top.menu.mesg.m invoke {Next}"
    bind $top.mesg.txt [mfv:menu-key Up] "$top.menu.mesg.m invoke {Prev}"
    bind $top.mesg.txt [mfv:menu-key d] "$top.menu.mesg.m invoke {Delete}"
    bind $top.mesg.txt [mfv:menu-key u] "$top.menu.mesg.m invoke {Undelete ...}"
    bind $top.mesg.txt [mfv:menu-key s] "$top.menu.mesg.m invoke {Save*}"
    bind $top.mesg.txt [mfv:menu-key p] "$top.menu.mesg.m invoke {Print*}"
    bind $top.mesg.txt [mfv:menu-key m] "$top.menu.mail.m invoke {Compose}"
    bind $top.mesg.txt [mfv:menu-key r] "$top.menu.mail.m invoke {Reply}"
    bind $top.mesg.txt [mfv:menu-key t] "$top.menu.mail.m invoke {Reply All}"
    bind $top.mesg.txt [mfv:menu-key k] "$top.menu.mail.m invoke {Forward}"
    bind $top.mesg.txt [mfv:menu-key h] "$top.menu.help.m invoke 0"
    if {[string range $mf(bind-alt-key) 0 0] == "<"} {
      bind $top.mesg.txt <Control-q>$mf(bind-alt-key) {bt:event-self-insert %W %A}
    }
    foreach k {o n w q b i d u s p m r t k h} {
      bind $top.mesg.txt <Control-q>[mfv:menu-key $k] {bt:event-self-insert %W %A}
    }
  } else {
    bind $top.mesg.txt <Control-o> "$top.menu.folder.m invoke {Open*}"
    bind $top.mesg.txt <Control-n> "$top.menu.view.m invoke {New*}"
    bind $top.mesg.txt <Control-w> "$top.menu.folder.m invoke {Close*}"
    bind $top.mesg.txt <Control-q> "$top.menu.folder.m invoke {Quit}"
    bind $top.mesg.txt <Control-b> "$top.menu.folder.m invoke {Main*}"
    bind $top.mesg.txt <Control-i> "$top.menu.folder.m invoke {Incor*}"
    bind $top.mesg.txt <Control-Down> "$top.menu.mesg.m invoke {Next}"
    bind $top.mesg.txt <Control-Up> "$top.menu.mesg.m invoke {Prev}"
    bind $top.mesg.txt <Control-d> "$top.menu.mesg.m invoke {Delete}"
    bind $top.mesg.txt <Control-u> "$top.menu.mesg.m invoke {Undelete ...}"
    bind $top.mesg.txt <Control-s> "$top.menu.mesg.m invoke {Save*}"
    bind $top.mesg.txt <Control-p> "$top.menu.mesg.m invoke {Print*}"
    bind $top.mesg.txt <Control-m> "$top.menu.mail.m invoke {Compose}"
    bind $top.mesg.txt <Control-r> "$top.menu.mail.m invoke {Reply}"
    bind $top.mesg.txt <Control-t> "$top.menu.mail.m invoke {Reply All}"
    bind $top.mesg.txt <Control-k> "$top.menu.mail.m invoke {Forward}"
    bind $top.mesg.txt <Control-h> "$top.menu.help.m invoke 0"
  }
  
  # configure second text widget for splits
  frame $top.mesg2
  scrollbar $top.mesg2.yscroll -command "$top.mesg2.txt yview" \
      -relief raised
  text $top.mesg2.txt -yscroll "$top.mesg2.yscroll set" \
      -relief sunken -bd 2
  $top.mesg2.txt configure -font [lindex [$top.$mfp(mesg) configure -font] 4]
  
  # configure pane handle below summary list
  frame $top.sep1 -relief raised -height 11 -bd 1
  frame $top.line1 -relief sunken -height 2 -bd 1
  frame $top.handle1 -relief raised  -bd 2 -cursor crosshair \
      -width 9 -height 9

  bind $top.handle1 <ButtonPress-1> "mfv:sash-begin $top $top.$mfp(head) 1"
  bind $top.handle1 <B1-Motion> "mfv:sash-draw $top %Y 1"
  bind $top.handle1 <ButtonRelease-1> "mfv:sash-end $top"

  place configure $top.line1 -in $top.sep1 -relx 0.03 -rely 0.4 \
      -relwidth 0.95
  place configure $top.handle1 -in $top.sep1 -relx 0.8 -rely 0.4 \
      -anchor center

  pack $top.menu $top.stat $top.head $top.bb $top.sep1 -side top -fill x
  pack $top.mesg -side top -expand true -fill both
  
  # configure pane handle between text widgets
  frame $top.sep2 -relief raised -height 11 -bd 1
  frame $top.line2 -relief sunken -height 2 -bd 1
  frame $top.handle2 -relief raised  -bd 2 -cursor crosshair \
      -width 9 -height 9

  bind $top.handle2 <ButtonPress-1> "mfv:sash-begin $top $top.$mfp(mesg) 2"
  bind $top.handle2 <B1-Motion> "mfv:sash-draw $top %Y 2"
  bind $top.handle2 <ButtonRelease-1> "mfv:sash-split-end $top"

  # configure widgets to user settings
  $top.$mfp(head) configure -height $mf(headlist-height)
  eval "$top.$mfp(mesg) tag configure headers $mf(header-config)"
  
  if {$mf(disp-left-scroll)} {
    pack $top.head.yscroll -side left -fill y
    pack $top.$mfp(head) -side left -expand true -fill both
    pack $top.mesg.yscroll -side left -fill y
    pack $top.mesg.txt -side left -expand true -fill both
    pack $top.mesg2.yscroll -side left -fill y
    pack $top.mesg2.txt -side left -expand true -fill both
  } else {
    pack $top.$mfp(head) -side left -expand true -fill both
    pack $top.head.yscroll -side left -fill y
    pack $top.mesg.txt -side left -expand true -fill both
    pack $top.mesg.yscroll -side left -fill y
    pack $top.mesg2.txt -side left -expand true -fill both
    pack $top.mesg2.yscroll -side left -fill y
  }
  pdebug "  Scroll\n"
  
  
  # build folder list menus
  mfv:build-folder-menus $top
  
  # source users mfv:viewer-hook procedure if defined
  if {[info procs mfv:viewer-hook] == "mfv:viewer-hook"} {
    mfv:viewer-hook $top
  }

  tk_autoMenuBar $top.menu
  
  lappend mfp(waitlist) $top.$mfp(head) $top.$mfp(mesg)
  set mfp($top.$mfp(head),cursor) {}
  set mfp($top.$mfp(mesg),cursor) {}

  wm geometry $top [string trim $mf(viewer-geom)]

  if {$ismain && [mfdb:file-test -r $mf(viewer-bitmap-nomail)]} {
    wm iconbitmap $top "@$mf(viewer-bitmap-nomail)"
  }
  if {$iconic} {
    wm iconify $mfp(top)
  } else {
    wm deiconify $top; raise $top
  }
  focus $top.mesg.txt
  update idletasks

  set mfp($top) {}
  if {[mfv:setup-folder $top $filename $msgndx]} {
    if {$ismain} {
      puts stderr "VIEWER: $mfdb(last-error)"
      puts stderr "        FATAL - can't open initial folder"
      mfdb:quit
    }
    set filename {}
  }
  set mfp($top,delmesg) {}

  return $top
}

proc mfv:init-bindings { } {
  # Initialize viewers in general. Called once.
  global mf mfp env btp ut_glob focus_priv
  
  # setup better bindings
  if {$mf(bind-use-meta)} { 
    set btp(use-meta) 1 
  } else { set btp(use-meta) 0 }
  if {$mf(bind-use-esc)} { 
    set btp(use-esc-prefix) 1 
  } else { set btp(use-esc-prefix) 1 }
  bind_motiftext Text
  bind_motifentry Entry
  bind_listbox Listbox
  
  if {$mf(bind-emacs)} {
    bind_emacstext Text
    bind_emacsentry Entry
    if {$mf(bind-use-esc)} {
      set mfp(cancel) Control-
      set ut_glob(cancel) Control-
      set focus_priv(cancel) Control-
    }
    set focus_priv(emacs) 1
    bind Text <Control-Shift-Y> "mfv:prefix-sel %W emacs"
    bind Text <Shift-Control-S> "mfv:search-prompt %W"
    bind Text <Control-s> "mfv:search %W \[winfo toplevel %W\]_search"
  } else {
    bind_mactext Text
    bind_macentry Entry
    bind Text <Control-Shift-V> "mfv:prefix-sel %W emacs"
    bind Text <Control-f> "mfv:search-prompt %W"
    bind Text <Control-g> "mfv:search %W \[winfo toplevel %W\]_search"
  }  
  bind Text <Shift-Meta-Button-2> "mfv:prefix-sel %W xsel"
  
  if {$mf(bind-emacs) || !$mf(bind-use-meta)} {
    if {[string range $mf(bind-alt-key) 0 0] == "<"} {
      proc mfv:menu-key { k } {
	global mf; return $mf(bind-alt-key)<$k>
      }
    } else {
      proc mfv:menu-key { k } {
	global mf; return <$mf(bind-alt-key)-$k>
      }
    }
  } else {
    proc mfv:menu-key { k } {global mf; return <Meta-$k>}
  }
  set ut_glob(key-hook) mfv:menu-key

  # set menu state key
  if {$mf(bind-alt-key) != ""} {
    if {[string range $mf(bind-alt-key) 0 0] == "<"} {
      bind Text $mf(bind-alt-key) { }
      bind Entry $mf(bind-alt-key) { }
    }
    bind Text [mfv:menu-key Any-KeyPress] {
      global mf
      if {"%A" != ""} {eval $mf(viewer-beep-error) }
    }
    bind Entry [mfv:menu-key Any-KeyPress] {
      global mf
      if {"%A" != ""} {eval $mf(viewer-beep-error) }
    }
  }
}

proc mfv:getopts { cargs } {
  # parse string <cargs> for options
  global mf mfp
  
  set cnt 0
  set mfp(starticonic) 0
  while {$cnt < [llength $cargs]} {
    set opt [lindex $cargs $cnt]
    case $opt {
      { -i -iconic } { set mfp(starticonic) 1 }
      { tkmail } { }
      default { 
	set mfp(file) [mfv:fullpath $opt]
      }
    }
    incr cnt 
  }
  
}

proc mfv:tkmail-init { args } {
  # called by server once ~/.tkmailrc is sourced to initialize WISH side
  global mf mfp
  
  # create temp text widget for use
  text $mfp(tmptxt)

  # transfer appropriate options to Perl side
  mfdb:option-set mail-mbox $mf(mail-mbox)
  mfdb:option-set mail-deliver $mf(mail-deliver)
  mfdb:option-set mail-tmpdir $mf(mail-tmpdir)
  mfdb:option-set mail-remove-empty $mf(mail-remove-empty)
  mfdb:option-set mime-parse $mf(mime-parse)
  mfdb:option-set headlist-format $mf(headlist-format)
  mfdb:option-list-set header-strip $mf(header-strip)
  mfdb:option-list-set header-retain $mf(header-retain)

  pdebug "Get cmdline opts ...\n"
  flush stderr
  mfv:getopts $args

  mfv:parse-alias-file {}

  if {$mfp(file)==""} { set mfp(file) $mf(mail-mbox) }
  pdebug "Starting with folder $mfp(file)\n"

  pdebug "Viewer initializing ...\n"
  flush stderr
  set top [mfv:new-viewer $mfp(file) 1 $mfp(starticonic)]
  mfv:add-recent $mfp(file)
  if {$mf(mail-auto-incorp) && [mfdb:mail-check $mf(mail-system)]} {
    if {[mfdb:same-files $mf(mail-mbox) $mfp(file)]} {
      mfv:mail-incorp $top 0
    }
  }
  mfv:schedule-check

  # check if person thought it was for seconds and not milliseconds
  if {$mf(mail-autosave) < 1000} {set mf(mail-autosave) [expr $mf(mail-autosave)*1000]}
  if {$mf(mail-autosave) > 0} { after $mf(mail-autosave) "mfv:autosave" }

  # set binding options
  set btp(prefix) $mf(insert-prefix)
  set btp(error) mfv:error-mesg
  set btp(beep) $mf(viewer-beep-error)

  if [file isdirectory $mf(mail-directory)] {
    cd $mf(mail-directory)
  }
  mfv:check-old-settings
}

proc mfv:quit { } {
  # safely quit tkmail
  global mf mfp mfopt

  if $mfopt(modified) {
    if [ut:getok -prompt "Options changed but not saved. Save now?" \
	    -master $mfp(top) -oklabel Yes -nolabel No] {
      mfv:opt-save .mf_newopt
    }
  }

  mfdb:quit
}

######### MAIN ##################
if {$mfp(debug)} {puts stderr "VIEWER: Finished sourcing procedures"}

wm withdraw .

# load packages we definitely need and auto_load rest
if {![file exists $mfp(tkmaillib)/disjoint.tk]} {
  puts stderr "VIEWER: Can't find libraries in $mfp(tkmaillib)"
  puts stderr "        Please modify the mfp(tkmaillib) setting at top of viewer.tcl"
  mfdb:quit
}
source $mfp(tkmaillib)/disjoint.tk
source $mfp(tkmaillib)/bindings.tk
source $mfp(tkmaillib)/mime.tk

# autoload other libraries
if {[info exists auto_path]} {
  lappend auto_path $mfp(tkmaillib)
} else {
  set auto_path [list $tk_library $mfp(tkmaillib)]
}

# read in defaults settings
mfv:default-set

# make default bindings
mfv:init-bindings

if {$mfp(debug)} {
  set mf(mail-debug) 1
  wm title . "TkMail v$mfp(version) Debug"

  label .cur -textvariable mfp(curtop) -relief raised
  entry .field -relief sunken
  bind .field <KeyPress-Return> {eval [.field get]; .field delete 0 end}

  frame .debug
  scrollbar .debug.scr -command ".debug.txt yview" -relief raised
  text .debug.txt -cursor left_ptr -relief sunken -wrap word \
      -yscroll ".debug.scr set" -width 45 -height 8
  pack .debug.scr -side left -fill y
  pack .debug.txt -side left -expand true -fill both

  frame .bb
  button .bb.b1 -text Eval -command {eval [.field get]}
  button .bb.b2 -text What? -command {.debug.txt insert end \n$errorInfo\n}
  button .bb.b3 -text Flush -command {flush stdout}
  button .bb.b4 -text Quit -command {mfdb:quit}
  pack .bb.b1 .bb.b2 .bb.b3 .bb.b4 -side left -expand true -fill x

  pack append  .
  pack .cur -side top -expand true -fill x
  pack .field -side top -pady 10 -expand true -fill x
  pack .debug -side top -expand true -fill both
  pack .bb -side top -expand true -fill x

  wm geometry . +0-172
  wm deiconify .
  update idletasks
}

proc pdebug { str } {
  global mfp
  if {$mfp(debug)} {
    .debug.txt insert end  "$str"
    .debug.txt yview -pickplace end
  }
  update idletasks
}
if {$mfp(debug)} {puts stderr "VIEWER: viewer.tcl has been loaded"}


# Local Variables: ***
# mode:tcl ***
# End: ***
