##########################################################
# FTP_AddresBook v1.00  Date: 17 Apr 98  (c) Juha Forsten
##########################################################
#
# TODO:
#
# - Load-button !?
# - Up/Down ei aiheuta 'edited' = 1
#
# DONE:
# 
# + Up/Down works allways
# + Note if whitespaces in 'alias' 
# + Apply now works right ie. added items are _avaliable_ even
#   not saved.
# + Confirm exit if changes
# + Added flash when save & apply
# + Added some keyboard shortcuts:
#     *Up/Down moves selection
#     *Shift+Up/Down moves item 
#     *Delete deletes item
# + Deletes are confirmed
#

# Replase 0.7 -> 1.0
package provide FAE 0.7
proc FAE {{port 21} {chunk 4096} {timeout 30} {passive 0} {short 0}} {
    global fa falist falists xf

    set xf(fae_on) 1
    if {[winfo exists .fae]} {
	wm deiconify .fae
	raise .fae
	return
    }

    set f [toplevel .fae]
    wm title $f "FTP Addressbook v1.0"
    wm resizable $f 0 0
    wm protocol $f WM_DELETE_WINDOW {set fae(edited) 0;FA_Close}

    set fa(editmode) "Advanced..."
    set fa(disabled)  0
    set fa(new)      0
    set fa(edited)   0
    set fa(datafile) $xf(ftpbook_file)
    set fa(toplevel) $f


    #Initialize vars
    set fa(port_default)     $port
    set fa(chunk_default)    $chunk
    set fa(timeout_default)  $timeout
    set fa(passive_defaults) $passive
    set fa(short_defaults)   $short
    
        
    # Main frames
    #############
    #############
    
    frame $f.0 -relief groove -bd 2
    frame $f.1 -relief sunken -bd 1
    frame $f.2 -relief groove -bd 2
    frame $f.3 ;#-relief groove -bd 2
    frame $f.4 -relief sunken -bd 1
    
    ####  Frame #0
    ##################
    # Buttons
    frame  $f.0.0
    button $f.0.0.add    -takefocus 0 -text Add    -command "FA_AddItem $f"
    button $f.0.0.delete -takefocus 0 -text Delete -command "FA_DeleteItem $f"
    button $f.0.0.up     -takefocus 0 -text Up     -command "FA_UpItem $f"
    button $f.0.0.down   -takefocus 0 -text Down   -command "FA_DownItem $f"

    pack   $f.0.0.up $f.0.0.down \
	    $f.0.0.add $f.0.0.delete -side top -fill x

    pack   $f.0.0 -side left -expand false -fill y
    
    # Scroll+Listbox 
    frame     $f.0.1 
    scrollbar $f.0.1.sc -command "$f.0.1.lb yview" -takefocus 0
    listbox   $f.0.1.lb -height 5 -width 20 -exportselection false \
	    -selectbackground "#dfdfaf" \
	    -yscrollcommand "$f.0.1.sc set"\
	    -selectmode single -setgrid 1\
	    -takefocus 0
    
    pack      $f.0.1.sc -side left -fill both
    pack      $f.0.1.lb -side left -fill both -expand true   
    pack   $f.0.1 -side left -fill both -expand true   
    
    
    ####  Frame #1
    ##################
    
    frame $f.1.0
    
    label  $f.1.0.la0  -text "Alias:"  -font *fixed*b*
    entry  $f.1.0.en0  -textvariable fa(alias)  -width 20
    checkbutton $f.1.0.chk -variable fa(anon) -relief ridge\
	    -text Anonymous -command "FA_Anon $f" -anchor w\

    label  $f.1.0.la1  -text "Host :"  -font *fixed*b*
    entry  $f.1.0.en1  -textvariable fa(host)   -width 53 
    label  $f.1.0.la2  -text "Login:"  -font *fixed*b*
    entry  $f.1.0.en2  -textvariable fa(login)  -width 20
    label  $f.1.0.la3  -text "  Password:" -font *fixed*b*
    entry  $f.1.0.en3  -textvariable fa(passwd) -width 20 -show *
    
    grid $f.1.0.la0  $f.1.0.en0  x            $f.1.0.chk   
    grid $f.1.0.la1  $f.1.0.en1  -            -   
    grid $f.1.0.la2  $f.1.0.en2  $f.1.0.la3   $f.1.0.en3
    
    grid $f.1.0.la0 -sticky w -padx 3 -pady 3
    grid $f.1.0.en0 -sticky ew
    grid $f.1.0.chk -sticky ew
    grid $f.1.0.la1 -sticky w -padx 3 -pady 3
    grid $f.1.0.en1 -sticky ew
    grid $f.1.0.la2 -sticky w -padx 3 -pady 3
    grid $f.1.0.en2 -sticky ew
    grid $f.1.0.la3 -sticky w
    grid $f.1.0.en3 -sticky ew
    grid columnconfigure $f.1.0 0 -weight 0
    grid columnconfigure $f.1.0 1 -weight 1
    grid columnconfigure $f.1.0 2 -weight 0
    grid columnconfigure $f.1.0 3 -weight 1
    
    pack $f.1.0 -expand true -fill both -padx 3 
    
    ####  Frame #2
    ##################
    
    button $f.2.adv -textvariable fa(editmode) -command "FA_ChEditMode $f"\
	    -width 8
    pack   $f.2.adv -side left -pady 3
    
    ####  Frame #3
    ##################
    
    frame  $f.3.0 -relief sunken -bd 1 -bg "#cacaca"
    
    label  $f.3.0.la0 -text "Port   :"    -bg "#cacaca" -font *fixed*b*
    entry  $f.3.0.en0 -textvariable fa(port)    -width 6
    label  $f.3.0.la1 -text "Chunk  :"   -bg "#cacaca" -font *fixed*b*
    entry  $f.3.0.en1 -textvariable fa(chunk)   -width 6
    label  $f.3.0.la2 -text "Timeout:" -bg "#cacaca" -font *fixed*b*
    entry  $f.3.0.en2 -textvariable fa(timeout) -width 6
    checkbutton $f.3.0.chk0 -bd 2 -highlightthickness 1 -bg "#cacaca" \
	    -text "PassiveDataConnection" -relief ridge  -font *fixed*b*\
	    -width 22 -anchor nw -variable fa(passive)
    checkbutton $f.3.0.chk1 -bd 2 -highlightthickness 1 -bg "#cacaca" \
	    -text "ShortListing" -relief ridge  -font *fixed*b* -width 22\
	    -anchor nw -variable fa(short)
    
    grid $f.3.0.la0 $f.3.0.en0  x          $f.3.0.chk0
    grid $f.3.0.la1 $f.3.0.en1  x          $f.3.0.chk1
    grid $f.3.0.la2 $f.3.0.en2  x          x          
    
    grid $f.3.0.la0 $f.3.0.la1 $f.3.0.la2 $f.3.0.la2 -sticky w -padx 3 -pady 3
    grid $f.3.0.en0 $f.3.0.en1 $f.3.0.en2  -sticky we -padx 3
    
    grid columnconfigure $f.3.0 0 -weight 0
    grid columnconfigure $f.3.0 1 -weight 1
    grid columnconfigure $f.3.0 2 -weight 1
    grid columnconfigure $f.3.0 3 -weight 0
    
    ####  Frame #4
    ##################
    
    button $f.4.ok    -text "Apply" -command "FA_Apply $f; $f.4.ok  flash"
    button $f.4.save  -text "Save"  -command "FA_Save $f"
    button $f.4.close -text "Close" -command "FA_Close $f"
    
    pack   $f.4.ok $f.4.save -side left -padx 4 -pady 4
    pack   $f.4.close -side right
    
    grid $f.0 -sticky news
    grid $f.1 -sticky news -padx 1 -ipady 3
    grid $f.2 -sticky news
    grid $f.3 -sticky news
    grid $f.4 -sticky news
    grid rowconfigure $f 0 -weight 1
    grid rowconfigure $f 1 -weight 0
    grid rowconfigure $f 2 -weight 0
    grid rowconfigure $f 3 -weight 1
    grid rowconfigure $f 4 -weight 0
    grid columnconfigure $f 0 -weight 1

    # Bindings
    ##########
    ##########
    
    ## ++ USE BINDTAGS !?
    
    bind $f.1.0.en0 <Return> {eval {
	if {[FA_Apply $fa(toplevel)] == 1} {
	    #FA_EnableButtons $fa(toplevel)
	    focus $fa(toplevel).1.0.en1   
	} {
	    focus $fa(toplevel).1.0.en0
	} 
    }   }

    #bind $f.1.0.en0 <Return> "focus $fa(toplevel).1.0.en1"
    bind $f.1.0.en1 <Return> "focus $fa(toplevel).1.0.en2"
    bind $f.1.0.en2 <Return> "focus $fa(toplevel).1.0.en3"
    bind $f.1.0.en3 <Return> "focus $fa(toplevel).4.ok"
    bind $f.4.ok    <Return> "$fa(toplevel).4.ok invoke"

    # Key-bindings for listbox 
    bind $f <Up>   "FA_MoveSelection $f -1"
    bind $f <Down>   "FA_MoveSelection $f  1"
    bind $f <Shift-Delete>  "FA_DeleteItem $f"
    bind $f <Shift-Insert>   "FA_AddItem    $f"
    bind $f <Shift-Up>   "FA_UpItem     $f"
    bind $f <Shift-Down> "FA_DownItem   $f"

    bind $f <Control-a>  "FA_Apply $f"
    bind $f <Control-c>  "FA_Close $f"
    bind $f <Control-s>  "FA_Save $f"
    
    
    bind $f.0.1.lb  <Button-1>  "FA_SelectItem $fa(toplevel) %x %y ;  break"
    bind $f.0.1.lb  <Button-3>  "FA_SwapItem $fa(toplevel) %y ;  break"
    bind $f.0.1.lb  <B1-Motion> {eval {
	global fa
	set f $fa(toplevel)
	FA_SwapItem $fa(toplevel) %y
	%W see [%W curselection]
	break
    }   }

    proc FA_MoveSelection {f direction} {
	global fa

	# if buttons disabled, changing selection is not allowed
	if {$fa(disabled)} {return}

	set index [$f.0.1.lb curselection]

	# Move selection in selected direction
	if {!(($index == 0 && $direction == -1) || 
	($index == [expr {[$f.0.1.lb index end] -1}] && $direction == 1))} {
	    tkListboxBeginSelect $f.0.1.lb [expr {$index + $direction}]
	}
	update
	FA_GetItem $f
	FA_Update $f
    }

    proc FA_CheckClones {f} {
	global fa
	
	#FA_EnableButtons $f
	
	FA_Apply $f
	set fa(new) 0
	return 1
	
    }

    proc FA_SelectItem {f x y} {
	global fa
	if {$fa(new) == 1} {return}
	if {[$f.0.1.lb get [$f.0.1.lb curselection]] == ""} {
	    FA_DeleteItem $f
	}
	tkListboxBeginSelect $f.0.1.lb [$f.0.1.lb index @$x,$y]
	FA_GetItem $f
	$f.0.1.lb see [$f.0.1.lb curselection]
    }
    
    proc FA_SwapItem {f y} {
	set index [$f.0.1.lb curselection]
	set near  [$f.0.1.lb nearest $y]
	if {$index != $near} {
	    set tmp [$f.0.1.lb get $index]
	    $f.0.1.lb delete $index
	    $f.0.1.lb insert $near $tmp
	    $f.0.1.lb select set $near
	}
	$f.0.1.lb see [$f.0.1.lb curselection]
    }
    
    proc FA_ChEditMode {f} {
    global fa
	if {$fa(editmode) == "Advanced..."} {
	    pack  $f.3.0 -fill both -padx 3 -pady 3
	    set fa(editmode) "Normal..."
	    update idletasks
	} {
	    pack forget $f.3.0
	    set fa(editmode) "Advanced..."     
	    $f.3 configure -height 1 -relief flat
	    update idletasks
	}
    }
    
    proc FA_Anon {f} {
	global fa
	if {$fa(anon) == 1} {
	    $f.1.0.en3 configure -show ""
	    if {$fa(login) == ""} {
		set fa(login) anonymous
	    }
	    if {$fa(passwd) == ""} {
		set fa(passwd) ftp@ftp
	    }
	} {
	    $f.1.0.en3 configure -show *
	}
    }
    
    proc FA_SetToDefaults {} {
	global fa
	set fa(port)    $fa(port_default)
	set fa(chunk)   $fa(chunk_default)  
	set fa(timeout) $fa(timeout_default)  
	set fa(passive) $fa(passive_defaults)
	set fa(short)   $fa(short_defaults) 
    }
    
    proc FA_AddItem {f} {
	global fa falists

	FA_DisableButtons $f 1
	update idletasks

	# Whitespaces ?
	if {[string match "* *" $fa(alias)]} {
	    MessageBox "Whitespaces are not allowed in alias name!"
	    return
	}

	# Name exists ?
	if {$fa(new) == 1} {
	    foreach i [$f.0.1.lb get 0 end] {
		if {$fa(alias) == $i} {
		    MessageBox "Alias exists! Please change name!"
		    return
		}
	    }
	}
	set index [$f.0.1.lb curselection]
	
	if {$index == ""} {set index -1}
	
	if {($index == -1) || ([$f.0.1.lb get $index] != "")} {
	    #FA_Apply $f $index
	    FA_EnableEntries $f
	    update idletasks
	    $f.0.1.lb insert [expr {$index + 1}] ""
	    $f.0.1.lb select clear 0 end
	    $f.0.1.lb select set [expr {$index + 1}]
	    focus $f.1.0.en0
	    FA_ClearEntries
	    FA_SetToDefaults
	    FA_Anon $f
	}
	$f.0.1.lb see [$f.0.1.lb curselection]
	set fa(new) 1
    }
    
    proc FA_DeleteItem {f} {
	global fa falist falists

	if {[$f.0.1.lb size] == 0} {return -1}

	set index [$f.0.1.lb curselection]
	if {![AskWin "Delete alias '[$f.0.1.lb get $index]' ?" -100 -100]} {
	    return
	}
	FA_EnableButtons $f
	catch {unset falist([$f.0.1.lb get $index])}
	$f.0.1.lb delete $index
	if {$index == [$f.0.1.lb index end]} {
	    $f.0.1.lb selection set [expr {$index - 1}]
	} {
	    $f.0.1.lb selection set $index
	}
	if {[$f.0.1.lb size] == 0} {
	    FA_ClearEntries
	    FA_DisableButtons $f
	    FA_DisableEntries $f
	} {
	    update idletasks
	    FA_GetItem $f
	}
	set falists [$f.0.1.lb get 0 end]	
	set fa(edited) 1
	set fa(new) 0
    }
    
    proc FA_UpItem {f} {

	if {[$f.0.1.lb size] == 0} {return -1}

	set index [$f.0.1.lb curselection]
	
	if {$index != 0} {
	    set tmp [$f.0.1.lb get $index]
	    $f.0.1.lb delete $index
	    $f.0.1.lb insert [incr index -1] $tmp
	    $f.0.1.lb select set $index
	}
	$f.0.1.lb see [$f.0.1.lb curselection]
	set fa(edited) 1
    }
    
    proc FA_DownItem {f} {

	if {[$f.0.1.lb size] == 0} {return -1}

	set index [$f.0.1.lb curselection]
	if {$index != [expr {[$f.0.1.lb index end] -1}]} {
	    set tmp [$f.0.1.lb get $index]
	    $f.0.1.lb delete $index
	    $f.0.1.lb insert [incr index] $tmp
	    $f.0.1.lb select set $index
	}
	$f.0.1.lb see [$f.0.1.lb curselection]
	set fa(edited) 1
    }
    
    proc FA_Apply {f {index 0}} {
	global fa falist falists
	
	if {$index == -1} {return -1}
	
	if {[$f.0.1.lb size] == 0} {
	    set falists ""
	    return -1
	}

	set index [$f.0.1.lb curselection]
	set name  [$f.0.1.lb get $index]
	
	set list [$f.0.1.lb get 0 end]
	#puts "APPLY-LIST: $list"
	set list [lreplace $list $index $index ""]
	
	#puts "APPLY-LIST: $list"

	if {[string match "" $fa(alias)]} {
	    MessageBox "You must give a name to alias!"
	    focus $f.1.0.en0
	    return -1
	}

	if {[string match "* *" $fa(alias)]} {
	    MessageBox "Whitespaces are not allowed in alias name!!"
	    focus $f.1.0.en0
	    return -1
	}
	
	if {[lsearch -exact $list $fa(alias)] != -1} {
	    MessageBox "Alias exists! Please change name!"
	    focus $f.1.0.en0
	    return -1
	}
	FA_Update $f
	#puts "APPLY: $name"

	set fa(edited) 1
	# Encrypt non-anonymous -login password
	if {$fa(anon) == 0} {
	    set pass [Encrypt $fa(passwd)]
	} {
	    set pass $fa(passwd)
	}
	
	set name $fa(alias)
	set falist(${name}) \
		[list $fa(host) $fa(anon) $fa(login) $pass\
		$fa(port) $fa(chunk) $fa(timeout) $fa(passive) $fa(short)]
	
	set falists [$f.0.1.lb get 0 end]	
	set fa(new) 0
	FA_EnableButtons $fa(toplevel)
	return 1
    }
    
    proc FA_Update {f} {
	global fa
	set index [$f.0.1.lb curselection]
	$f.0.1.lb delete $index
	$f.0.1.lb insert $index $fa(alias)
	$f.0.1.lb selection set $index
	$f.0.1.lb see [$f.0.1.lb curselection]
    }
    
    proc FA_GetItem {f} {
	global fa falist
	set index [$f.0.1.lb curselection]
	
	if {$index == ""} {
	    return
	}
	
	set fa(alias)   [set n [$f.0.1.lb get $index]]
	set fa(host)    [lindex $falist($n) 0]
	set fa(anon)    [lindex $falist($n) 1]
	set fa(login)   [lindex $falist($n) 2]
	set fa(port)    [lindex $falist($n) 4]
	set fa(chunk)   [lindex $falist($n) 5]
	set fa(timeout) [lindex $falist($n) 6]
	set fa(passive) [lindex $falist($n) 7]
	set fa(short)   [lindex $falist($n) 8]
	
	# Anonymous-login password is NOT encrypted
	if {$fa(anon) == 0} {
	    set fa(passwd)  [Decrypt [lindex $falist(${n}) 3]]
	} {
	    set fa(passwd)  [lindex $falist(${n}) 3]
	}
	FA_Anon $f
	$f.0.1.lb see [$f.0.1.lb curselection]
    }
    
    proc FA_Load {f} {
	global fa falist falists

	if {[info exists falist]} {
	    foreach i $falists {

		$f.0.1.lb insert end $i
	    }
	    $f.0.1.lb selection set 0
	    FA_GetItem $f
	} {
	    FA_DisableButtons $f
	    FA_DisableEntries $f
	}
    }
    
    proc FA_Save {f} {
	global fa falist falists xf
	FA_Apply $f

	MakeSaveDir
	set xf(ftpbook_file) "$xf(user_home)[file tail $xf(ftpbook_file)]"
	set fileid [open $xf(ftpbook_file) w] 
	
	puts $fileid "set falists \[list [$f.0.1.lb get 0 end]\]"	
	
	foreach i [$f.0.1.lb get 0 end] {
	    puts $fileid "set falist($i) \[list $falist(${i})\]"
	}
	close $fileid
	unset falist falists
	catch {source $xf(ftpbook_file)}
	set fa(edited) 0
	$f.4.save flash
    }
    
    proc FA_ClearEntries {} {
	global fa
	set fa(alias)  ""
	set fa(host)   ""
	set fa(login)  ""
	set fa(passwd) ""
	set fa(anon)   0
    }
    
    proc FA_DisableEntries {f} {
	global fa
	foreach i [list $f.1.0.en0 $f.1.0.en1 \
		$f.1.0.en2 $f.1.0.en3 $f.1.0.chk] {
	    $i configure -state disabled
	}
	set fa(disabled) 1
    }
    
    proc FA_DisableButtons {f {addbutton 0}} {
	global fa
	set list [list $f.2.adv $f.4.save]
	
	# Disable Add-button
	if {$addbutton == 1} {
	    set list [linsert $list end $f.0.0.add]
	    $f.0.0.delete configure -state normal
	} {
	    #set list [linsert $list end $f.0.0.delete]
	}
	
	foreach i $list {
	    $i configure -state disabled
	}
	set fa(disabled) 1
    }

    proc FA_EnableEntries {f} {
	global fa
	foreach i [list $f.1.0.en0 $f.1.0.en1 $f.1.0.en2 $f.1.0.en3 $f.1.0.chk] {
	    $i configure -state normal
	}
    }
    
    proc FA_EnableButtons {f} {
	global fa
	foreach i [list $f.0.0.delete $f.0.0.up $f.0.0.down \
		$f.2.adv $f.4.ok $f.4.save $f.0.0.add] {
	    $i configure -state normal
	}
	set fa(disabled) 0
    }
    
    proc FA_Close {f} {
	global xf fa

	if {$fa(edited)} {
	    set ret [AskWin "You have made changes! Close the editor without saving?" -100 -100 "Save"]
	    switch $ret {
		0 {return}
		2 {FA_Save $f}
		default {}
	    }
	}
	destroy $f
	unset fa
	set xf(fae_on) 0
    }

    FA_SetToDefaults
    FA_Load $f   

}





