#!/usr/local/bin/tkgwish8.0
# \
exec wish8.0 "$0" ${1+"$@"}

# Dialer; for establishing connection and authorization for 
# ppp or term link.

# Version 8.0
# (numbered so as to coincide with the required version of tk)

# Assumes your modem takes AT... commands.
# Works for linux and several other platforms.  Your mileage may
# suck.

# M. Crimmins 1/27/97

if {([set tk_version] < 4.1)} {
   bgerror "This program requires tk version 4.1 or higher."
   exit
}

set file ""
if {$argv != ""} {
    set file [lindex $argv 0]
    set argv [lreplace $argv 0 0]
}
set debug 0
set send_slow {1 .1}
set quickwait 5
set labelwidth 20
set entrywidth 50

set stringvars { numbers repeatscript port speed init1 init2\
		     loginscript outcommand }
set booleans {login exitsucc repeat}

# Give the first-time user an example
set numbers "555-1000 555-2000"
set repeatscript { * {{ 555-1000 1} {555-2000 1} {555-1000 3}}}
set repeat 0
set port /dev/modem
set speed 38400
set init1 "ATZ"
set init2 ""
set login 1
set loginscript {
    {"" "host: " 10} 
    {ppp "login: " 20}
    { myloginname@umich.edu "Password: " 5}
    {mypassword "" "" }
}
set outcommand "/usr/sbin/pppd $port $speed asyncmap 0 defaultroute crtscts modem noipdefault"
set exitsucc 0

option add *Listbox*font {Helvetica 14} startupFile
option add *Entry*font   {Helvetica 14} startupFile
option add *Label*font   {Helvetica 14} startupFile
option add *Text*font    {Helvetica 14} startupFile

proc DialWindow {} {
    global normalbg
    wm title . "Dialer"
    wm withdraw .
    wm iconname . "Dialer"

    bind . <Key-Escape> exit
    frame .status
    pack .status -side left -pady 20 -padx 10
    label .status.statuslabel -text "Dialing Status:" 
    pack .status.statuslabel -side top -pady 5
    text .status.text -height 12 -width 40 -takefocus 0
    set normalbg [.status.text cget -background]
    global statuswindow
    set statuswindow .status.text
    pack .status.text -side top -fill x -fill y -padx 15
    
    frame .buttons
    button .buttons.dial -text Dial -command Dial
    button .buttons.settings -text Settings -command EditSettings
    button .buttons.next -text "Try Next\nNumber"  -command {Next} -state disabled
    button .buttons.abort -text Abort -command {Abort} -state disabled
    button .buttons.dismiss -text Dismiss -command {exit}
    pack .buttons.dial .buttons.settings .buttons.next .buttons.abort .buttons.dismiss \
	-side top -expand y -fill x -padx 15 -pady 5
    pack .buttons -side left -fill x -expand y

    .status.statuslabel configure -font [.buttons.dial cget -font]
    
    focus .buttons.dial
    update
    set x [expr ([winfo screenwidth .] - [winfo reqwidth .] )/2]
    set y [expr ([winfo screenheight .] - [winfo reqheight .] )/2]
    wm geometry . +$x+$y
    wm deiconify .
}

proc Next {} {
    global numaborted
    set numaborted 1
}

proc Abort {} {
    global aborted
    set aborted 1
}

proc DoAbort {} {
    global normalbg
    Hangup
    Outstatus "Aborted"
    ResetButtons
    .status.text config -background $normalbg
}

proc Hangup {} {
    global id port
    Outmodem ""
    after 1250
    Outmodem + 0
    after 100
    Outmodem + 0
    after 100
    Outmodem + 0
    if [Dialexpect {} OK {} 3 ] {
	Dialexpect ATH OK {} 3
	after 500
    }
    exec stty sane < $port > $port
    exec stty 38400 raw -echo clocal < $port > $port
    return 1
}

proc CloseLine {} {
    global id
    if [info exists id] {
	close -i $id
	catch "unset id"
    }
}

proc CheckBoth {} {
    global aborted numaborted
    expr $aborted || $numaborted
}

proc Dial {{repeating 0}} {
    global statuswindow id nums loops
    global tkgsentok aborted numaborted
    global stringvars booleans
    foreach v [eval list $stringvars $booleans] {
	global $v
    }
    if !$repeating {
	.buttons.dial config -state disabled
	.buttons.dismiss config -state disabled
	.buttons.abort config -state normal
	.buttons.next config -state normal
	.status.text config -background yellow
	focus .buttons.next
	set aborted 0
	if !$repeat {
	    set nums $numbers
	    set loops 1
	} else {
	    set nums ""
	    foreach pair [lindex $repeatscript 1] {
		for {set i 0} {$i < [lindex $pair 1]} {incr i} {
		    lappend nums [lindex $pair 0]
		}
	    }
	    set loops [lindex $repeatscript 0]
	}
	if ![OpenModem] {return 0}
    } 
    for {set i 0} {$i < [llength $nums]} {incr i} {
	set number [lindex $nums $i]
	if {($i == ([llength $nums] - 1)) && \
		 ($loops != "*") && ($loops < 2 )} {
	    .buttons.next config -state disabled
	}
	set numaborted 0
	if ![ModemInit] {
	    Hangup
	    ResetButtons
	    set loops 1
	    break
	}
	if { ![DialNumber $number] || ![LoginScript] } { 
	    if $aborted {DoAbort; return}
	    continue
	}
	OutCommand
	ExitSucc
	ResetButtons
	focus .buttons.dismiss
	return 1
    }
    catch "incr loops -1"
    if {($loops == "*") || ($loops > 0)} {
	Dial 1
	return
    }
    .status.text config -background red
    Outstatus "Sorry, failed to connect."
    ResetButtons
    return 0
}

proc OpenModem {} {
    global port id 
    if [catch {set id [open $port w+]}] {
	Dialerror "Error opening modem port."
	.status.text config -background red
	Outstatus "Aborted"
	ResetButtons
	return 0
    }
    fconfigure $id -blocking 0 -buffering none
    exec stty 38400 raw -echo clocal < $port > $port
    return 1
}
	
proc ModemInit {} {
    global init1 init2
    #initialize modem
    if { $init1 != "" } {
	if ![Dialexpect "$init1" "OK\n" "Error initializing modem."] {
	    set loops 1 
	    return 0
	}
	if { $init2 != "" } {
	    if ![Dialexpect "$init2" "OK\n" "Error initializing modem."] {
		set loops 1 
		return 0
	    }
	}
    }
    return 1
}

proc DialNumber {number} {
    global Dialer_expect_out
    Outstatus "Dialing $number . . ."
    set fail 0
    if [Dialexpect ATDT$number \
	    { "OK" "BUSY" "NO DIAL TONE" "NO CARRIER" "NO ANSWER" "CONNECT .*\[\r\n\]" } \
	    {} 45 ] {
	switch -regexp -- $Dialer_expect_out {
	    "OK"  { Outstatus "  Unknown failure (\"OK\"!)."; set fail 1}
	    "BUSY" { Outstatus "  Line is busy."; set fail 1}
	    "NO CARRIER" { 
		Outstatus "  No carrier (no answer or phone line not connected)."
		set fail 1
	    } "NO DIAL TONE" { 
		Outstatus "  No dial tone (no busy signal or ringing detected)."
		set fail 1
	    } "NO ANSWER" { Outstatus "  No answer." ; set fail 1} 
	    "CONNECT .*\[\r\n\]" {Outstatus $Dialer_expect_out}
	}
	if $fail {
	    after 1000
	    return 0
	}
    } else {
	Hangup
	return 0
    }
    return 1
}

proc LoginScript {} {
    global loginscript login
    .status.text config -background yellowgreen
    if !$login {return 1}
    Outstatus "Running login script. . ."
    foreach entry $loginscript {
	if ![Dialexpect [lindex $entry 0] \
		 [lindex $entry 1] \
		 "In login script, never got \"[lindex $entry 1]\"" \
		 [lindex $entry 2] ] {
		     Hangup
		     return 0
		 }
    }
    Outstatus "Login script succeeded."
    .status.text config -background chartreuse1
    return 1
}

proc OutCommand {} {
    global outcommand
    if {$outcommand != ""} {
	Outstatus "Setting up the network:"
	Outstatus "  ([string range $outcommand 0 30] . . .)"
	eval exec $outcommand &
    }
}

proc ExitSucc {} {
    global exitsucc
    if !$exitsucc return
    if {[lsearch [winfo interps] tkgoodstuff] != -1} {
	if {[send tkgoodstuff {lsearch $TKG(clients) Net}] != -1} {
	    send tkgoodstuff Net_start_wait
	    send tkgoodstuff {DEBUG {Dialer suggests we look for net-up}}
	}
    }
    bell; after 250 { bell; after 5000 exit }
}

proc ResetButtons {} {
    .buttons.dial config -state normal
    .buttons.dismiss config -state normal
    .buttons.next config -state normal
    .buttons.abort config -state disabled
    .buttons.next config -state disabled
    focus .buttons.dial
}

proc Dialexpect "outstring instrings {error none} \"wait $quickwait\"" {
    global statuswindow global quickwait id aborted numaborted Dialer_expect_out
    set Dialer_expect_out ""
    if ![info exists id] { bgerror "no channel with id $id"}
    if [CheckBoth] {return 0}
    if { [regexp " *(&PAUSE|&pause) *(.*)$" $outstring l v pause] && ($pause != -1)} {
	if { [scan $pause "%f" pause1] && [info exists pause1]} {
	    after [format %.0f [expr $pause1 * 1000]]
	}
    } elseif [regexp " *(&RETURN|&return) *$" $outstring l l] {
	DEBUG "Sending return character. . ."
	Outmodem ""
    } elseif {$outstring != ""} {
	DEBUG "Sending \"$outstring\". . ."
	Outmodem "$outstring"
    }
    if {$instrings == ""} {return 1}
    DEBUG "Waiting $wait seconds for \"$instrings\""
    set timeout 1000
    for {set i 0} {$i < $wait} {incr i} {
	if [CheckBoth] {return 0}
	if ![info exists id] { bgerror "no \$id"}
	if [DialerExpect $id $instrings $timeout] {
	    return 1
	}
    }
    DEBUG "Never got $instrings."
    if [CheckBoth] {return 0}
    if {$error != "none"} {Dialerror $error}
    return 0
}

proc DialerExpect {id strings timeout} {
    after $timeout DialerCancelExpect
    fileevent $id readable [list DialerRead $strings $id]
    global Dialer_cancelexpect
    vwait Dialer_cancelexpect
    fileevent $id readable {}
    global Dialer_expect_out Dialer_expect_in
    set Dialer_expect_in ""
    set result [expr ![string match "" $Dialer_expect_out]]
    return $result
}

proc DialerRead {strings id} {
    global Dialer_expect_in Dialer_expect_out
    append Dialer_expect_in [read $id]
    foreach string $strings {
	if [regexp $string $Dialer_expect_in Dialer_expect_out] {
	    DialerCancelExpect
	}
    }    
}

proc DialerCancelExpect {} {
     global Dialer_cancelexpect
     set Dialer_cancelexpect 0
}
     

proc Outmodem {string {return 1}} {
    global id 
    switch $return {
	1 {eval puts -nonewline $id \"$string\\r\"}
	0 {eval puts -nonewline $id \"$string\"}
    }
}

proc Outstatus {string} {
    global statuswindow
    $statuswindow insert end "$string\n"
    $statuswindow see end
    update
}

proc DEBUG {string} {
    global debug
    switch $debug {
	0 return
	1 "Outstatus $string"
	2 "puts $string"
    }
}

proc Dialerror {error {die 0}} {
    global statuswindow id
    Outstatus "$error"
    if $die { 
	if [info exists id] { close -i $id }
	bgerror "$error" 
	exit
    }
}

proc EditSettings {} {
    global labelwidth file
    set w .editsettings
    catch {destroy $w}
    toplevel $w
    bind $w <Key-Escape> "destroy $w"
    wm title $w "Dialer Settings"
    wm iconname $w "Dialer Settings"

    trace variable file w UpdateFileDisplay 

    frame $w.menu -relief raised -bd 2
    pack $w.menu -side top -fill x
    
    set m $w.menu.file.m
    menubutton $w.menu.file -text "File" -menu $m -underline 0
    menu $m
    $m add command -label "Open settings file" -command Open
    $m add command -label "New settings file" -command New
    $m add command -label "Save settings" -command Save
    $m add command -label "Save settings As ..." -command SaveAs
    
    pack $w.menu.file -side left

    set m $w.menu.help.m
    menubutton $w.menu.help -text "Help" -menu $m -underline 0
    menu $m
    $m add command -label "About Dialer" -command About
    $m add separator
    $m add command -label "Dialer Help" -command HelpSettings

    pack $w.menu.help -side right

    button .b
    set bfont [.b cget -font]
    destroy .b
    frame $w.curfile
    label $w.curfile.label -font $bfont
    UpdateFileDisplay
    pack $w.curfile.label -side left
    label $w.curfile.file -textvariable file -font $bfont
    pack $w.curfile.file -side left
    pack $w.curfile -expand y -pady 10

    global labelwidth entrywidth
    frame $w.numbers
    radiobutton $w.numbers.check -variable repeat -value 0
    pack $w.numbers.check -side left -padx 10
    label $w.numbers.numberslabel -text "Dial these numbers:" -width $labelwidth -anchor c
    pack $w.numbers.numberslabel -side left
    entry $w.numbers.numbersentry -textvariable numbers -width $entrywidth
    pack $w.numbers.numbersentry -side left -fill x -expand y

    frame $w.repeat
    radiobutton $w.repeat.check -variable repeat -value 1
    pack $w.repeat.check -side left -padx 10
    label $w.repeat.label1 -text "Dial from repeat script"
    pack $w.repeat.label1 -side left

    AddEntry $w port Port:
    AddEntry $w speed Speed:
    AddEntry $w init1 "Modem\ninit string"
    AddEntry $w init2 "Second modem\ninit string"
    AddEntry $w outcommand "Command to execute\nwhen logged in:"

    frame $w.login
    checkbutton $w.login.check -variable login
    pack $w.login.check -side left -padx 10
    label $w.login.label1 -text "Run login script when connected"
    pack $w.login.label1 -side left
    
    frame $w.exitsucc
    checkbutton $w.exitsucc.check -variable exitsucc
    pack $w.exitsucc.check -side left -padx 10
    label $w.exitsucc.label1 -text "Exit on successful connection"
    pack $w.exitsucc.label1 -side left
    
    pack $w.numbers $w.repeat $w.port $w.speed $w.init1 $w.init2 $w.outcommand $w.login $w.exitsucc\
	-fill x -pady 5

    frame $w.buttons
    button $w.buttons.ers -command EditRepeatScript -text "Edit\nRepeat Script"
    pack $w.buttons.ers -side left -expand y -fill y -padx 15 -pady 15
    button $w.buttons.els -command EditLoginScript -text "Edit\nLogin Script"
    pack $w.buttons.els -side left -expand y -fill y -padx 15 -pady 15
    button $w.buttons.dismiss -text Dismiss -command "destroy $w"
    pack $w.buttons.dismiss -side left -expand y -fill y -padx 15 -pady 15
    pack $w.buttons -fill x -expand y
}

proc UpdateFileDisplay args {
    global file
    if {$file == ""} {
	.editsettings.curfile.label config -text "(No current file)"
    } else {
	.editsettings.curfile.label config -text "File:  "
    }
}

proc AddEntry { w v s } {
    global labelwidth entrywidth
    frame $w.$v
    label $w.$v.[set v]label -text $s -width $labelwidth -anchor c
    pack $w.$v.[set v]label -side left
    entry $w.$v.[set v]entry -textvariable $v -width $entrywidth
    pack $w.$v.[set v]entry -side left -fill x -expand y
}

proc EditRepeatScript {} {
    global labelwidth repeatscript rscript file repeat
    set entrywidth 25

    set w .editrepeatscript 
    catch {destroy $w}
    toplevel $w
    bind $w <Key-Escape> "UpdateRepeatScript; destroy $w"
    wm title $w "Dialer Repeat Script"
    wm iconname $w "Dialer Repeat Script"

    frame $w.title -relief raised
    pack $w.title -fill x -expand y
    label $w.title.0 -text "Numer:"
    pack $w.title.0 -pady 10 -fill x -expand y -side left
    label $w.title.1 -text "Repeat ___ times:"
    pack $w.title.1 -pady 10 -fill x -expand y -side left
    frame $w.entries
    pack $w.entries
    frame $w.entries.0
    frame $w.entries.1
    pack $w.entries.0 $w.entries.1 -side left -fill x
    foreach s {0 1} {
	for {set i 0} {$i<16} {incr i} {
	    set rscript($i,$s) [lindex [lindex [lindex $repeatscript 1] $i] $s]
	    entry $w.entries.$s.entry$i -width $entrywidth \
		-textvariable rscript($i,$s)
	    pack $w.entries.$s.entry$i -padx 5 -pady 3 -fill x
	}
    }
    frame $w.grep
    label $w.grep.greplabel -text "Repeat entire script ___ times:\n(\"*\" means forever)" -anchor c
    pack $w.grep.greplabel -side left
    entry $w.grep.grepentry -textvariable loops -width 4
    pack $w.grep.grepentry -side left
    pack $w.grep -fill x -pady 10
    frame $w.buttons
    button $w.buttons.dismiss -text Dismiss -command "UpdateRepeatScript; destroy $w"
    pack $w.buttons.dismiss -side left -expand y -fill y -padx 15 -pady 15
    pack $w.buttons -side bottom -fill x -expand y
}

proc UpdateRepeatScript {} {
    global rscript repeatscript loops
    set repeatscript ""
    set i 0
    set s ""
    while {($rscript($i,0) != "") || \
	       ($rscript($i,1) != "")} {
	lappend s [list $rscript($i,0) $rscript($i,1) ]
	incr i
    }
    set repeatscript [list $loops $s]
}

proc EditLoginScript {} {
    global labelwidth loginscript script file
    set entrywidth 25

    set w .editloginscript 
    catch {destroy $w}
    toplevel $w
    bind $w <Key-Escape> "UpdateScript; destroy $w"
    wm title $w "Dialer Login Script"
    wm iconname $w "Dialer Login Script"

    frame $w.title -relief raised
    pack $w.title -fill x -expand y
    label $w.title.0 -text "Send:"
    pack $w.title.0 -pady 10 -fill x -expand y -side left
    label $w.title.1 -text "Then Expect:"
    pack $w.title.1 -pady 10 -fill x -expand y -side left
    label $w.title.2 -text "After ___ seconds:"
    pack $w.title.2 -pady 10 -fill x -expand y -side left
    frame $w.entries
    pack $w.entries
    frame $w.entries.0
    frame $w.entries.1
    frame $w.entries.2
    pack $w.entries.0 $w.entries.1 $w.entries.2 -side left -fill x
    foreach s {0 1 2} {
	for {set i 0} {$i<16} {incr i} {
	    set script($i,$s) [lindex [lindex $loginscript $i] $s]
	    entry $w.entries.$s.entry$i -width $entrywidth \
		-textvariable script($i,$s)
	    pack $w.entries.$s.entry$i -padx 5 -pady 3 -fill x
	}
    }
    frame $w.buttons
    button $w.buttons.dismiss -text Dismiss -command "UpdateScript; destroy $w"
    pack $w.buttons.dismiss -side left -expand y -fill y -padx 15 -pady 15
    pack $w.buttons -side bottom -fill x -expand y
}

proc UpdateScript {} {
    global script loginscript
    set loginscript ""
    set i 0
    while {($script($i,0) != "") || \
	       ($script($i,1) != "") || \
	       ($script($i,2) != "")} {
	lappend loginscript [list $script($i,0) $script($i,1) $script($i,2) ]
	incr i
    }
}

proc HelpSettings {} {

    set text "
This Dialer assumes you have a basically Hayes-compatible modem (like\
nearly all modems sold these days).

All settings, including a login script, are saved in a settings file.\
In the Settings window, you can open and save settings files.  You can\
also select a settings file from the command line as follows:
\ \ \ \"Dialer mysettingsfile\"
If no settings file is given on the command line, Dialer looks for th\
file \".DialSettings\" in your home directory.\
To make the Dialer start dialing on invocation, you add \"Dial\" to the\
command line:
\ \ \ \"Dialer mysettingsfile Dial\"

(The Dialer also can be started automatically by the \"tkgoodstuff\"\
button bar's \"Net\" client.)

There are two dialing strategies to choose between:
  1. Each phone number in the list of phone numbers will be tried in\
succession.  You can use the usual Hayes characters in the dial string\
(consult your modem manual).  NOTE to those who want to type\
\"ATDP555-7777\" here: you should not include the \"ATD\" command, but\
only the phone number.
  2. A repeat script will be followed (which you edit by pressing a\
button in the Settings window).  Each number will be tried a specified\
number of times, and the whole list will be tried a specified number of\
time (or forever).

The \"port\" is a unix device file governing the serial port to which\
your modem is connected, such as /dev/modem or /dev/cua1.  (Linux\
users: remember that /dev/cua0 is COM1, . . ., and /dev/cua3 is\
COM4.)

The speed is the baud rate OF THE PORT, which typically is best set\
higher than the modem's baud rate.  You don't need to set the modem\
baud rate explicitly (though you can do so in one of the init\
strings if necessary).

The modem init strings are \"AT...\" commands to send to the modem (we\
look for an \"OK\" from the modem afterwards).  These are optional, and\
can be anything from \"AT\" to \"ATZ\" to the monstrously long ones modem\
freaks swear by (the author uses \"ATZ4\" with his Sportster v.34).

The entry \"command to execute when logged in\" allows you to start up\
your networking software once you have dialed in and authenticated to\
the server (through the login script, as described below).  Here you\
can start, for instance, ppp or term.  The author, for his dynamic ppp\
connection from home, uses:
\ \ \ \"/usr/sbin/pppd /dev/modem 38400 asyncmap 0 defaultroute crtscts\
modem noipdefault\"

There is a checkbox which enables using the login script (on which more\
below). 

And there is a checkbox which tells the Dialer window to disappear\
once all its tasks are completed sucessfully (including launching the\
networking command, if you have set one).  Note that the Dialer itself\
does not check whether the networking command is successful in setting\
up the networking (for this indication, and for an easy way to use\
the dialer when needed, the author modestly suggests using his\
\"tkgoodstuff\" button bar's \"Net\" button).

To edit the login script associated with the current settings file,\
press the \"Edit Login Script\" button (did you guess that?).  The login\
script is a sequence of \"steps\".  Each step involves (optionally)\
sending a string to the modem (which automatically will be followed by\
a carriage return), and then (optionally) waiting a certain number of\
seconds for a string from the modem.  If you leave the entries for the\
send (or expect) strings empty, then at that step no string will be\
sent (or expected).  A send string of \"&pause 2.5\" will not send a\
string but instead will pause for 2.5 seconds.  A send string of\
\"&return\" will send just a return character\
You can include special characters according to\
tcl's backslash substitution rules (see \"man Tcl\").
"

    set w .loginscripthelp
    catch {destroy $w}
    toplevel $w
    wm title $w "Dialer Help"
    wm iconname $w "Dialer Help"

    button .b
    label $w.title -text "DIALER HELP" \
        -font [.b cget -font]
    destroy .b
    pack $w.title -pady 10 -fill x -expand y

    frame $w.view
    text $w.view.text -width 80 -height 20 \
	-takefocus 0 -yscrollcommand "$w.view.scrollbar set" \
	-relief sunken -borderwidth 2 -state disabled \
        -wrap word

    pack $w.view.text -side left -fill both -expand 1
    scrollbar $w.view.scrollbar -command "$w.view.text yview"
    pack $w.view.scrollbar -side left -fill y -padx 3
    pack $w.view -side top -fill both -expand 1 -padx 10
    $w.view.text configure -state normal
    $w.view.text insert end "$text"
    $w.view.text configure -state disabled

    frame $w.buttons
    button $w.buttons.dismiss -text Dismiss -command "destroy $w"
    pack $w.buttons.dismiss -side left -expand y -fill y -padx 15 -pady 15
    pack $w.buttons -side bottom -fill x -expand y

}    

proc About {} {

    set text "
Dialer, by Mark Crimmins (markcrim@umich.edu), copyright 1995.
Look for the latest version in the tkgoodstuff distribution at:
\ \ \ ftp://merv.philosophy.lsa.umich.edu/pub
"
    set w .about
    catch {destroy $w}
    toplevel $w
    wm title $w "About Dialer"
    wm iconname $w "About Dialer"

    button .b
    label $w.title -text "About Dialer" \
        -font [.b cget -font]
    destroy .b
    pack $w.title -pady 10 -fill x -expand y

    frame $w.view -relief groove -borderwidth 5
    message $w.view.text -width 18c -text $text

    pack $w.view.text -side left -fill both -expand 1
    pack $w.view -side top -fill both -expand 1 -padx 10

    frame $w.buttons
    button $w.buttons.dismiss -text Dismiss -command "destroy $w"
    pack $w.buttons.dismiss -side left -expand y -fill y -padx 15 -pady 15
    pack $w.buttons -side bottom -fill x -expand y

}    

proc Save {{f ""}} {
    global file stringvars booleans 
    if {$f != ""} {set file $f}
    if {$file == ""} {SaveAs; return}
    set id [ open $file w ]
    puts $id "\#Dialer Settings File"
    foreach v [eval list $stringvars $booleans] {
	global $v
	puts $id "set $v \{[set $v]\}"
    }
    close $id
}

proc Open {} {
    global file
    if ![select f "Open file:"] {
	return
    }
    if ![file exists $f] {
	bgerror "Error:  $f doesn't exist."
	return
    }
    if ![file readable $f] {
	bgerror "Error:  $f isn't readable."
	return
    }
    set file $f
    GetSettings
}

proc New {} {
    global file 
    if ![select f "New file name:"] {
	return
    }
    if [file exists $f] {
	bgerror "Error: can't create $f: file exists"
	return
    }
    if [catch "exec touch $f"] {
	bgerror "Error: can't create $f."
	return
    }
    ClearSettings
    set file $f
    wm title . "Dialer"
}

proc SaveAs {} {
    global file
    if ![select f "File name:"] {
	return
    }
    if [file exists $f] {
	bgerror "Error: can't create $f: file exists"
	return
    }
    if [catch "exec touch $f"] {
	bgerror "Error: can't create $f."
	return
    }
    set file $f
    Save
}

proc ClearSettings {} {
    global stringvars booleans
    foreach v $stringvars {
	global $v
	set $v ""
    }
    foreach v $booleans {
	global $v
	set $v 0
    }
}

proc GetSettings {} {
    global file 
    global stringvars booleans
    foreach v [eval list $stringvars $booleans] {
	global $v
    }
    set id [open $file]
    gets $id s
    close $id
    if {$s != "\#Dialer Settings File"} {
	bgerror "File $file is not a dialer settings file."
	return
    }
    source $file
    global loops
    set loops [lindex $repeatscript 0]
}

proc select {var {message "Open File"}} {
    global selection
    set selection ""
    fileselect selectdone $message
    tkwait window .fileSelectWindow
    uplevel "set $var \"$selection\"" 
    if { $selection == "" } {
	return 0
    } else {
	return 1
    }
}

proc selectdone {f} {
    global selection 
    set selection $f
}

#
# fileselect.tcl --
# simple file selector.
#
# Mario Jorge Silva			          msilva@cs.Berkeley.EDU
# University of California Berkeley                 Ph:    +1(510)642-8248
# Computer Science Division, 571 Evans Hall         Fax:   +1(510)642-5775
# Berkeley CA 94720                                 
# 
# Layout:
#
#  file:                  +----+
#  ____________________   | OK |
#                         +----+
#
#  +------------------+    Cancel
#  | ..               |S
#  | file1            |c
#  | file2            |r
#  |                  |b
#  | filen            |a
#  |                  |r
#  +------------------+
#  currrent-directory
#
# Copyright 1993 Regents of the University of California
# Permission to use, copy, modify, and distribute this
# software and its documentation for any purpose and without
# fee is hereby granted, provided that this copyright
# notice appears in all copies.  The University of California
# makes no representations about the suitability of this
# software for any purpose.  It is provided "as is" without
# express or implied warranty.
#


# names starting with "fileselect" are reserved by this module
# no other names used.

# use the "option" command for further configuration



# this is the default proc  called when "OK" is pressed
# to indicate yours, give it as the first arg to "fileselect"

proc fileselect.default.cmd {f} {
  puts stderr "selected file $f"
}


# this is the default proc called when error is detected
# indicate your own pro as an argument to fileselect

proc fileselect.default.errorHandler {errorMessage} {
    puts stdout "error: $errorMessage"
    catch { cd ~ }
}

# this is the proc that creates the file selector box

proc fileselect {
    {cmd fileselect.default.cmd} 
    {purpose "Open file:"} 
    {w .fileSelectWindow} 
    {errorHandler fileselect.default.errorHandler}} {

    catch {destroy $w}

    toplevel $w
    grab $w
    wm title $w "Select File"


    # path independent names for the widgets
    global fileselect

    set fileselect(entry) $w.file.eframe.entry
    set fileselect(list) $w.file.sframe.list
    set fileselect(scroll) $w.file.sframe.scroll
    set fileselect(ok) $w.bframe.okframe.ok
    set fileselect(cancel) $w.bframe.cancel
    set fileselect(dirlabel) $w.file.dirlabel

    # widgets
    frame $w.file -bd 10 
    frame $w.bframe -bd 10
    pack append $w \
        $w.file {left filly} \
        $w.bframe {left expand frame n}

    frame $w.file.eframe
    frame $w.file.sframe
    label $w.file.dirlabel -anchor e -width 24 -text [pwd] 

    pack append $w.file \
        $w.file.eframe {top frame w} \
	$w.file.sframe {top fillx} \
	$w.file.dirlabel {top frame w}


    label $w.file.eframe.label -anchor w -width 24 -text $purpose
    entry $w.file.eframe.entry -relief sunken 

    pack append $w.file.eframe \
		$w.file.eframe.label {top expand frame w} \
                $w.file.eframe.entry {top fillx frame w} 


    scrollbar $w.file.sframe.yscroll -relief sunken \
	 -command "$w.file.sframe.list yview"
    listbox $w.file.sframe.list -relief sunken \
	-yscroll "$w.file.sframe.yscroll set" 

    pack append $w.file.sframe \
        $w.file.sframe.yscroll {right filly} \
 	$w.file.sframe.list {left expand fill} 

    # buttons
    frame $w.bframe.okframe -borderwidth 2 -relief sunken
 
    button $w.bframe.okframe.ok -text OK -relief raised -padx 10 \
        -command "fileselect.ok.cmd $w $cmd $errorHandler"

    button $w.bframe.cancel -text cancel -relief raised -padx 10 \
        -command "fileselect.cancel.cmd $w"
    pack append $w.bframe.okframe $w.bframe.okframe.ok {padx 10 pady 10}

    pack append $w.bframe $w.bframe.okframe {expand padx 20 pady 20}\
                          $w.bframe.cancel {top}

    # Fill the listbox with a list of the files in the directory (run
    # the "/bin/ls" command to get that information).
    # to not display the "." files, remove the -a option and fileselect
    # will still work
 
    $fileselect(list) insert end ".."
    foreach i [exec /bin/ls -a [pwd]] {
        if {[string compare $i "."] != 0 && \
	    [string compare $i ".."] != 0 } {
            $fileselect(list) insert end $i
        }
    }

   # Set up bindings for the browser.
    bind $fileselect(entry) <Return> {eval $fileselect(ok) invoke; break}
    bind $fileselect(entry) <Control-c> {eval $fileselect(cancel) invoke; break}

    bind $w <Control-c> {eval $fileselect(cancel) invoke;break}
    bind $w <Return> {eval $fileselect(ok) invoke;break}


#    tk_listboxSingleSelect $fileselect(list)


    bind $fileselect(list) <Button-1> {
        # puts stderr "button 1 release"
        %W selection set [%W nearest %y]
	$fileselect(entry) delete 0 end
	$fileselect(entry) insert 0 [%W get [%W nearest %y]]
	break
    }

    bind $fileselect(list) <Key> {
        %W selection set [%W nearest %y]
        $fileselect(entry) delete 0 end
	$fileselect(entry) insert 0 [%W get [%W nearest %y]]
	break
    }

    bind $fileselect(list) <Double-ButtonPress-1> {
        # puts stderr "double button 1"
        %W selection set [%W nearest %y]
	$fileselect(entry) delete 0 end
	$fileselect(entry) insert 0 [%W get [%W nearest %y]]
	$fileselect(ok) invoke
	break
    }

    bind $fileselect(list) <Return> {
        %W selection set [%W nearest %y]
	$fileselect(entry) delete 0 end
	$fileselect(entry) insert 0 [%W get [%W nearest %y]]
	$fileselect(ok) invoke
	break
    }

    # set kbd focus to entry widget

    focus $fileselect(entry)

}


# auxiliary button procedures

proc fileselect.cancel.cmd {w} {
    # puts stderr "Cancel"
    destroy $w
}

proc fileselect.ok.cmd {w cmd errorHandler} {
    global fileselect
    set selected [$fileselect(entry) get]

    # some nasty file names may cause "file isdirectory" to return an error
    set sts [catch { 
	file isdirectory $selected
    }  errorMessage ]

    if { $sts != 0 } then {
	$errorHandler $errorMessage
	destroy $w
	return

    }

    # clean the text entry and prepare the list
    $fileselect(entry) delete 0 end
    $fileselect(list) delete 0 end
    $fileselect(list) insert end ".."

    # selection may be a directory. Expand it.

    if {[file isdirectory $selected] != 0} {
	cd $selected
	set dir [pwd]
	$fileselect(dirlabel) configure -text $dir

	foreach i [exec /bin/ls -a $dir] {
	    if {[string compare $i "."] != 0 && \
		[string compare $i ".."] != 0} {
		$fileselect(list) insert end $i
	    }
	}
	return
    }

    destroy $w
    $cmd $selected
}
##### end of fileselect code

# Main Program
DialWindow
if {($file != "") || \
      ([file exists $env(HOME)/.DialSettings] && \
             ![catch {set file $env(HOME)/.DialSettings}]) } { 
   ClearSettings
   GetSettings 
}
eval $argv
