#!/usr/local/bin/wish -f
global mf mfp

# $Header: /usr/people/raines/prog/tk/tkmail/RCS/tkmail,v 1.5 93/05/29 23:28:17 raines Exp Locker: raines $
###########################################################################
set mfp(version) "1.3"
set mfp(globalset) "/usr/local/lib/tkmail/settings"
set mfp(readme) {

   TkMail v1.3  -- A Tk/Tcl interface to Mail
	    		by Paul Raines (raines@bohr.physics.upenn.edu)

   TkMail is an X windows interface to the unix Mail command built
   using Tk/Tcl.  Reading, sending, and managing mail messages can
   almost all be done using only the mouse (except for the body of
   the message, of course).  TkMail depends on the unix 'Mail' and
   'sendmail' commands to do the real network mail work. It puts
   the X window interface on top and adds some additional features.

   Major Features:
	- listbox interface to messages for easy click and read
	- viewed messages are editable inside viewing widget so
	  one can add or delete text for printing or piping selections
	- dynamic (at startup) menus for quick access to mail folders
	  for reading, copying, and moving messages
	- button bar for quick composing, replying to, and forwarding of
	  mail messages
	- built in 'biff' for detecting new mail
	- easy inclusion of files into message compositions with
	  automatic uuencoding and compression, if desired
	- access alternate editor for compositions to send
	- spell check compositions using a X windows interface
	  (must have the ispell program version 3.0.09)
	- filtering of headers to remove clutter (only on viewing)

   See the USER SETTINGS section below to get a real feel for
   what TkMail can do.

   This program is still very young so don't trust it without testing!
   Make a backup copy of your ~/mbox file and some of your folders
   and then test out the features on those files.

INSTALLATION:
     (1) set the first line in the tkmail file to the location of 
	 your wish executable. Do the same for the altedit.tk file.
     (2) copy the files utils.tk, ispell.tk, and altedit.tk to the
         location of your choice and then edit the mf(utils), mf(spell),
         and mf(alteditlib) variables below to reflect that location
     (3) edit the other user setting in the tkmail file, particularly
         mf(cmd), mf(nopaging), mf(system), and mf(fdir). Also make
         sure you have the bitmaps in the locations given below. See
         BUGS below about mf(beep). Alternately, you set the variable
	 mfp(globalset) at the top of the tkmail to a file that contains
	 your site-wide settings.
     (4) Read the top of the ispell.tk file and follow the 
         installation instruction there.

COMPATIBILITY:
     I developed this app on SGI, Sun, and IBM workstations.  This app
     depends on the following working:

         echo "h 1" | Mail -N -f folder

     This lists the headers of messages in folder to standard out. 
     Make sure the mf(nopaging) variable is set right below or you will
     only get the a list of the first screenful of messages.
     On SUN, setting it to "set screen=100000" will do the trick

         echo "2" | Mail -N -f folder
     Displays message 2 on stdout.

         echo "d 3 4 8 9" | Mail -N -f folder
     Deletes messages 3,4,8, and 9 from folder.

         echo "s 4 5 9 24 file" | Mail -N -f folder
     Saves messages 4,5,9, and 24 from folder in file.

         echo "q" | Mail -N
     Prints a message like "Held  4 messages in /usr/mail/raines"
     if you have messages in your system box.

         cat file | sendmail -bm -t

     Mails file to users in To:, Cc:, and Bcc: fields. This bypasses
     the Mail command and therefore options such as 'record'.
     I have created the mf(record) user setting to account
     for this. It might be necessary to hack the mf_recordmsg
     procedure below to make it fit your mail folder format.

     I you find others that are bypassed and you
     want them included in TkMail, please mail me about it.
     I need to use sendmail for arbitrary header definition.	

     You can change the name of the Mail command in the user
     settings below, but it must support the structure above.
     If you find that you have to make changes to the code to
     get it to work on your machine, please e-mail me and tell
     me what machine you are on and what changes you made.

GENERAL USAGE:

     From your unix command line, type

     	tkmail [-iconic] [folder] &

     If you do not specify a folder, your ~/mbox will be show.

     Most things in the menu should be self evident (yeah...that is
     what they always say). A few menu items are also on the button
     bar. All menu items can be accessed with the accelerator characters
     using the Alt key or Alt-Shift keys. Because of the hassle of 
     dealing with the system inbox, I have the app incorporate new mail
     into the ~/mbox instead of treating it as just another folder.
     I hope to some day figure out a good way to not have to do this.

     When you get new mail, just click the 'Incorp' button and you
     will be taken to the ~/mbox folder at the first new message.
     The ~/mbox serves as the central place to dish out messages to
     your folders using the copy and move menu items. If you set
     mf(autoincorp) to '1', TkMail will automatically incorporate
     new mail if it is in the iconic state.

     If a ~/.tkmail or ~/tk/tkmail file exists, they are sourced
     so that the individual user can change the User Settings 
     described below. The knowledgable Tk programmer can also do
     much more customization. One can define a procedure called
     mf_compose_hook in these files that will be evaluated each
     time a compose message window is created. See the included
     example.tkmail.

     The viewed message will always have the '>' character in front of it.
     The Delete, Copy, Move, Save, and Print commands operate on all
     selected messages. The Reply and Forward operate on only the
     currently viewed one.

     For efficiency and undo ability, deletes are not done immediately.
     Deletes are processed when changing folders or quitting. This means
     that if the something kills TkMail, any impending deletes are
     not done.

ALIASES:

     TkMail parses your ~/.mailrc file on startup and creates an alias
     database. It understands lines beginning with 'a' or 'alias', it
     only does one sweep, so aliases refering to other aliases will not
     expand and will cause your mail to bounce. Normally, sendmail will
     not send the mail to any of the users listed if any one of them
     is not valid.

MOUSE BINDINGS:

     Mouse bindings in the header list box are:
         B1 Click - view clicked message and make it the sole selection
         B1 Drag - select additional messages for later possible operations.
         B2 Click - unselect message clicked if not the viewed one.
         B3 Click - select message clicked with viewing.
         B3 Drag - select all messages dragged over.

     Mouse bindings in the message and compose window are normal Text bindings.
     B2 will insert the current selection and B3 will unselect it.
     See the 'text' man page.

     Using B3 on the Reply button will cause the message to be
     automatically included in the compose text. Using B3 on the
     Compose button will do the same but leave the To: field
     blank (for Forwarding with editing).

KEY BINDINGS:

     Menu item can be accessed through keystrokes by using the Alt (Meta)
     key with the letter in brackets next to the item. If the letter is
     in uppercase, the Shift key is required.  See the example.tkmail
     file to see how to set your own key settings.

     Set the mf(emacs) variable to '1' to get the typical emacs bindings
     for Control a,e,f,b,n,p,k,w,y. No arguments are supported and only a
     single kill buffer exists. Control-k only kills to the end of the line.
     Control-space sets the mark for the Control-w command.

     Control-Shift-S will pop up a window to enter a string for searching.
     Control-s will search again. Only the forward direction is supported.

     Control-Home goes to top of text, Control-End to the end. Home, End,
     PageUp, PageDown, and the arrow keys do their obvious operations.

READER MENU:
     Folder:	Open...		Select new folder to view
		Quit		Quit TkMail (processes deletions)
		Main Box	View main mail box (usually ~/mbox)
		Incorporate	Incorporate new mail from system box
		<Folders>	List of folders in folder directory to open

     Edit:	Cut		Cut selection into private buffer
		Copy		Copy selection into private buffer
		Paste		Paste private buffer at insert mark
		Save Sel	Select file to current selection to
		Print Sel	Select printer command to print selection to
		Eval TCL Sel	Evaluate the current selection as Tcl
		Pipe UNIX Sel	Pipe the current selection through a UNIX cmd
				  and choose what to do with results

     Mesg:	Next		View next message in list
		Prev		View previous message in list
		Delete		Mark selected messages for deletion
		Undelete All	Unmark messages mark for deletion
		Copy =>		Show folders to copy selected messages to
		Move =>		Show folders to move selected messages to
		Save		Select file to save message to
		Print		Select printer command to print message with
		Quick Decode    Search mesg for encoding, and decode it

     Mail:	Compose		Popup window to compose new message
		Reply		Popup window to reply to current message
		Forward		Forward selected messages to a user
		Gripe		Mail a gripe message to author
		Restore Last	Restore last composition window 
		<Names>		List of addresses in mf(quicksend)

     Options:   Reverse Order	toggle mf(reverse)
		Format Headers  toggle mf(format)
		Auto Incorp	toggle mf(autoincorp)
		Save Last Send  toggle mf(savesend)
		Parse From:	toggle mf(getfrom)
		All Settings    popup text window to edit all settings

     Help:	if your reading this, you know what it does

COMPOSE MENU:

     File:	Insert File...	Select a file to insert into composition
				   - can encode, compress, and prefix
		Insert Mesg...  Select messages to insert into composition
		Send		Mail the composition
		Cancel		Kill the window and don't send

     Edit:	Cut		Cut selection into private buffer
		Copy		Copy selection into private buffer
		Paste		Paste private buffer at insert mark
		Ispell		Run tkispell on composition
		Show Cc/Bcc	Toggle on/off the Cc/Bcc entries

SETTINGS:

     Settings you will most likely want to customize in ~/.tkmail file:

	mf(mbox) 	- main mail box for messages to incorporate to
				( this is usually ~/mbox )
	mf(fdir) 	- directory containing mail folders
				( hidden files will be ignored )
	mf(reverse)	- set to 1 so that most recent message at top
	mf(maxfold)	- maximum number of folders displayed in menu
	mf(print)	- UNIX commmand for printing (with %F subst for file)
	mf(tfont)	- font for text widgets to use
	mf(tcolor)	- color for tex widgets to use as background
	mf(fcolor)	- color for tex widgets to use as foreground
	mf(headheight)	- number of messages visible in top list at one time
	mf(geom)	- initial geometry for main mail window
	mf(compgeom)	- initial geometry of compose window
	mf(mailchk)	- number of milliseconds between mail checks
	mf(prefix)	- prefix for included messages
	mf(format)	- format message header list in pretty way
	mf(record)	- file to record outgoing messages in
				( set to {} for none )
	mf(beep)	- alternate command to run when mail received
				( set to {} for no beep )
	mf(autoincorp)	- whether to incorporate new mail automatically
				( only when iconized )
	mf(showcc)	- whether to show Cc and Bcc entries in compose
	mf(savesend)	- whether to save text of last compose window
	mf(mesgstate)	- 'normal' or 'disabled' to allow mesg window editing
	mf(quicksend)	- list of common addresses for composing mail
				( can include aliases )
	mf(getfrom)	- whether to try to parse the From: field
        mf(emacs)	- whether to put in emacs bindings
				( see key bindings above )
        mf(headers)	- text to automatically put at top of all compositions
	mf(signature)	- file to include at end of compostions
	mf(encoder)	- program to use for encoding inserted files
	mf(compress)	- program to use for compressing inserted files
	mf(hdrstrip)	- header fields to strip out of messages
				( use this cautiously as it can delete body )
        mf(altedit)	- command to run alternate editor for compositions
				( must be an X-window command )
	mf(pipedir)	- directory to run piped UNIX commands

     All widget configuration options can be accessed through your
     .Xdefaults file in the normal Tk way.  See the 'options' and
     individual widget man pages for more info. A few examples are:

        tkmail*Button.foreground:       blue
	tkmail*Entry.font:	   	fixed
	tkmail*Scrollbar.background:	grey42

     Settings that were most likely set to the proper defaults during
     installation include:

        mfp(globalset)	- locations of site-wide settings file
	mf(utils) 	- location of utils.tk
	mf(spell) 	- location of ispell.tk
	mf(alteditlib)  - location of altedit.tk
	mf(cmd) 	- BSD style mail command
	mf(deliver) 	- mail delivery command
	mf(system) 	- system mail box where mail is received
	mf(nopaging)	- mail setting to prevent screen paging
	mf(tmp)		- temporary file directory to use
	mf(fdownbmp)	- bitmap to use to signal no mail
	mf(fupbmp)	- bitmap to use to signal mail
	mf(writebmp)	- bitmap to use for compose window

     The site adminstrator only has to modify the mfp(globalset)
     variable at the top of the tkmail source file and can put
     the rest of the settings in the pointed to file.

     An example line in the .tkmail file is shown below (note that
     you must use $env(HOME) instead of ~ for your home directory):

	set mf(fdir) $env(HOME)/Mail

IMPLEMENTATION:
     The header list box is not a Tk Listbox but a text widget I hacked
     up so it could support non-contiguous selection. However, because
     it is a text widet, there is no horizontal scrolling.

     Every operation on a folder is a separate system call to the
     Mail command. It would be much more efficient to use one open
     pipeline command, but in standard Tcl, if one does a gets when
     there is nothing to get, the app hangs. As I see it, I have three
     choices which I list below in the order of my preference.

         (1) Require having one of the several extensions
     		to wish that solve this problem.
         (2) Leave it like it is now
         (3) Write my own Mail command.
     
     The main change would be the speed with which a message is displayed.
     I would appreciate your opinions on this matter.

     When reading the stdout from the Mail command:
       Any ending lines that doesn't begin with a space in the header
       are interpreted as Mail information and discarded.
       All lines at the bottom of a message until the first blank
       (moving up from the end) are interpreted as Mail information.

BUGS:
     
     The bell doesn't work when the shell TkMail was run from
     is killed. This is because the normal way of sounding the
     bell in Tcl is writing a '\007' to standard out. So until
     Tk supports a bell command, you must either make sure not
     to quit the shell TkMail was run from, or get one of the
     many patches to Tk that implement a bell. I have created
     the user setting of mf(beep) to put an alternate bell command
     if you have one.

     I am sure there are plenty of other bugs in this, so mail me
     about them (raines@bohr.physics.upenn.edu)

TODO:
     more command line options than iconic, folder
     save dynamic user setting procedure
     MIME ( a long way off )
     incoming mail filtering
     parse .mailrc to merge more Mail and TkMail options
     use autoload or toolbox for utils.tk and ispell.tk
     handle unshar similar to uudecode
     subdir structure for folders
     compressed folder support
     better emacs bindings

COPYRIGHT:
     Copyright 1993 by Paul Raines (raines@bohr.physics.upenn.edu)

     Permission to use, copy, modify, and distribute this
     software and its documentation for any purpose and without
     fee is hereby granted, provided that the above copyright
     notice appear in all copies.  The University of Pennsylvania
     makes no representations about the suitability of this
     software for any purpose.  It is provided "as is" without
     express or implied warranty.

DISCLAIMER:
     UNDER NO CIRCUMSTANCES WILL THE AUTHOR OF THIS SOFTWARE OR THE
     UNIVERSITY OF PENNSYLVANIA BE RESPONSIBLE FOR ANY DIRECT OR
     INCIDENTAL DAMAGE ARISING FROM THE USE OF THIS SOFTWARE AND ITS
     DOCUMENTATION. THE SOFTWARE HEREIN IS PROVIDED "AS IS" WITH NO
     IMPLIED OBLIGATION TO PROVIDE SUPPORT, UPDATES, OR MODIFICATIONS.

HISTORY:
  v1.0 alpha
     93-05-19    released original version

  v1.0 alpha r1
     93-05-19    insert file no longer uses prefix
	         Re: now works correctly

  v1.0 alpha r2
     93-05-21    fixed bug in mf_delete with finding closest undeleted mesg
	         fixed yview's to use mfp(curtndx)

  v1.0
     93-05-28    fixed bug where $mf(cmd) was not used everywhere
     93-05-29    fixed bug in Next and Prev
		 directed bell to stderr
		 fixed system folder stat bug (thanks Dan Schenck)
		 changed sendmsg to use sendmail so headers can be included
		 fixed bug with calling selmesg after delmesg
		 sendmail now in background, tabbing in compose
		 reverse order available, user def file sourced
		 implemented mf_compose_hook, itegrated tkispell
		 added Gripe
     93-05-30    x-y posting of alert and getstring

  v1.1
     93-05-31    fixed compose window bug with xpos

  v1.2
     93-05-31    forward now uses $mf(deliver)
     	         now keeps directories out of listed folders

     93-06-02   Put checkmail before setup on startup so iconic option works
     	    	Fixed getopts and Main Box menu item to allow user to specify
     	       		a main box other than ~/mbox
     	    	Changed incorporate mail to use "s" instead of "mb" 
     	    	Implemented mf_recordmsg
     93-06-03   Got rid of update idletasks, put in alternate bell ability
     	    	Implemented getting number of message in system box.

  v1.3
     93-07-18   added quotespecial to formatting to prevent errors
    	    	added .signature inclusion
     	    	changed mail delivery to use sendmail '-t' option
     	    	added headers option to mf_compose
     	    	parse for Cc: and Reply-To: and include Cc: in Reply
     93-07-29   added Help menu item and help display procedure 
		added mf(autoincorp)
     93-07-30   added parsing of .mailrc for aliases
		added mf_getaddress for expanding aliases
		added Cc and Bcc entries in compose with hide option
		added print and save selections
     93-07-31   added mf(mesgstate)
		added mf(quicksend)
		added mf(savesend) to be able to recover last compose
		changed mf_compose to take Cc and incmesg options
		added Button bindings for <3> events and made <3> on
			Reply button a reply with message include
		added mf(getfrom) and From: parsing
		added text searching with Control-S
		added emacs bindings
		added automatic header inclusion
		added toggling of showing Cc and Bcc in compose menu
		added TCL evaluation of selection
		turned off the message flashing to be indefinite
     93-08-03   added pipe of selection to unix command
		added including file with encoding, compression, prefix
		added <3>  on Compose to auto message include
     93-08-04   added mf_quickdecode
     93-08-05	fixed button/menu sync bug
		added mfp(globalset)
		added mf_defaultset to fix ignoring of some settings
		added %F construct to mf(print)
		fixed mf_checkmail to have flag up for non-empty folder
		added header stripping with mf(hdrstrip)
     93-08-06	added alternate editor ability
     93-08-07   added options menu
		moved private variables to mfp array
		moved Alt-x,c,v bindings to general Text
	        added mf_defaultstart
		fixed mf_checkmail to keep flag up for autoincorporaters
     93-08-08   added saving of options in mf_evalopt
		added pipe directory

 Please mail any suggestions, bugs, whines to raines@bohr.physics.upenn.edu
 The latest version is available by anonymous ftp at
	bohr.physics.upenn.edu:pub/tk/tkmail.tk.Z

}

#default startup only settings
proc mf_defaultstart { } {
  global mf mfp env

  # location of utils.tk
  set mf(utils) /usr/local/lib/tkmail/utils.tk
  # location of ispell.tk (set to "" if you don't want to use it)
  set mf(spell) /usr/local/lib/tkmail/ispell.tk
  # location of altedit.tk
  set mf(alteditlib) /usr/local/lib/tkmail/altedit.tk
  # name of BSD style mail command (do not put options here!)
  set mf(cmd) Mail
  # name of mail delivery command (takes address as additional argument)
  set mf(deliver) "/usr/lib/sendmail -bm -t"
  # name of main MBOX
  set mf(mbox) $env(HOME)/mbox
  # name of system inbox
  set mf(system) /usr/spool/mail/[exec whoami]
  # name of folder directory
  set mf(fdir) $env(HOME)/Mail
  # maximum number of folders listed in menu
  set mf(maxfold) 25
  # Mail setting needed to prevent screen paging
  set mf(nopaging) "set screen=100000"
  # geometry of main reader
  set mf(geom) 600x720
  # whether to use emacs type bindings
  set mf(emacs) 1

}

# default user settings
proc mf_defaultset {} {
  global mf env

  # USER SETTINGS
  #
  #   * spaces inclosed in {}'s are not just white-space and
  #       can cause errors in evaluation.
  #   * use of the $mf or $env arrays require you to replace
  #	  the surrounding {}'s with ""'s
  #   * other setting not listed here can be added, but changing
  #	  them will have unpredicable or no effect after startup

  # if you want reverse time order in mail header, set to 1
  set mf(reverse) 0
  # printing command where %F is the imaginary file to print
  set mf(print) "lpr %F"
  # temporary file directory
  set mf(tmp) /usr/tmp
  # font for text widget
  set mf(tfont) -adobe-courier-medium-r-normal--12-120-75-75-m-70-iso8859-1
  # color for text widget background
  set mf(tcolor) seashell2
  # color for text widget foreground
  set mf(fcolor) black
  # number messages displayed in header widget
  set mf(headheight) 8
  # geometry of composition tool
  set mf(compgeom) 600x420
  # milliseconds to check mail
  set mf(mailchk) 10000
  # include file prefix
  set mf(prefix) ">> "
  # bitmaps
      set mf(fdownbmp) "/usr/include/X11/bitmaps/flagdown"
      set mf(fupbmp) "/usr/include/X11/bitmaps/flagup"
      set mf(writebmp) "/usr/include/X11/bitmaps/letters"
  # formated header (needed for SUN's and IBM's for pretty layout)
  set mf(format) 1
  # file to record all outgoing messages in. Please see the mf_recordmsg
  # procedure below to see if you may need to hack it to give the
  # proper format for your mail folder.
  set mf(record) {}
  # command to eval for a beep (in case you have a better one like me)
  # set it to {} for no beep
  set mf(beep) {puts stderr "\007\007" nonewline}
  # whether to incorporate mail automatically when detected
  set mf(autoincorp) 0
  # whether to show the Cc and Bcc fields in compose
  set mf(showcc) 1
  # whether to store a copy of the last sent message for possible restore
  set mf(savesend) 1
  # either 'normal' or 'disabled' to allow message window editing
  set mf(mesgstate) normal
  # list of common addresses for composing (should be set in ~/.tkmail)
  set mf(quicksend) {}
  # whether to try to parse From: field
  set mf(getfrom) 0
  # text to automatically put at top of every composition
  set mf(headers) {}
  # name of .signature to put at end of messages
  set mf(signature) $env(HOME)/.signature
  # program to encode inserted files
  set mf(encoder) uuencode
  # program to compress inserted files and suffix it uses
  set mf(compress) compress
  set mf(compress_suffix) Z
  # header fields to strip out of display. This feature can end up
  # deleting message body if your headers are in a nonstandard format,
  # so set to {} if you are having problems
  set mf(hdrstrip) "Received: Status:"
  # alternate editor command. If it is not an X windows editor, you
  # must use xterm (i.e. xterm  -e vi %F). %F is imaginary file name
  set mf(altedit) "emacs %F"
  # directory to run piped UNIX commands in
  set mf(pipedir) $env(HOME)
}

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

# Private program variables
# name of user
    set mfp(user) [exec whoami]
# current name of folder file
    set mfp(file) ""
# list of files in folder directory
    set mfp(foldfiles) ""
# toplevel widget
    set mfp(top) .mf
# header list holding & displaying widget
    set mfp(head) $mfp(top).head.list
# message holding & displaying widget
    set mfp(mesg) $mfp(top).mesg.txt
# header status label
    set mfp(hstat) $mfp(top).hstat
# message status label
    set mfp(mstat) $mfp(top).mstat
# temp text processing widget
    set mfp(tmptxt) $mfp(top).tmptxt
# number of messages in folder
    set mfp(mesgnum) 0
# list of mesg status #'s
    set mfp(statlist) ""
# current message number
    set mfp(curnum) 0
# current message text index
    set mfp(curtndx) 0.0
# delete message list
    set mfp(delmesg) ""
# bitmaps
    set mfp(flag) -1
# compose window counter
    set mfp(compcnt) 0
# current message from
    set mfp(curfrom) ""
# current message Cc
    set mfp(curcc) ""
# current message subj
    set mfp(cursubj) ""
# cutbuffer
    set mfp(cutbuffer) ""
# system file last size
    set mfp(lastsize) 0
# save of text of last sent message
    set mfp(savesendtxt) ""
# search string for text
    set mfp(searchstr) ""
# settings for file insertion
    set mfp(ins_compress) 0
    set mfp(ins_encode) 0
    set mfp(ins_prefix) 0
# name of settings file read
    set mfp(setfile) ""
# alias lists
    set mfp(aliasnames) ""
    set mfp(aliasaddr) ""

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

# this is from Tom Phelps
proc lreverse {l} {
   set l2 ""
   for {set i [expr [llength $l]-1]} {$i>=0} {incr i -1} {
      lappend l2 [lindex $l $i]
   }
   return $l2
}

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

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

# selection_if_any - return selection if it exists, else {}
#   this is from kjx@comp.vuw.ac.nz (R. James Noble)
proc selection_if_any {} {
  if {[catch {selection get} s]} {return ""} {return $s}
}

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

    if {[info exists env(TMPDIR)]} {
	set dir $env(TMPDIR)
    } else {
	set dir /tmp
    }

    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
}

proc textlinelimits { tw } {

    set pheight [expr [winfo height $tw]-8]
    set top [lindex [split [$tw index @2,2] .] 0]
    set bott [lindex [split [$tw index @2,$pheight] .] 0]
    return [list $top $bott [expr $bott-$top]]

}

proc emacsbind { } {

    bind Text <Control-a> {%W mark set insert {insert linestart};
	%W yview -pickplace insert}
    bind Text <Control-b> {%W mark set insert insert-1c;
	%W yview -pickplace insert}
    bind Text <Control-d> {%W delete insert insert+1c;
        %W yview -pickplace insert}
    bind Text <Control-e> {%W mark set insert {insert lineend};
	%W yview -pickplace insert}
    bind Text <Control-f> {%W mark set insert insert+1c;
	%W yview -pickplace insert}
    bind Text <Control-n> {%W mark set insert insert+1l;
	%W yview -pickplace insert}
    bind Text <Control-p> {%W mark set insert insert-1l;
	%W yview -pickplace insert}
    bind Text <Control-h> {
       if [catch {%W delete sel.first sel.last}] {
	   %W delete [%W index insert-1c]
       }
    }
    bind Text <Control-k> {
	global mfp
	set mfp(cutbuffer) [%W get insert {insert lineend}];
	%W delete insert {insert lineend};
        %W yview -pickplace insert
	}
    bind Text <Control-w> {
	global mfp
	if {[%W compare emacs < insert]} {
	    set mfp(cutbuffer) [%W get emacs insert]
	    %W delete emacs insert
        } else {
            set mfp(cutbuffer) [%W get insert emacs]
            %W delete insert emacs
        }
    }
    bind Text <Control-y> {
	global mfp
	%W insert insert $mfp(cutbuffer)
	%W yview -pickplace insert
    }
    bind Text <Control-space> {%W mark set emacs insert}

   bind Entry <Control-KeyPress-d> {%W delete [%W index insert]}
   bind Entry <Control-KeyPress-k> {%W delete [%W index insert] end}
   bind Entry <Control-KeyPress-f> {%W icursor [expr [%W index insert]+1]}
   bind Entry <Control-KeyPress-b> {%W icursor [expr [%W index insert]-1]}
   bind Entry <Control-KeyPress-a> {%W icursor 0}
   bind Entry <Control-KeyPress-e> {%W icursor end}

   bind Entry <Control-KeyPress-h> {
      if [catch {%W delete sel.first sel.last}] \
         {%W delete [expr [%W index insert]-1]}
   }


}

proc TextSearch { tw string {sndx 1.0} {view 1}} {

    set sline [lindex [split $sndx .] 0]
    set schar [lindex [split $sndx .] 1]
    scan [$tw index end] %d numLines
    for {set i $sline} {$i <= $numLines} {incr i} {
        if {[regexp -indices $string \
		[$tw get $i.$schar [$tw index "$i.0 lineend"]] ndx]} {
            if {$view} {
                $tw mark set insert $i.[expr [lindex $ndx 1]+$schar+1]
                $tw yview -pickplace $i.0
	    }
            return $i.[expr [lindex $ndx 1]+$schar+1]
        }
        set schar 0
    }
    return 0
}

############################################################

proc mf_quickdecode { tw } {
    global mf mfp env

    set bndx [TextSearch $tw {^begin [0-9]* } 1.0 0]
    if {$bndx == 0} {
	mf_mailerror "Can't find uuencode beginning."
	return 0
    }
    set endx [TextSearch $tw "^end" $bndx 0]
    if {$endx == 0} {
	mf_mailerror "Can't find uuencode ending."
	return 0
    }
    set bndx [$tw index "$bndx linestart"]
    set endx [$tw index "$endx lineend + 1 c"]

    set tdir [pwd]
    cd $mf(pipedir)
    set cmd "exec uudecode << \\\[$tw get $bndx $endx\\\]\n"
    if {[catch "eval $cmd" res]} {
        mf_mailerror $res
        return 0
    }
    cd $tdir

    set file [lrange [$tw get $bndx "$bndx lineend"] 2 end]
    $mfp(mstat) configure -text "Uudecoded $file"
    return 1
}

proc mf_searchtxt { tw getnew } {
    global mf mfp

    set xpos [expr [winfo rootx $mfp(top)]+[winfo width $mfp(top)]/3]
    set ypos [expr [winfo rooty $mfp(top)]+[winfo height $mfp(top)]/3]
    if {$getnew || $mfp(searchstr) == ""} {
	set mfp(searchstr) [tkgetstring "Search for:" $mfp(searchstr) $xpos $ypos]
	if {$mfp(searchstr) == ""} {return 0 }
    }
    focus $tw

    if {![TextSearch $tw $mfp(searchstr) [$tw index insert]]} {
	eval $mf(beep); return 0
    }
    return 1
}

# parse user .mailrc file for aliases and some options
proc mf_parsemailrc {} {
    global mf mfp env

    set mfp(aliasnames) ""
    set mfp(aliasaddr) ""
    if {![file exists $env(HOME)/.mailrc]} {
        return
    }
    set fid [open $env(HOME)/.mailrc r]

    while {[gets $fid line] != -1} {
	set tmp [lindex $line 0]
	case $tmp {
	    {a alias} {
		lappend mfp(aliasnames) [lindex $line 1]
		lappend mfp(aliasaddr) [lrange $line 2 end]
		}
	    {alt alternates} {set mf(alternates) [lrange $line 2 end]}
	}
    }
    close $fid
}

proc mf_getaddress { aliases } {
    global mf mfp

    set addresses ""
    foreach al $aliases {
	if {[set ndx [lsearch $mfp(aliasnames) $al]] > -1} {
	    lappend addresses [lindex $mfp(aliasaddr) $ndx]
	} else {
	    lappend addresses $al
	}
    }
    return $addresses
}

# check for new mail and set icon and beep accordingly
proc mf_checkmail {} {
    global mf mfp

    if { [file exists $mf(system)] && ([file size $mf(system)] != 0)} {
	if {[file size $mf(system)] != $mfp(lastsize) \
	       && ([file mtime $mf(system)] >= [file atime $mf(system)])} {
	    eval $mf(beep)
	    $mfp(mstat) configure -text "New Mail has arrived!"
	    if {$mf(autoincorp) && ![winfo ismapped $mfp(top)]} { 
		mf_incorporate
	    }
	}
        if {$mfp(flag) != 1} {
            wm iconbitmap $mfp(top) "@$mf(fupbmp)"
            set mfp(flag) 1
        }
    } else {
        if {$mfp(flag) != 0} {
	    # people with autoincorp still want to know if they got mail
            if {!$mf(autoincorp) || [winfo ismapped $mfp(top)]} {
        	wm iconbitmap $mfp(top) "@$mf(fdownbmp)"
        	set mfp(flag) 0
 	    }
        }
    }
    if { [file exists $mf(system)] } {
      set mfp(lastsize) [file size $mf(system)]
    } else {
      set mfp(lastsize) 0
    }
}

# schedule a mail check
proc mf_schedule {} {
    global mf mfp
    
    mf_checkmail
    # check if person thought it was for seconds and not milliseconds
    if {$mf(mailchk) < 1000} {set mf(mailchk) [expr $mf(mailchk)*1000]}
    after $mf(mailchk) mf_schedule
}

# popup a error message and beep
proc mf_mailerror { res } {
    global mf mfp

    set xpos [expr [winfo rootx $mfp(top)]+[winfo width $mfp(top)]/3]
    set ypos [expr [winfo rooty $mfp(top)]+[winfo height $mfp(top)]/3]
    eval $mf(beep)
    flush stderr
    tkgetokay "ERROR: $res" $xpos $ypos
    focus $mfp(mesg)
}

# Incorporate new mail into the main mbox
proc mf_incorporate { } {
    global mf mfp

    set cmd "exec echo \"q\" | $mf(cmd) -N"
    if {[catch "eval $cmd" res]} {
	eval $mf(beep)
	flush stderr
	$mfp(mstat) configure -text $res
	return 0
    } else {
	if {![regexp {Held[ ]*([0-9][0-9]*)[ ]*mess} $res trash newm]} {
	    puts stdout $res
	    set newm 1
	}
    }

    set cmd "exec echo \"s ^-$ $mf(mbox)\" | $mf(cmd) -N"
    if {[catch "eval $cmd" res]} {
	mf_mailerror $res
	mf_setupfolder $mf(mbox) 1
	return 0
    } else {
	if {$mf(reverse)} {
	    mf_setupfolder $mf(mbox) 1
	} else {
	    mf_setupfolder $mf(mbox) -$newm
	}
    }
    $mfp(mstat) configure -text $res
    $mfp(head) yview -pickplace $mfp(curtndx)
    return 1
}

# Alternate Incorporate New Mail that uses the 'mb' command so
# that message status is preserved (i.e. 'N' 'U').
#proc mf_incorporate { } {
#    global mf mfp
#
#    set cmd "exec echo \"mb ^-$\" | $mf(cmd) -N"
#    if {[catch "eval $cmd" res]} {
#        puts stderr "\007" nonewline
#        flush stderr
#    } else {
#        if {![regexp {Saved ([0-9][0-9]*) mess} $res trash newm]} {
#            set newm 1
#        }
#        mf_setupfolder $mf(mbox) -$newm
#    }
#    $mfp(mstat) configure -text $res
#$mfp(mesgnum)\"
#    $mfp(head) yview -pickplace $mfp(curtndx)
#    return 1
#}



# Read in folder headers and set view to first message
proc mf_setupfolder { folder ndx } {
    global mf mfp

    if {![file exists $folder]} {return 0}
    
    mf_procdelete
    set mfp(file) $folder
    set mfp(curnum) 0
    set mfp(curtndx) 0.0

    $mfp(head) delete 1.0 end
    $mfp(mesg) configure -state normal
    $mfp(mesg) delete 1.0 end
    $mfp(mesg) configure -state $mf(mesgstate)
    if {$mf(reverse)} {
	set cmd {exec echo \"$mf(nopaging)\nh 1\" | $mf(cmd) -N -f $mfp(file) | sort -r +0.2}
    } else {
	set cmd {exec echo \"$mf(nopaging)\nh 1\" | $mf(cmd) -N -f $mfp(file)}
    }
    if {[catch "eval $cmd" res]} {
	mf_mailerror $res
	set mfp(file) ""
	return 0
    } else {
	if {$mf(format)} {
	    set res [split $res "\n"]
	    foreach line $res {
		set line [quotespecial $line]
		regsub {^>} $line { } line
		set line "  [string range $line 2 end]"
		set line [format "%5d %-18.18s %s" \
		    [lindex $line 0] [lindex $line 1] [lrange $line 2 end]]
		$mfp(head) insert end [unquotespecial $line]
		$mfp(head) insert end "\n"
	    }
	} else {
	    $mfp(head) insert end $res
	    $mfp(head) insert end "\n"
	}
    }

    set mfp(mesgnum) [lindex [split [$mfp(head) index end] .] 0]
    while {[$mfp(head) get $mfp(mesgnum).0]!=" " && $mfp(mesgnum)!=0} {
	if {[$mfp(head) get $mfp(mesgnum).0]==">"} {
	    $mfp(head) delete $mfp(mesgnum).0
	    $mfp(head) insert $mfp(mesgnum).0 " "
	    break
	}
	$mfp(head) delete $mfp(mesgnum).0 "$mfp(mesgnum).0 lineend + 1 c"
	incr mfp(mesgnum) -1
     }

    $mfp(hstat) configure -text "Current folder: $mfp(file)"

    if {$mfp(mesgnum)==0} {
	set mfp(curnum) 0
	set mfp(curtndx) 0.0
        $mfp(mesg) configure -state normal
        $mfp(mesg) delete 1.0 end
        $mfp(mesg) configure -state $mf(mesgstate)
	$mfp(mstat) configure -text "Message 0 out of 0"
	return 1
    }
    if {$ndx < 0} {set ndx [expr $ndx+1+$mfp(mesgnum)]}
    if {$ndx > $mfp(mesgnum) || $ndx < 1} {
	mf_selmesg $mfp(mesgnum).0 1
    } else {
	mf_selmesg $ndx.0 1
    }
    return 1
}

# get the From and Subject from header
proc mf_parseheader { } {
    global mf mfp

    set msubj ""
    set mfrom ""
    set dellist ""
    set mfp(curcc) ""
    set mfp(curfrom) [lindex [$mfp(mesg) get 1.0 "1.0 lineend"] 1]

    scan [$mfp(mesg) index end] %d numLines
    for {set i 2} {$i <= $numLines} {incr i} {
	set line [$mfp(mesg) get $i.0 "$i.0 lineend"]

	if {[lsearch $mf(hdrstrip) [lindex $line 0]] > -1} {
	    set dellist [linsert $dellist 0 $i.0]
	    set j $i
	    while {1} {
		incr j
		set nchar [$mfp(mesg) get $j.0]
		if {$nchar == " " || $nchar == "\t"} {
		    incr i
		    set dellist [linsert $dellist 0 $i.0]
		} else {break}
	    }
	}

	if {[regexp {^Subject:([ -~]*)} $line trash msubj]} {}
	if {[regexp {^Reply-To:([ -~]*)} $line trash mfrom]} {}
	if {[regexp {^Cc:([ -~]*)} $line trash mfp(curcc)]} {}
        if {[regexp {^From:[ -~]*} $line trash] && $mf(getfrom)} {
	    if {![regexp {<(.*)>} $line trash mfrom]} {
		set mfrom [lindex $line 1]
	    }
	}
	# we assume the header is separated from the body by blank line
	if {$line == ""} {break}
    }
    if {$mfrom != ""} {set mfp(curfrom) $mfrom}
    set mfp(cursubj) [string trim $msubj]

    $mfp(mesg) configure -state normal
    foreach ndx $dellist {
	$mfp(mesg) delete $ndx "$ndx lineend +1c"
    }
    $mfp(mesg) configure -state $mf(mesgstate)

    return 1
}

# display a message in the mesg text widget
proc mf_dispmesg { ndx } {
    global mf mfp
    
    bind $mfp(top).head.list <ButtonRelease-1> " "
    if {$ndx < 1 || $ndx > $mfp(mesgnum)} {return 0}

    $mfp(mesg) configure -state normal
    $mfp(mesg) delete 1.0 end

    set cmd {exec echo \"$ndx\" | $mf(cmd) -N -f $mfp(file)}
    if {[catch "eval $cmd" res]} {
	mf_mailerror $res
	return 0
    } else {
	$mfp(mesg) insert end $res
	set lline [lindex [split [$mfp(mesg) index end] .] 0]
	while {[$mfp(mesg) get $lline.0]!="\n" && $lline!=0} {
	    $mfp(mesg) delete $lline.0 "$lline.0 lineend + 1 c"
	    incr lline -1
	 }
    }

    if {[$mfp(mesg) get 1.0 1.4]=="Mess"} {
	$mfp(mesg) delete 1.0 "1.0 lineend + 1 c"
    }
    $mfp(mstat) configure -text "Message $ndx out of $mfp(mesgnum)"
    mf_parseheader
    $mfp(mesg) configure -state $mf(mesgstate)

    return 1
}

# select a message in the header list according to mode
proc mf_selmesg { tndx mode } {
    global mf mfp
    
    # if tndx not in allowed range of header lines
    if {[$mfp(head) compare $tndx >= "end - 1 c"] || \
	[$mfp(head) compare $tndx < 1.0]} {return 0}
    
    # This will need special handling eventually
    if {$mode<2} {
	if {[$mfp(head) compare $mfp(curtndx) < end] && $mfp(curtndx)!="0.0"} {
	    $mfp(head) delete $mfp(curtndx) "$mfp(curtndx) + 2 c"
	    $mfp(head) insert $mfp(curtndx) "  "
	}
	$mfp(head) delete $tndx "$tndx + 2 c"
	$mfp(head) insert $tndx "> "
    }

    # set the highlighting
    if {$mode!=3} {$mfp(head) tag remove selmesg 0.0 end}
    if {$mode!=2} {
	$mfp(head) tag add selmesg $tndx  [$mfp(head) index "$tndx lineend + 1 chars"]
    } else {
	set anchor $mfp(curtndx)
	if {[$mfp(head) compare $tndx < $anchor]} {
	    $mfp(head) tag add selmesg $tndx  [$mfp(head) index "$anchor lineend + 1 chars"]
	} else {
	    $mfp(head) tag add selmesg $anchor  [$mfp(head) index "$tndx lineend + 1 chars"]
	}
	$mfp(head) yview -pickplace "$tndx - 1 line"
	$mfp(head) yview -pickplace "$tndx + 1 line"
    }
    
    # if in show mode
    if {$mode<2} {
	# get message number of selected message
	set ndx [mf_text2mesg $tndx]
	if {$ndx < 1 || $ndx > $mfp(mesgnum)} {return 0}
	if {$ndx==$mfp(curnum)} {return 1}

	if {$mode} {
	    mf_dispmesg $ndx
	} else {
	    bind $mfp(top).head.list <ButtonRelease-1> "mf_dispmesg $ndx"
	}
    
	set mfp(curnum) $ndx
	set mfp(curtndx) $tndx
	$mfp(head) yview -pickplace $mfp(curtndx)
    }
    focus $mfp(mesg)
    return 1
}

proc mf_deselmesg { tndx } {
    global mf mfp
    
    if {$tndx==$mfp(curtndx)} {return 0}
    $mfp(head) tag remove selmesg $tndx  [$mfp(head) index "$tndx lineend + 1 chars"]
    return 1
}

proc mf_text2mesg { tndx } {
    global mf mfp

    if {[regexp {[0-9][0-9]*} \
	[$mfp(head) get $tndx "$tndx lineend"] ndx]} {
	return [string trim $ndx]
    } else {
	puts stderr "Error getting message number on line $tndx"
	return 0
    }
    
}

proc mf_getselrange { } {
    global mf mfp

    set raw [$mfp(head) tag ranges selmesg]
    set tndxlist ""
    set cnt 0
    while {$cnt<[llength $raw]} {
	set tndx [lindex $raw $cnt]
	incr cnt
	while {[$mfp(head) compare $tndx < [lindex $raw $cnt]]} {
	    lappend tndxlist $tndx
	    set tndx [$mfp(head) index "$tndx + 1 line"]
	}
	incr cnt
    }
    return $tndxlist
}

proc mf_getselmesg { } {
    global mf mfp

    set ndxlist ""
    foreach tndx [mf_getselrange] {lappend ndxlist [mf_text2mesg $tndx]}
    return $ndxlist
}

proc mf_delmesg { } {
    global mf mfp

    set chk 1
    foreach tndx [lreverse [mf_getselrange]] {    
	lappend mfp(delmesg) [mf_text2mesg $tndx]
	$mfp(head) delete $tndx "$tndx lineend + 1 c"
	if {[$mfp(head) compare end <= 1.0]} {
	    set mfp(curnum) 0
	    set mfp(curtndx) 0.0
	    return 1
	}
	set chk 0
    }
    if {$chk} {puts stdout "No selected messages!"; return 1}

    if {[$mfp(head) compare "end - 1 line linestart" > $mfp(curtndx)]} {
	mf_selmesg [$mfp(head) index $mfp(curtndx)] 1
    } else {
	mf_selmesg [$mfp(head) index "end - 1 line linestart"] 1
    }
    $mfp(head) yview -pickplace $mfp(curtndx)

    return 1
}

proc mf_undelete { } {
    global mf mfp

    set mfp(delmesg) ""
    mf_setupfolder $mfp(file) $mfp(curnum)
    return 1
}

proc mf_procdelete { } {
    global mf mfp

    if {![llength $mfp(file)] || ![llength $mfp(delmesg)]} {return 0}

    set cmd {exec echo \"d $mfp(delmesg)\" | $mf(cmd) -N -f $mfp(file)}
    if {[catch "eval $cmd" res]} {
	mf_mailerror $res
	set mfp(file) ""
	return 0
    }
    $mfp(mstat) configure -text $res
    set mfp(delmesg) ""
    return 1
}

proc mf_save { filename type } {
    global mf mfp

    if {[file exists $filename]} {
	set xpos [expr [winfo rootx $mfp(top)]+[winfo width $mfp(top)]/3]
	set ypos [expr [winfo rooty $mfp(top)]+[winfo height $mfp(top)]/3]
	if {[tkgetokay "Append to existing file ($filename)?" $xpos $ypos]} {
	    set cmd "exec touch $filename"
	    if {[catch "eval $cmd" res]} {
		mf_mailerror $res
		return 0
	    }
	} else {
	    return 0
	}
    }

    case $type {
	{ mesg } {
	    set cmd {exec echo \"s [mf_getselmesg] $filename\" | $mf(cmd) -N -f $mfp(file)}
	    if {[catch "eval $cmd" res]} {
		mf_mailerror $res
		return 0
	    } }
	{ sel } { 
		set fid [open $filename a]
		puts $fid "\n[selection get]"
		close $fid }
        default { mf_mailerror "Invalid save type"; return 0}
    }

    $mfp(mstat) configure -text $res
    return 1
}

proc mf_copymesg { filename } {
    global mf mfp
    
    if {![file exists $filename]} {
	set xpos [expr [winfo rootx $mfp(top)]+[winfo width $mfp(top)]/3]
	set ypos [expr [winfo rooty $mfp(top)]+[winfo height $mfp(top)]/3]
	if {[tkgetokay "Create new folder ($filename)?" $xpos $ypos]} {
	    set cmd "exec touch $filename"
	    if {[catch "eval $cmd" res]} {
		mf_mailerror $res
		return 0
	    }
	} else {
	    return 0
	}
    }

    set cmd {exec echo \"s [mf_getselmesg] $filename\" | $mf(cmd) -N -f $mfp(file)}
    if {[catch "eval $cmd" res]} {
	mf_mailerror $res
	return 0
    }
    $mfp(mstat) configure -text $res
    return 1
}

proc mf_movemesg { filename } {
    global mf mfp
    
    if {![file exists $filename]} {
	set xpos [expr [winfo rootx $mfp(top)]+[winfo width $mfp(top)]/3]
	set ypos [expr [winfo rooty $mfp(top)]+[winfo height $mfp(top)]/3]
	if {[tkgetokay "Create new folder ($filename)?" $xpos $ypos]} {
	    set cmd "exec touch $filename"
	    if {[catch "eval $cmd" res]} {
		mf_mailerror $res
		return 0
	    }
	} else {
	    return 0
	}
    }

    set cmd {exec echo \"s [mf_getselmesg] $filename\" | $mf(cmd) -N -f $mfp(file)}
    if {[catch "eval $cmd" res]} {
	mf_mailerror $res
	return 0
    }

    $mfp(mstat) configure -text $res
    mf_delmesg
    return 1
}

proc mf_print { type } {
    global mf mfp

    set xpos [expr [winfo rootx $mfp(top)]+[winfo width $mfp(top)]/3]
    set ypos [expr [winfo rooty $mfp(top)]+[winfo height $mfp(top)]/3]
    set pcmd [tkgetstring "Print command (use %F for filename):" \
	$mf(print) $xpos $ypos]
    if {![llength $pcmd]} {return 0}
    set mf(print) $pcmd

    set tfile [tmpfile tkmail]
    case $type {
	{ mesg } {
    	    set cmd { exec echo \"s [mf_getselmesg]  $tfile\" | $mf(cmd) -N -f $mfp(file)}
	    if {[catch "eval $cmd" res]} {
		mf_mailerror $res
		return 0
	    } }
	{ sel } { exec cat > $tfile << [selection get] }
	default { 
	    mf_mailerror "Invalid print type"; return 0 }
    }

    if {![regsub -all {%F} $pcmd "$tfile" pcmd]} {
        set pcmd "$pcmd $tfile" 
    }
    set cmd {exec $pcmd}
    if {[catch "eval $cmd" res]} {
	mf_mailerror $res
	return 0
    }
    $mfp(mstat) configure -text $res

    set cmd {exec /bin/rm $tfile}
    if {[catch "eval $cmd" res]} {
	mf_mailerror $res
	return 0
    }
    
    return 1
}

proc mf_quit { } {
    global mf mfp

    mf_procdelete
    exit
}

proc mf_insertprefix { txt } {
    global mf mfp

    $mfp(tmptxt) delete 1.0 end
    $mfp(tmptxt) insert end $txt
    scan [$mfp(tmptxt) index end] %d numLines
    for {set i 1} {$i <= $numLines} {incr i} {
	 $mfp(tmptxt) insert $i.0 $mf(prefix)
    }
}

proc mf_recordmsg { txt } {
    global mf mfp

    if {$mf(record)==""} {return 1}

    set date [exec date "+%a %h %d %T 19%y"]
# IF YOUR MAILER STORES MESSAGE WITH DIFFERENT FIRST LINE, YOU WILL
# NEED TO HACK THE LINE BELOW
    $txt insert 1.0 "From $mfp(user) $date\n"
    $txt insert end "\n"
    set rid [open $mf(record) a+]
    puts $rid [$txt get 1.0 end]
    close $rid

    return 1
}

proc mf_forward { } {
    global mf mfp env

    set xpos [expr [winfo rootx $mfp(top)]+[winfo width $mfp(top)]/3]
    set ypos [expr [winfo rooty $mfp(top)]+[winfo height $mfp(top)]/3]
    set sendto [tkgetstring "Forward message to:" {} $xpos $ypos]
    set sendto [mf_getaddress $sendto]
    if {![llength $sendto]} {return 0}

    mf_insertprefix [$mfp(mesg) get 2.0 end]

    if {$mf(headers) != ""} {$mfp(tmptxt) insert 1.0 "$mf(headers)\n"}
    $mfp(tmptxt) insert 1.0 "To: $sendto\nSubject: $mfp(cursubj)\n"
    if {[file exists $mf(signature)]} {
	$mfp(tmptxt) insert end "\n[exec cat $mf(signature)]"
    }
    set cmd {exec $mf(deliver) << \[$mfp(tmptxt) get 1.0 end\] &}
    if {[catch "eval $cmd" res]} {
	mf_mailerror $res
	return 0
    }
    mf_recordmsg $mfp(tmptxt)

    $mfp(mstat) configure -text "Mail forwarded to $sendto"
    focus $mfp(mesg)
    return 1
}

proc mf_reply { {type 0} } {
    global mf mfp

    if {$type > 0} {set incmesg 1} else {set incmesg 0}
    if {$type == 2} {set from ""} else {set from $mfp(curfrom)}

    if {[string match {[Rr][Ee]:} [string range $mfp(cursubj) 0 2]]} {
	mf_compose $from $mfp(cursubj) $mfp(curnum) {} $mfp(curcc) $incmesg
    } else {
	mf_compose $from "Re: $mfp(cursubj)" $mfp(curnum) {} $mfp(curcc) $incmesg
    }
}

proc mf_insertfile { filename mfc } {
    global mf mfp

    if {![llength $filename]} {return 1}

    if {![file exists $filename]} {
	mf_mailerror "File $filename does not exist."
	return 0
    }

    if {$mfp(ins_encode) && $mfp(ins_compress)} {
	set cmd "exec cat $filename | $mf(compress) | $mf(encoder) [exec basename $filename].$mf(compress_suffix)"
    } elseif {$mfp(ins_encode)} {
	set cmd "exec $mf(encoder) $filename [exec basename $filename]"
    } else {
	set cmd "exec cat $filename"
    }

    if {$mfp(ins_prefix)} {
        mf_insertprefix "[eval $cmd]\n"
	$mfc.comp.txt insert insert [$mf(tmptext) get 1.0 end]
    } else {
    	$mfc.comp.txt insert insert "[eval $cmd]\n"
    }
    return 1
}

proc mf_insertset { fs } {
    global mf mfp

    set mfp(ins_compress) 0
    set mfp(ins_encode) 0
    set mfp(ins_prefix) 0

    frame $fs.ins
    checkbutton $fs.ins.encode -text "Uuencode" -variable mfp(ins_encode) \
	-command {if {$mfp(ins_compress) && !$mfp(ins_encode)} {set mfp(ins_compress) 0}}
    checkbutton $fs.ins.compress -text "Compress" -variable mfp(ins_compress) \
	-command {if {!$mfp(ins_encode) && $mfp(ins_compress)} {set mfp(ins_encode) 1}}
    checkbutton $fs.ins.prefix -text "Prefix" -variable mfp(ins_prefix)

    pack append $fs.ins $fs.ins.encode {left fillx} \
	$fs.ins.compress {left fillx} $fs.ins.prefix {left fillx}
    pack after $fs.file $fs.ins {top}
}

proc mf_cutedit { tw } {
    global mf mfp

    set mfp(cutbuffer) [$tw get sel.first sel.last]
    $tw delete sel.first sel.last
}

proc mf_copyedit { tw } {
    global mf mfp

    set mfp(cutbuffer) [$tw get sel.first sel.last]
}

proc mf_pasteedit { tw } {
    global mf mfp

    $tw insert insert $mfp(cutbuffer)
}

proc mf_insertmessages { mfc } {
    set xpos [expr [winfo rootx $mfc]+[winfo width $mfc]/3]
    set ypos [expr [winfo rooty $mfc]+[winfo height $mfc]/3]
    mf_insertmesg [tkgetstring {Message numbers to insert:} \
	{} $xpos $ypos] $mfc
}

proc mf_compose { {sendto "" } {subject ""} {mesg ""} {headers ""} \
		  {curcc ""} {incmesg 0} } {
    global mf mfp

    set cnt 0
    while {[winfo exists .mfc${cnt}]} {incr cnt}
    set mfc .mfc${cnt}

    toplevel $mfc
    wm iconbitmap $mfc "@$mf(writebmp)"
    wm iconname $mfc "Compose"
    wm title $mfc "Compose ($mfc)"
    wm geometry $mfc $mf(compgeom)
    wm minsize $mfc 400 400
    wm protocol $mfc WM_DELETE_WINDOW mf_cancelmesg

    frame $mfc.menu -relief raised -bd 2
    menubutton $mfc.menu.file -text {File} -menu $mfc.menu.file.m
    menubutton $mfc.menu.edit -text {Edit} -menu $mfc.menu.edit.m

    menu $mfc.menu.file.m
    $mfc.menu.file.m add command -label {Insert File . . .} -accelerator {[i]} \
        -command "fileselect mf_insertfile {File:} 0 mf_insertset $mfc"
    $mfc.menu.file.m add command -label {Insert Messages . . .} \
	-accelerator {[m]} -command "mf_insertmessages $mfc"
    $mfc.menu.file.m add separator
    $mfc.menu.file.m add command -label {Send} -accelerator {[s]} \
        -command "mf_sendmesg $mfc"
    $mfc.menu.file.m add command -label {Cancel} -accelerator {[q]} \
        -command "mf_cancelmesg $mfc"
    $mfc.menu.file.m add separator
    $mfc.menu.file.m add command -label {Alternate Editor} -accelerator {[a]} \
        -command "mf_altedit $mfc.comp.txt"

    menu $mfc.menu.edit.m
    $mfc.menu.edit.m add command -label {Cut} -accelerator {[x]} \
        -command "mf_cutedit $mfc.comp.txt"
    $mfc.menu.edit.m add command -label {Copy} -accelerator {[c]} \
        -command "mf_copyedit $mfc.comp.txt"
    $mfc.menu.edit.m add command -label {Paste} -accelerator {[v]} \
        -command "mf_pasteedit $mfc.comp.txt"
    $mfc.menu.edit.m add separator
    $mfc.menu.edit.m add command -label {Ispell} \
        -command "if {\[info procs tkispell_text\]==\"\"} {source $mf(spell)}; \
	    tkispell_text $mfc.comp.txt"
    $mfc.menu.edit.m add command -label {TCL Evaluate Sel} \
	-command {
	    if {[catch {eval [selection_if_any]} res]} {
		mf_mailerror $res
		return 0
	    }
	}
    $mfc.menu.edit.m add command -label {UNIX Pipe Sel . . .} \
	-command "mf_pipesel $mfc.comp.txt"
    $mfc.menu.edit.m add separator
    $mfc.menu.edit.m add checkbutton -label "Show Cc/Bcc" \
	-variable mf(showcc) -command "mf_togglecc $mfc"

    pack append $mfc.menu $mfc.menu.file {left} $mfc.menu.edit {left}

    frame $mfc.to
    label $mfc.to.lbl -text "To:" -width 8 -anchor w
    entry $mfc.to.ent -relief sunken
    label $mfc.to.dum -text " " -width 1
    pack append $mfc.to $mfc.to.lbl {left} \
	$mfc.to.ent {left pady 8 expand fillx} $mfc.to.dum {left}
    $mfc.to.ent insert end $sendto
    bind $mfc.to.ent <Tab> "focus $mfc.subj.ent"
    bind $mfc.to.ent <Return> "focus $mfc.subj.ent"

    frame $mfc.subj
    label $mfc.subj.lbl -text "Subject:" -width 8 -anchor w
    entry $mfc.subj.ent -relief sunken
    label $mfc.subj.dum -text " " -width 1
    pack append $mfc.subj $mfc.subj.lbl {left} \
	$mfc.subj.ent {left pady 8 expand fillx} $mfc.subj.dum {left}
    $mfc.subj.ent insert end $subject

    if {$mf(showcc)} {
	bind $mfc.subj.ent <Tab> "focus $mfc.cc.ent"
	bind $mfc.subj.ent <Return> "focus $mfc.cc.ent"
    } else {
	bind $mfc.subj.ent <Tab> "focus $mfc.comp.txt"
	bind $mfc.subj.ent <Return> "focus $mfc.comp.txt"
    }

    frame $mfc.cc
    label $mfc.cc.lbl -text "Cc:" -width 8 -anchor w
    entry $mfc.cc.ent -relief sunken
    label $mfc.cc.dum -text " " -width 1
    pack append $mfc.cc $mfc.cc.lbl {left} \
	$mfc.cc.ent {left pady 8 expand fillx} $mfc.cc.dum {left}
    $mfc.cc.ent insert end $curcc
    bind $mfc.cc.ent <Tab> "focus $mfc.bcc.ent"
    bind $mfc.cc.ent <Return> "focus $mfc.bcc.ent"

    frame $mfc.bcc
    label $mfc.bcc.lbl -text "Bcc:" -width 8 -anchor w
    entry $mfc.bcc.ent -relief sunken
    label $mfc.bcc.dum -text " " -width 1
    pack append $mfc.bcc $mfc.bcc.lbl {left} \
	$mfc.bcc.ent {left pady 8 expand fillx} $mfc.bcc.dum {left}
    bind $mfc.bcc.ent <Tab> "focus $mfc.comp.txt"
    bind $mfc.bcc.ent <Return> "focus $mfc.comp.txt"

    frame $mfc.comp
    scrollbar $mfc.comp.yscroll -command "$mfc.comp.txt yview" -relief raised
    text $mfc.comp.txt \
	-yscroll "mf_compscrollset $mfc.comp.yscroll $mfc.comp.txt" \
	-relief sunken -bd 2 -font $mf(tfont) -bg $mf(tcolor) -fg $mf(fcolor)
    proc mf_compscrollset { sw tw args } {
	global mfp

	eval "$sw set $args"
	set mfp($tw) $args
    }
    bind $mfc.comp.txt <Next> "
      global mfp
      %W mark set insert \[lindex \$mfp(%W) 3\].0
      %W yview insert "
    bind $mfc.comp.txt <Prior> "
      global mfp
      %W mark set insert \[expr \[lindex \$mfp(%W) 2\]-\[lindex \$mfp(%W) 1\]\].0
      %W yview insert "


    bind $mfc.comp.txt <Meta-KeyPress-i> "$mfc.menu.file.m invoke 0"
    bind $mfc.comp.txt <Meta-KeyPress-m> "$mfc.menu.file.m invoke 1"
    bind $mfc.comp.txt <Meta-KeyPress-s> "$mfc.menu.file.m invoke 3"
    bind $mfc.comp.txt <Meta-KeyPress-q> "$mfc.menu.file.m invoke 4"
    bind $mfc.comp.txt <Meta-KeyPress-a> "$mfc.menu.file.m invoke 6"
    bind $mfc.comp.txt <Meta-Tab> "focus $mfc.to.ent"

    pack append $mfc.comp $mfc.comp.yscroll {left filly} \
	$mfc.comp.txt {expand fill}

    frame $mfc.bb
    button $mfc.bb.mesg -text "Insert Message" -command "mf_insertmesg $mesg $mfc $mfp(file)"
    button $mfc.bb.file -text "Insert File" -command "$mfc.menu.file.m invoke 0"
    button $mfc.bb.alt -text "Alt Editor" -command "$mfc.menu.file.m invoke 6"
    button $mfc.bb.send -text "Send" -command "$mfc.menu.file.m invoke 3"
    button $mfc.bb.cancel -text "Cancel" -command "$mfc.menu.file.m invoke 4"

    pack append $mfc.bb $mfc.bb.mesg {left expand fill} \
	$mfc.bb.file {left expand fill} \
	$mfc.bb.alt {left expand fill} \
	$mfc.bb.send {left expand fill} \
	$mfc.bb.cancel {left expand fill}

    if {$mf(showcc)} {
	pack append $mfc $mfc.menu {top fillx} $mfc.to {top fillx} \
	    $mfc.subj {top fillx} $mfc.cc {top fillx} \
	    $mfc.bcc {top fillx} $mfc.comp {top expand fill} \
	    $mfc.bb {top fillx}
    } else {
	pack append $mfc $mfc.menu {top fillx} $mfc.to {top fillx} \
	    $mfc.subj {top fillx} $mfc.comp {top expand fill} \
	    $mfc.bb {top fillx}
    }

    if {[info proc mf_compose_hook]!=""} {
	mf_compose_hook $mfc
    }

    # Put in text requested
    if {[llength $headers] != 0} {$mfc.comp.txt insert end "$headers\n"}
    if {[llength $mf(headers)] != 0} {$mfc.comp.txt insert end "$mf(headers)\n"}
    if {$incmesg} {mf_insertmesg $mesg $mfc $mfp(file)}
    set tndx [$mfc.comp.txt index end]
    if {[file exists $mf(signature)]} {
	$mfc.comp.txt insert end "\n[exec cat $mf(signature)]"
    }
    $mfc.comp.txt mark set insert $tndx
    
    if {[$mfc.to.ent get]==""} {
	focus $mfc.to.ent
    } else {
	focus $mfc.comp.txt
    }
}

proc mf_altedit { tw } {
    global mf mfp

    set tfile [tmpfile tkmail]
    if {![regsub -all {%F} $mf(altedit) "$tfile" tcmd]} {
        set tcmd "$mf(altedit) $tfile"
    }
    
    set cmd "exec cat > $tfile << \\\[$tw get 1.0 end\\\]"
    if {[catch "eval $cmd" res]} {
        mf_mailerror $res
        return 0
    }

    $tw configure -state disabled
    set cmd "exec $mf(alteditlib) \\\{[winfo name .]\\\} $tw $tfile {$tcmd} &"
    if {[catch "eval $cmd" res]} {
        mf_mailerror $res
        return 0
    }

}

proc mf_togglecc { mfc } {
    global mf mfp

    if {$mf(showcc)} {
        bind $mfc.subj.ent <Tab> "focus $mfc.cc.ent"
        bind $mfc.subj.ent <Return> "focus $mfc.cc.ent"
        pack after $mfc.subj $mfc.cc {top fillx} \
            $mfc.bcc {top fillx}
    } else {
        bind $mfc.subj.ent <Tab> "focus $mfc.comp.txt"
        bind $mfc.subj.ent <Return> "focus $mfc.comp.txt"
	pack unpack $mfc.cc
	pack unpack $mfc.bcc
    }

}

proc mf_insertmesg {mesgs mfc {mfile ""}} {
    global mf mfp

    if {![llength $mfile]} {set mfile $mfp(file)}

    foreach ndx $mesgs {

	set cmd {exec echo \"$ndx\" | $mf(cmd) -N -f $mfp(file)}
	if {[catch "eval $cmd" res]} {
	    mf_mailerror $res
	    return 0
	} else {
	    mf_insertprefix $res
	    $mfc.comp.txt insert insert "[$mfp(tmptxt) get 1.0 end]\n"
	}

    }
    return 1
}

proc mf_sendmesg { mfc } {
    global mf mfp env

    set sendto [$mfc.to.ent get]
    set sendto [mf_getaddress $sendto]
    set subject [$mfc.subj.ent get]
    if {![llength $sendto] || ![llength $subject]} {
	mf_mailerror "No To: or Subject: given!"
	return 0
    }
    if {$mf(showcc)} {
	set sendalso [$mfc.bcc.ent get]
	if {$sendalso != ""} {
	    set sendalso [mf_getaddress $sendalso]
	    $mfc.comp.txt insert 1.0 "Bcc: $sendalso\n"
	}
	set sendalso [$mfc.cc.ent get]
	if {$sendalso != ""} {
	    set sendalso [mf_getaddress $sendalso]
	    $mfc.comp.txt insert 1.0 "Cc: $sendalso\n"
	}
    }

    $mfc.comp.txt insert 1.0 "To: $sendto\nSubject: $subject\n"
    set cmd {exec $mf(deliver) << \[$mfc.comp.txt get 1.0 end\] &}
    if {[catch "eval $cmd" res]} {
	mf_mailerror $res
	return 0
    }
    mf_recordmsg $mfc.comp.txt
    if {$mf(savesend)} { set mfp(savesendtxt) [$mfc.comp.txt get 1.0 end] }

    destroy $mfc
    $mfp(mstat) configure -text "Mail sent to $sendto"
    focus $mfp(mesg)
    return 1
}

proc mf_cancelmesg { mfc } {
    global mf mfp

    if {$mf(savesend)} { set mfp(savesendtxt) [$mfc.comp.txt get 1.0 end] }

    destroy $mfc
    $mfp(mstat) configure -text "Compose cancelled"
    focus $mfp(mesg)
    return 1
}

proc mf_getopts { cargs } {
    global mf mfp

    set cnt 0
    while {$cnt < [llength $cargs]} {
	set opt [lindex $cargs $cnt]
	case $opt {
	    { -iconic } { wm iconify $mfp(top) }
	    { tkmail } { }
	    default { 
		if {[file exists $opt]} { 
		    set mfp(file) $opt
		} else {
		    puts stdout "Folder $opt not found."
		}
	    }
	}
	incr cnt 
    }
    
}

proc mf_disphelp {section} {
    global mf mfp
    set w $mfp(top)_help

    if { ![winfo exists $w] } {
	toplevel $w
	wm title $w "TkMail v$mfp(version) Help"
        text $w.txt -yscroll "$w.scr set" \
		-cursor left_ptr -relief sunken \
		-font $mf(tfont) -bg $mf(tcolor)
	scrollbar $w.scr -command "$w.txt yview"
	button $w.close -text "Close" -command "wm withdraw $w"
	pack append $w $w.close {bottom fillx} $w.scr {left filly} \
		$w.txt {left expand fill}
		
    } else { wm deiconify $w }

    $w.txt configure -state normal
    $w.txt delete 0.0 end
    $w.txt insert end $mfp(readme)
    $w.txt configure -state disabled

    if {$section != "TOP"} {TextSearch $w.txt ^$section}
}    

proc mf_dispopt { } {
    global mf mfp
    set w $mfp(top)_opt

    if { ![winfo exists $w] } {
	toplevel $w
	wm title $w "TkMail v$mfp(version) Options"
        text $w.txt -yscroll "$w.scr set" \
		-cursor left_ptr -relief sunken \
		-font $mf(tfont) -bg $mf(tcolor)
	scrollbar $w.scr -command "$w.txt yview"
	frame $w.bb
	button $w.bb.accept -text "Accept" \
	    -command "mf_evalopt $w 0; wm withdraw $w"
	button $w.bb.save -text "Accept & Save" \
	    -command "mf_evalopt $w 1; wm withdraw $w"
	button $w.bb.cancel -text "Cancel" \
	    -command "grab release $w; wm withdraw $w"
	pack append $w.bb $w.bb.accept { left expand fillx } \
		$w.bb.save { left expand fillx } \
		$w.bb.cancel { left expand fillx }
	pack append $w $w.bb {bottom expand fillx} $w.scr {left filly} \
		$w.txt {left expand fill}
		
    } else { wm deiconify $w }

    grab set $w
    $w.txt delete 0.0 end
    $w.txt insert end [info body mf_defaultset]

    scan [$w.txt index end] %d numLines
    for {set i 0} {$i <= $numLines} {incr i} {
	set line [$w.txt get $i.0 "$i.0 lineend"]
	if {[string first "  set mf" $line] != -1} {
	    set var [lindex $line 1]
	    set val [eval "set $var"]
	    $w.txt delete $i.0 "$i.0 lineend"
	    $w.txt insert $i.0 "  set $var {$val}"
	}
    }
}    

proc mf_evalopt { w {save 0} } {
    global mf mfp env

    grab release $w
    eval [$w.txt get 1.0 end]

    if {$save} {
	set varlist ""

	$w.txt delete 1.0 end
	if {[file exists $mfp(setfile)]} {

	    $w.txt insert 1.0 [exec cat $mfp(setfile)]
	    scan [$w.txt index end] %d numLines
	    for {set i 0} {$i <= $numLines} {incr i} {
		set line [$w.txt get $i.0 "$i.0 lineend"]
		if {[regexp {([ \t]*)set[ \t]*(mf\(([0-9a-z_]*)\))} \
			    $line trash sp var name]} {
		    set val [eval "set $var"]
		    lappend varlist $name
		    $w.txt delete $i.0 "$i.0 lineend"
		    $w.txt insert $i.0 "${sp}set $var {$val}"
		}
	    }

	} else {
	    set mfp(setfile) $env(HOME)/.tkmail
        }

	foreach name [array names mf] {
	    if {[lsearch $varlist $name] == -1} {
		set val [eval "set mf($name)"]
		$w.txt insert end "\n  set mf($name) {$val}"
	    }
	}
	puts stdout "\n\n"
	$w.txt insert end "\n"
	exec cat > $mfp(setfile) << [$w.txt get 1.0 end]
    }

    mf_setupfolder $mfp(file) [lindex [split $mfp(curtndx) .] 0]
    $mfp(top).head.list configure -height $mf(headheight) \
	-font $mf(tfont) -bg $mf(tcolor)
    $mfp(top).mesg.txt configure -font $mf(tfont) -bg $mf(tcolor) \
	-fg $mf(fcolor)

    return 1
}

proc mf_pipesel { tw } {
    global mf mfp env

    set tmp [pwd]

    set xpos [expr [winfo rootx $mfp(top)]+[winfo width $mfp(top)]/3]
    set ypos [expr [winfo rooty $mfp(top)]+[winfo height $mfp(top)]/3]
    set cmd [tkgetstring "Pipe Command:" {} $xpos $ypos mf_pipeselset]
    if {$cmd == ""} { return 0 }
    cd $mf(pipedir)
    set cmd "exec $cmd << \\\[selection_if_any\\\]"
    if {[catch "eval $cmd" res]} {
        mf_mailerror $res
        return 0
    }

    $mfp(mstat) configure -text "Pipe command: $cmd"
    if {$mf(pipe_res) == 1} {
        if {![catch "set tndx [$tw index sel.first]"]} {
	    $tw delete sel.first sel.last
	} else { set tndx [$tw index insert] }
	$tw insert $tndx "$res\n"
    } elseif {$mf(pipe_res) == 2} {
        if {[catch "set tndx [$tw index sel.last+1c]"]} {
            set tndx [$tw index insert ]
        }
        $tw insert $tndx "$res\n"
    }
	
    cd $tmp
    return 1
}

proc mf_pipeselset { sw } {
    global mf mfp env

    set mf(pipe_res) 0

    frame $sw.pipe
    label $sw.pipe.lbl -text "Do what with results?" -pady 5
    radiobutton $sw.pipe.discard -text "Discard" -variable mf(pipe_res) \
	-value 0
    radiobutton $sw.pipe.replace -text "Replace" -variable mf(pipe_res) \
	-value 1
    radiobutton $sw.pipe.append -text "Append" -variable mf(pipe_res) \
	-value 2

    frame $sw.pdir
    label $sw.pdir.lbl -text "Execute in what directory?" -pady 5
    entry $sw.pdir.ent -relief sunken -textvariable mf(pipedir)

    pack append $sw.pipe $sw.pipe.lbl {top fillx}  \
	$sw.pipe.discard {left fillx} \
        $sw.pipe.replace {left fillx} $sw.pipe.append {left fillx}
    pack append $sw.pdir $sw.pdir.lbl {top fillx} \
	$sw.pdir.ent {top fillx}
    pack after $sw.ent $sw.pipe {top} $sw.pdir {top expand fillx}
}

######### MAIN ##################

wm withdraw .

toplevel $mfp(top)
wm iconname $mfp(top) "TkMail"
wm title $mfp(top) "TkMail v$mfp(version)"
wm minsize $mfp(top) 400 400
wm protocol $mfp(top) WM_DELETE_WINDOW mf_quit

# get opts (eventually use topgetopts.tcl from Kennard White)
mf_getopts $argv

text $mfp(top).tmptxt

frame $mfp(top).menu -relief raised
menubutton $mfp(top).menu.folder -text {Folder} -menu $mfp(top).menu.folder.m
menubutton $mfp(top).menu.edit -text {Edit} -menu $mfp(top).menu.edit.m
menubutton $mfp(top).menu.mesg -text {Mesg} -menu $mfp(top).menu.mesg.m
menubutton $mfp(top).menu.mail -text {Mail} -menu $mfp(top).menu.mail.m
menubutton $mfp(top).menu.opt  -text {Options} -menu $mfp(top).menu.opt.m
menubutton $mfp(top).menu.help -text {Help} -menu $mfp(top).menu.help.m

menu $mfp(top).menu.folder.m
$mfp(top).menu.folder.m add command -label {Open . . .} -accelerator {[o]} \
    -command "fileselect mf_setupfolder {Folder:} 0 {} 1"
$mfp(top).menu.folder.m add command -label {Quit} -accelerator {[q]} \
    -command "mf_quit"
$mfp(top).menu.folder.m add separator
$mfp(top).menu.folder.m add command -label {Main Box} -accelerator {[b]} \
    -command "mf_setupfolder \$mf(mbox) 1"
$mfp(top).menu.folder.m add command -label {Incorporate New Mail} -accelerator {[i]} \
    -command "mf_incorporate"
$mfp(top).menu.folder.m add separator

menu $mfp(top).menu.edit.m
$mfp(top).menu.edit.m add command -label {Cut} -accelerator {[x]} \
    -command "mf_cutedit $mfp(top).mesg.txt"
$mfp(top).menu.edit.m add command -label {Copy} -accelerator {[c]} \
    -command "mf_copyedit $mfp(top).mesg.txt"
$mfp(top).menu.edit.m add command -label {Paste} -accelerator {[v]} \
    -command "mf_pasteedit $mfp(top).mesg.txt"
$mfp(top).menu.edit.m add separator
$mfp(top).menu.edit.m add command -label {Save Selection . . .} \
    -command {fileselect mf_save {File:} 0 {} sel}
$mfp(top).menu.edit.m add command -label {Print Selection . . .} \
    -command {mf_print sel}
$mfp(top).menu.edit.m add command -label {TCL Evaluate Sel} \
    -command {
	if {[catch {eval [selection_if_any]} res]} {
            mf_mailerror $res
            return 0
        }
    }
$mfp(top).menu.edit.m add command -label {UNIX Pipe Sel . . .} \
    -command "mf_pipesel $mfp(top).mesg.txt"

menu $mfp(top).menu.mesg.m
$mfp(top).menu.mesg.m add command -label {Next} -accelerator {[n]} \
    -command {mf_selmesg [$mfp(head) index "$mfp(curtndx) + 1 line"] 1}
$mfp(top).menu.mesg.m add command -label {Prev} -accelerator {[p]} \
    -command {mf_selmesg [$mfp(head) index "$mfp(curtndx) - 1 line"] 1}
$mfp(top).menu.mesg.m add command -label {Delete} -accelerator {[d]} \
    -command "mf_delmesg"
$mfp(top).menu.mesg.m add command -label {Undelete All} -accelerator {[u]} \
    -command "mf_undelete"
$mfp(top).menu.mesg.m add cascade -label {Copy =>} -accelerator {[y]} \
    -menu $mfp(top).menu.mesg.m.copy
$mfp(top).menu.mesg.m add cascade -label {Move =>} -accelerator {[m]} \
    -menu $mfp(top).menu.mesg.m.move
$mfp(top).menu.mesg.m add separator
$mfp(top).menu.mesg.m add command -label {Save . . .} -accelerator {[s]} \
    -command {fileselect mf_save {File:} 0 {} mesg}
$mfp(top).menu.mesg.m add command -label {Print . . .} -accelerator {[r]} \
    -command {mf_print mesg}
$mfp(top).menu.mesg.m add separator
$mfp(top).menu.mesg.m add command -label {Quick Decode} \
    -command "mf_quickdecode $mfp(top).mesg.txt"

# Dynamic menus for adding folder entries
menu $mfp(top).menu.mesg.m.copy
menu $mfp(top).menu.mesg.m.move

menu $mfp(top).menu.mail.m
$mfp(top).menu.mail.m add command -label {Compose} -accelerator {[C]} \
    -command {mf_compose {} {} $mfp(curnum) }
$mfp(top).menu.mail.m add command -label {Reply} -accelerator {[R]} \
    -command {mf_reply}
$mfp(top).menu.mail.m add command -label {Forward} -accelerator {[F]} \
    -command {mf_forward}
$mfp(top).menu.mail.m add separator
$mfp(top).menu.mail.m add command -label {Gripe} \
    -command {mf_compose raines@bohr.physics.upenn.edu "TkMail Gripe" $mfp(curnum)}
$mfp(top).menu.mail.m add command -label {Restore Last} \
    -command {mf_compose {} {} $mfp(curnum) $mfp(savesendtxt)}

menu $mfp(top).menu.opt.m
$mfp(top).menu.opt.m add checkbutton -label "Reverse Order" \
    -variable mf(reverse) -command {
	global mfp
        mf_setupfolder $mfp(file) 1
        }
$mfp(top).menu.opt.m add checkbutton -label "Format Headers" \
    -variable mf(format) -command {
        global mfp
        mf_setupfolder $mfp(file) [lindex [split $mfp(curtndx) .] 0]
        }
$mfp(top).menu.opt.m add checkbutton -label "Auto-incorporate" \
    -variable mf(autoincorp)
$mfp(top).menu.opt.m add checkbutton -label "Save Last Compose" \
    -variable mf(savesend)
$mfp(top).menu.opt.m add checkbutton -label "Parse From:" \
    -variable mf(getfrom) -command {
	global mfp
	mf_selmesg $mfp(curtndx) 0
	}
$mfp(top).menu.opt.m add separator
$mfp(top).menu.opt.m add command -label "All Settings . . ." \
    -command { mf_dispopt }


menu $mfp(top).menu.help.m
$mfp(top).menu.help.m add command -label {Intro} -command "mf_disphelp TOP" \
    -accelerator {[h]}

foreach line [split $mfp(readme) "\n"] {
    if {[string range $line 0 0] > " "} {
	set topic [lindex [split $line ":"] 0]
	$mfp(top).menu.help.m add command -label [string tolower $topic] \
		-command "mf_disphelp [string range $topic 0 3]"
    }
}


pack append $mfp(top).menu $mfp(top).menu.folder {left} \
    $mfp(top).menu.edit {left} $mfp(top).menu.mesg {left} \
    $mfp(top).menu.mail {left} $mfp(top).menu.opt {left} \
    $mfp(top).menu.help {right}

label $mfp(top).hstat -relief raised

frame $mfp(top).head
scrollbar $mfp(top).head.yscroll -command "$mfp(top).head.list yview" \
	  -relief raised
text $mfp(top).head.list -yscroll "$mfp(top).head.yscroll set" -wrap none \
    -cursor left_ptr -relief sunken
$mfp(top).head.list tag configure selmesg -background lightskyblue -relief raised


bind $mfp(top).head.list <Any-KeyPress> " "
bind $mfp(top).head.list <Button-1> \
    "mf_selmesg \[$mfp(top).head.list index {@%x,%y linestart}\] 0"
bind $mfp(top).head.list <ButtonRelease-1> " "
bind $mfp(top).head.list <B1-Motion> \
    "mf_selmesg \[$mfp(top).head.list index {@%x,%y linestart}\] 2"
bind $mfp(top).head.list <Button-2> \
    "mf_deselmesg \[$mfp(top).head.list index {@%x,%y linestart}\]"
bind $mfp(top).head.list <B2-Motion> \
    "mf_deselmesg \[$mfp(top).head.list index {@%x,%y linestart}\]"
bind $mfp(top).head.list <Button-3> \
    "mf_selmesg \[$mfp(top).head.list index {@%x,%y linestart}\] 3"
bind $mfp(top).head.list <B3-Motion> \
    "mf_selmesg \[$mfp(top).head.list index {@%x,%y linestart}\] 3"
bind $mfp(top).head.list <Shift-1> " "
bind $mfp(top).head.list <Shift-B1-Motion> " "

pack append $mfp(top).head $mfp(top).head.yscroll {left filly} \
    $mfp(top).head.list {expand fill}

frame $mfp(top).bb
button $mfp(top).bb.incorp -text "Incorp" -command "$mfp(top).menu.folder.m invoke 4"
button $mfp(top).bb.save -text "Save" -command "$mfp(top).menu.mesg.m invoke 7"
button $mfp(top).bb.move -text "Move" -command "fileselect mf_movemesg {Folder:} 0 {}"
button $mfp(top).bb.del -text "Delete" -command "$mfp(top).menu.mesg.m invoke 2"
button $mfp(top).bb.comp -text "Compose" -command "$mfp(top).menu.mail.m invoke 0"
button $mfp(top).bb.reply -text "Reply" -command "$mfp(top).menu.mail.m invoke 1"
button $mfp(top).bb.forw -text "Forward" -command "$mfp(top).menu.mail.m invoke 2"
button $mfp(top).bb.quit -text "Quit" -command "$mfp(top).menu.folder.m invoke 1"

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

bind $mfp(top).bb.comp <ButtonRelease-3> "tk_butUp3 %W; mf_reply 2"
bind $mfp(top).bb.reply <ButtonRelease-3> "tk_butUp3 %W; mf_reply 1"

pack append $mfp(top).bb $mfp(top).bb.incorp {left expand fill} \
    $mfp(top).bb.save {left expand fill} \
    $mfp(top).bb.move {left expand fill} \
    $mfp(top).bb.del {left expand fill} \
    $mfp(top).bb.comp {left expand fill} \
    $mfp(top).bb.reply {left expand fill} \
    $mfp(top).bb.forw {left expand fill} \
    $mfp(top).bb.quit {left expand fill}

label $mfp(top).mstat -relief raised

frame $mfp(top).mesg
scrollbar $mfp(top).mesg.yscroll -command "$mfp(top).mesg.txt yview" \
	  -relief raised
text $mfp(top).mesg.txt -yscroll "mf_mesgscrollset $mfp(top).mesg.yscroll" \
    -relief sunken
proc mf_mesgscrollset { sw args } {
    global mfp
    eval "$sw set $args"
    set mfp(mesgview) $args
}
bind $mfp(top).mesg.txt <Next> {
  global mfp
  %W mark set insert [lindex $mfp(mesgview) 3].0
  %W yview insert
}
bind $mfp(top).mesg.txt <Prior> {
  global mfp
  %W mark set insert [expr [lindex $mfp(mesgview) 2]-[lindex $mfp(mesgview) 1]].0
  %W yview insert
}

bind $mfp(top).mesg.txt <Meta-KeyPress-o> "$mfp(top).menu.folder.m invoke 0"
bind $mfp(top).mesg.txt <Meta-KeyPress-q> "$mfp(top).menu.folder.m invoke 1"
bind $mfp(top).mesg.txt <Meta-KeyPress-b> "$mfp(top).menu.folder.m invoke 3"
bind $mfp(top).mesg.txt <Meta-KeyPress-i> "$mfp(top).menu.folder.m invoke 4"
bind $mfp(top).mesg.txt <Meta-KeyPress-n> "$mfp(top).menu.mesg.m invoke 0"
bind $mfp(top).mesg.txt <Meta-KeyPress-p> "$mfp(top).menu.mesg.m invoke 1"
bind $mfp(top).mesg.txt <Meta-KeyPress-d> "$mfp(top).menu.mesg.m invoke 2"
bind $mfp(top).mesg.txt <Meta-KeyPress-u> "$mfp(top).menu.mesg.m invoke 3"
bind $mfp(top).mesg.txt <Meta-KeyPress-y> "fileselect mf_copymesg {Folder:} 0 {}"
bind $mfp(top).mesg.txt <Meta-KeyPress-m> "fileselect mf_movemesg {Folder:} 0 {}"
bind $mfp(top).mesg.txt <Meta-KeyPress-s> "$mfp(top).menu.mesg.m invoke 7"
bind $mfp(top).mesg.txt <Meta-KeyPress-r> "$mfp(top).menu.mesg.m invoke 8"
bind $mfp(top).mesg.txt <Shift-Meta-KeyPress-C> "$mfp(top).menu.mail.m invoke 0"
bind $mfp(top).mesg.txt <Shift-Meta-KeyPress-R> "$mfp(top).menu.mail.m invoke 1"
bind $mfp(top).mesg.txt <Shift-Meta-KeyPress-F> "$mfp(top).menu.mail.m invoke 2"
bind $mfp(top).mesg.txt <Meta-KeyPress-h> "$mfp(top).menu.help.m invoke 0"

pack append $mfp(top).mesg $mfp(top).mesg.yscroll {left filly} \
    $mfp(top).mesg.txt {expand fill}

pack append $mfp(top) $mfp(top).menu {top fillx} \
    $mfp(top).hstat {top fillx} \
    $mfp(top).head {top fillx} \
    $mfp(top).bb {top fillx} \
    $mfp(top).mstat {top fillx} \
    $mfp(top).mesg {top expand fill}

# Some better bindings for text and entry
bind Text <Up> {
  %W mark set insert {insert - 1 line}
  %W yview -pickplace insert
}
bind Text <Down> {
  %W mark set insert {insert + 1 line}
  %W yview -pickplace insert
}
bind Text <Left> {
  %W mark set insert {insert - 1 char}
  %W yview -pickplace insert
}
bind Text <Right> {
  %W mark set insert {insert + 1 char}
  %W yview -pickplace insert
}
bind Text <Home> {%W mark set insert {insert linestart};
     %W yview -pickplace insert}
bind Text <End> {%W mark set insert {insert lineend};
     %W yview -pickplace insert}
bind Text <Control-Home> {%W mark set insert 1.0;
     %W yview -pickplace insert}
bind Text <Control-End> {%W mark set insert end;
     %W yview -pickplace insert}

bind Text <Any-KeyPress> {
     if {"%A" != ""} {
         catch {%W delete sel.first sel.last}
	 %W insert insert %A
        %W yview -pickplace insert
    }
}

bind Text <KeyPress-Delete> {
   if [catch {%W delete sel.first sel.last}] {
       %W delete [%W index insert-1c]
   }
}
bind Text <KeyPress-BackSpace> {
   if [catch {%W delete sel.first sel.last}] {
       %W delete [%W index insert-1c]
   }
}

bind Text <2> {%W insert insert [selection_if_any]}
bind Text <3> {selection clear %W}

bind Text <Shift-Control-KeyPress-S> \
	"mf_searchtxt %W 1"
bind Text <Control-KeyPress-s> \
	"mf_searchtxt %W 0"
bind Text <Meta-KeyPress-x> "mf_cutedit %W"
bind Text <Meta-KeyPress-c> "mf_copyedit %W"
bind Text <Meta-KeyPress-v> "mf_pasteedit %W"

bind Text <B1-Motion> {textB1Motion %W @%x,%y}

set mfp(txnd) 0
set mfp(xdelay) 100
proc textB1Motion  { w loc } {
    global mfp

    set ypos [lindex [split $loc ","] 1]
    if {$ypos > [winfo height $w]} {
            if {!$mfp(txnd)} {after $mfp(xdelay) textExtend $w}
            set mfp(txnd) 1
            set mfp(direction) down
    } elseif {$ypos < 0} {
            if {!$mfp(txnd)} {after $mfp(xdelay) textExtend $w}
            set mfp(txnd) 1
            set mfp(direction) up
    } else {
            set mfp(txnd) 0
            set mfp(direction) 0
    }

    if {!$mfp(txnd)} {
            tk_textSelectTo $w $loc
    }

}

bind Text <ButtonRelease-1> { global mfp; set mfp(txnd) 0 }

proc textExtend { w } {
     global mfp

     if {$mfp(txnd)} {
         if {$mfp(direction) == "down"} {
             tk_textSelectTo $w sel.last+1l
             $w yview -pickplace sel.last+1l
         } elseif {$mfp(direction) == "up"} {
             tk_textSelectTo $w sel.first-1l
             $w yview -pickplace sel.first-1l
         } else { return }
         after $mfp(xdelay) textExtend $w
     }
}


bind Entry <Right> {%W icursor [expr [%W index insert]+1]}
bind Entry <Left> {%W icursor [expr [%W index insert]-1]}
bind Entry <Home> {%W icursor 0}
bind Entry <End> {%W icursor end}
bind Entry <KeyPress-Delete> {
   if [catch {%W delete sel.first sel.last}] \
      {%W delete [expr [%W index insert]-1]}
}
bind Entry <KeyPress-BackSpace> {
   if [catch {%W delete sel.first sel.last}] \
      {%W delete [expr [%W index insert]-1]}
}
bind Entry <Any-KeyPress> {
    if {"%A" != ""} {
        catch {%W delete sel.first sel.last}
        %W insert insert %A
        tk_entrySeeCaret %W
    }
}
bind Entry <Double-Button-1> {%W select from 0; %W select to end}
bind Entry <2> {%W insert insert [selection_if_any]}
bind Entry <3> {selection clear %W}

###################################################################

# read in defaults settings
mf_defaultstart
mf_defaultset

# read in global settings file
if {[file exists $mfp(globalset)]} {
    source $mfp(globalset)
}

# read user defaults file before running any real code
mf_parsemailrc

if {[file exists $env(HOME)/.tkmail]} {
    source $env(HOME)/.tkmail
    set mfp(setfile) $env(HOME)/.tkmail
}
if {[file exists $env(HOME)/tk/tkmail]} {
    source $env(HOME)/tk/tkmail
    set mfp(setfile) $env(HOME)/tk/tkmail
}

# checks
if {![llength $mf(prefix)]} {
    puts stdout "Zero length mail prefix unacceptable!"
    return 1
}

# configure widgets to user settings
$mfp(top).head.list configure -height $mf(headheight) \
    -font $mf(tfont) -bg $mf(tcolor)
$mfp(top).mesg.txt configure -font $mf(tfont) -bg $mf(tcolor) \
    -fg $mf(fcolor)

if {$mf(emacs)} emacsbind

source $mf(utils)

if {[llength $mf(quicksend)] != 0 } {
    $mfp(top).menu.mail.m add separator
    foreach addr $mf(quicksend) {
        $mfp(top).menu.mail.m add command -label $addr \
            -command "mf_compose $addr {} $mfp(curnum)"
    }
}

if {[file isdirectory $mf(fdir)]} {
    set ftmp [lrange [lsort [glob $mf(fdir)/*]] 0 $mf(maxfold)]
    set mfp(foldfiles) ""
    foreach tfile $ftmp {
	if {[file isfile $tfile]} {lappend mfp(foldfiles) $tfile}
    }
    foreach mfold $mfp(foldfiles) {
	$mfp(top).menu.folder.m add command -label [file tail $mfold] \
	    -command "mf_setupfolder $mfold 1"
    }
    foreach mfold $mfp(foldfiles) {
	$mfp(top).menu.mesg.m.copy add command -label [file tail $mfold] \
	    -command "mf_copymesg $mfold"
    }
    foreach mfold $mfp(foldfiles) {
	$mfp(top).menu.mesg.m.move add command -label [file tail $mfold] \
	    -command "mf_movemesg $mfold"
    }
}

$mfp(top).menu.mesg.m.move add separator
$mfp(top).menu.mesg.m.move add command -label {Other . . .} \
  -command "fileselect mf_movemesg {Folder:} 0 {}"
$mfp(top).menu.mesg.m.copy add separator
$mfp(top).menu.mesg.m.copy add command -label {Other . . .} \
  -command "fileselect mf_copymesg {Folder:} 0 {}"

wm iconbitmap $mfp(top) "@$mf(fdownbmp)"
wm geometry $mfp(top) $mf(geom)

if {$mfp(file)==""} { set mfp(file) $mf(mbox) }
mf_setupfolder $mfp(file) 1
mf_schedule

cd $mf(fdir)
focus $mfp(top).mesg.txt
