# fileselector.tcl --
#
# This file contains the file selector code.
#
#
#  TkRat software and its included text is Copyright 1996,1997,1998
#  by Martin Forssén
#
#  Postilion software and its included text and images
#  Copyright (C) 1998, 1999, 2000 Nic Bernstein
#
#  The full text of the legal notices is contained in the files called
#  COPYING and COPYRIGHT.TkRat, included with this distribution.
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
# 
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

# This is a list of previously used directories
set file_prevDirs {}

# This is true if we should show dotfiles
set file_ShowAll 0

# FileSelectorCreate --
#
# Creates a file selector window which is packed into the window whose
# name is passed in the window parameter. The selector uses the variable
# whose name is passed as the fileNameVariable argument to store the
# filename. When the user is done (doubleklicks or presses return) doneCmd
# is executed in the global context. This command returns a handler which
# is meant to be passed to FileSelectorDone when we are done with this.
#
# Arguments:
# window           -	Window in which the fileselctor should be created
# fileNameVariable -	Name of variable into which the filename should be
#			delivered upon completion
# doneCmd          -	A command which is executed when the user is done
# dirOK		   -	True if we may want a directory.
# mustExist	   -	True if we only accept an existing entity.

proc FileSelectorCreate {window fileNameVariable doneCmd dirOK mustExist} {
    global t b idCnt file_prevDirs file_ShowAll defaultFont
    upvar #0 $fileNameVariable fileName

    # Create a unique handler
    set handler fileWin_[incr idCnt]
    upvar #0 $handler fh

    set fh(fileName) $fileNameVariable
    set fh(doneCmd) $doneCmd
    set fh(dirOK) $dirOK
    set fh(mustExist) $mustExist

    # Main frame
    set w $window.f
    frame $w -bd 2
    set fh(w) $w

    # The buttons
    frame $w.b
    checkbutton $w.b.show \
	    -text $t(show_all) \
	    -variable file_ShowAll \
	    -command "FileSelectorUpdate $handler 0"
    menubutton $w.b.old \
	    -relief raised \
	    -text $t(old_dirs) \
	    -menu $w.b.old.m
    pack $w.b.show -side left
    pack $w.b.old -side left -padx 2m -fill x -expand 1
    set b($w.b.show) fs_show
    set b($w.b.old) fs_old

    # Populate menu
    menu $w.b.old.m
    foreach dir $file_prevDirs {
	$w.b.old.m add command -label $dir \
	    -command "set ${handler}(dir) $dir; FileSelectorUpdate $handler 0"
    }

    # The names
    frame $w.names
    entry $w.names.dir \
	    -relief flat \
	    -textvariable ${handler}(dir) \
	    -width 40 \
	    -state disabled
    entry $w.names.file -relief sunken -textvariable ${handler}(file) -width 40
    pack $w.names.dir \
	 $w.names.file -side top -fill x
    bind $w.names.file <Return> "FileSelectorUpdate $handler 1"

    # The list
    frame $w.l
    scrollbar $w.l.s \
	    -relief sunken \
	    -command "$w.l.l yview" \
	    -highlightthickness 0
    listbox $w.l.l \
	    -yscroll "$w.l.s set" \
	    -height 15 \
	    -setgrid true \
	    -font $defaultFont \
	    -selectmode single
    # Scroll mouse support added by Stephen Ryan 14 Oct 1999
    bind $w.l.l <5> [list $w.l.l yview scroll 5 units]
    bind $w.l.l <4> [list $w.l.l yview scroll -5 units]
    bind $w.l.l <Shift-5> [list $w.l.l yview scroll 1 units]
    bind $w.l.l <Shift-4> [list $w.l.l yview scroll -1 units]
    bind $w.l.l <Control-5> [list $w.l.l yview scroll 1 pages]
    bind $w.l.l <Control-4> [list $w.l.l yview scroll -1 pages]
    pack $w.l.s -side left -fill y
    pack $w.l.l -side right -expand 1 -fill both
    set fh(list) $w.l.l
    bind $w.l.l <ButtonPress-1> "set ${handler}(file) \[%W get \[%W nearest %y\]\]"
    bind $w.l.l <ButtonRelease-1> "$w.l.l selection clear 0 end"
    bind $w.l.l <Double-1> "FileSelectorUpdate $handler 1"
    bindtags $w.l.l [list Listbox $w.l.l [winfo toplevel $w] all]

    pack $w.b -side top -fill x
    pack $w.names -side top -fill x -pady 5
    pack $w.l -fill both -expand 1
    pack $w -fill both -expand 1

    # Initialize filename if needed
    if ![info exists fileName] {
	if [llength $file_prevDirs] {
	    set fh(dir) [lindex $file_prevDirs 0]
	} else {
	    set fh(dir) [pwd]
	}
	set fh(file) ""
    } else {
	if { ![string match "/*" $fileName] } {
	    if [llength $file_prevDirs] {
		set fh(dir) [lindex $file_prevDirs 0]
	    } else {
		set fh(dir) [pwd]
	    }
	    set fh(file) $fileName
	} else {
	    if [file isdirectory $fileName] {
		set fh(dir) $fileName
		set fh(file) ""
	    } else {
		set fh(dir) [file dirname $fileName]
		set fh(file) [file tail $fileName]
	    }
	}
    }
    focus $w.names.file

    FileSelectorUpdate $handler 0
    return $handler
}

# FileSelectorCleanup --
#
# Combines fh(dir) and fh(file) to one single pathname then simplifies this
# and stores it back. The combined value is also inserted into the global
# variable
#
# Arguments:
# handler -	The handler which identifies this selection session

proc FileSelectorCleanup {handler} {
    upvar #0 $handler fh
    upvar #0 $fh(fileName) fileName
    global env

    regsub {/$} $env(HOME) {} home

    # If the filename starts with / or ~ then use it to work with, othervise
    # it should be concatenated with the directoryname
    if [regexp ^/|^~ $fh(file)] {
	if {[catch {set fileName [glob $fh(file)]}] && [catch {set fileName\
		    [glob [file dirname $fh(file)]]/[file tail $fh(file)]}]} {
	    set fileName $fh(file)
	}
    } else {
	set fileName $fh(dir)/$fh(file)
    }
    # Resolve any starting ~
    regsub ^~/ $fileName $home/ fileName
    # Replace // with /
    regsub -all //+ $fileName / fileName
    # Resolve all /.. (remove the dir before and he ..)
    while { 1 == [regexp {/\.\.$|/\.\./} $fileName]} {
	regsub {/[^/]+/\.\.} $fileName {} fileName
    }
    # Replace // with / again and remove trailing /
    regsub -all //+ $fileName / fileName
    regsub {/$} $fileName {} fileName
    if { 0 == [string length $fileName]} {
	set fileName /
    }

    if {[file isdirectory $fileName] || [regexp /$ $fileName]} {
	set dir $fileName
	set file ""
    } else {
	set dir [file dirname $fileName]
	set file [file tail $fileName]
    }
    if { 0 == [string first $home $dir]} {
	set last [string length $dir]
	set start [string length $home]
	set dir ~[string range $dir $start $last]
    }

    set fh(dir) $dir
    set fh(file) $file
}

# FileSelectorUpdate --
#
# This is the part of the file selector which does the actual work. First
# we take the filename and simplify it. When that is done the list is
# repopulated.
#
# Arguments:
# handler -	The handler which identifies this selection session
# go      -	Non null if the fileselector should go in, i.e. call done if it
#		found a file.

proc FileSelectorUpdate {handler go} {
    upvar #0 $handler fh
    upvar #0 $fh(fileName) fileName
    global env t file_ShowAll

    FileSelectorCleanup $handler
    if { 0 < $go } {
	if ![file isdirectory [file dirname $fileName]] {
	    set fail $fileName
	    set fileName [file dirname $fileName]
	    while {![file isdirectory $fileName]} {
		if [string length $fh(file)] {
		    set fh(file) [file tail $fileName]/$fh(file)
		} else {
		    set fh(file) [file tail $fileName]
		}
		set fileName [file dirname $fileName]
		set fh(dir) [file dirname $fh(dir)]
	    }
	    $fh(list) delete 0 end
	    Popup "$fail $t(does_not_exist)"
	    return
	}

	if {([file exists $fileName] && ([file isfile $fileName]
		    || ([file isdirectory $fileName] && $fh(dirOK) && $go>1)))
		|| (0 == [file exists $fileName] && 0 == $fh(mustExist))} {
	    set cmd $fh(doneCmd)
	    uplevel #0 $cmd
	    return
	}
    }

    # Populate list
    $fh(list) delete 0 end
    if { "/" != $fileName } {
	$fh(list) insert end ../
    }
    if $file_ShowAll {
	set list [glob -nocomplain $fh(dir)/.* $fh(dir)/*]
	set i [lsearch $list */.]
	set list [lreplace $list $i $i]
	set i [lsearch $list */..]
	set list [lreplace $list $i $i]
    } else {
	set list [glob -nocomplain $fh(dir)/*]
    }
    set list [lsort -ascii $list]
    set lastdir 1
    foreach i $list {
	if [file isdirectory $i] {
	    $fh(list) insert $lastdir [file tail $i]/
	    incr lastdir
	} else {
	    $fh(list) insert end [file tail $i]
	}
    }
}

# FileSelectorDone --
#
# Insert the filename into the cache
#
# Arguments:
# handler -	The handler which identifies this selection session

proc FileSelectorDone {handler} {
    upvar #0 $handler fh
    global b file_prevDirs option

    FileSelectorCleanup $handler
    # Insert dirname in cache
    if { -1 != [set index [lsearch -exact $file_prevDirs $fh(dir)]]} {
	set file_prevDirs [lreplace $file_prevDirs $index $index]
    }
    set file_prevDirs [lrange [linsert $file_prevDirs 0 $fh(dir)] 0 \
	    [expr $option(prev_dir)-1]]
    foreach bn [array names b $fh(w).*] {unset b($bn)}
    unset fh
}


# CreateModeMenu --
#
# Creates a menu which the user may use to select the mode of a file
#
# Arguments:
# w       - The name of the menubutton to create
# handler - The name of a global array we can use to store our data
# mode	  - The default mode of the file

proc CreateModeMenu {w handler mode} {
    upvar #0 $handler hd
    global t b

    menubutton $w -text $t(mode) -menu $w.m -bd 1 -relief raised \
	    -indicatoron 1 -padx 8 -pady 4
    set b($w) mode_menu

    if {$mode & 0400} {set hd(ur) 1}
    if {$mode & 0200} {set hd(uw) 1}
    if {$mode & 0100} {set hd(ux) 1}
    if {$mode & 0040} {set hd(gr) 1}
    if {$mode & 0020} {set hd(gw) 1}
    if {$mode & 0010} {set hd(gx) 1}
    if {$mode & 0004} {set hd(or) 1}
    if {$mode & 0002} {set hd(ow) 1}
    if {$mode & 0001} {set hd(ox) 1}
    menu $w.m
    $w.m add command -label $t(all_read) -command "\
	    set ${handler}(ur) 1; set ${handler}(gr) 1; set ${handler}(or) 1"
    $w.m add command -label $t(all_read_write) -command "\
	    set ${handler}(ur) 1; set ${handler}(gr) 1; set ${handler}(or) 1; \
	    set ${handler}(uw) 1; set ${handler}(gw) 1; set ${handler}(ow) 1"
    $w.m add cascade -label $t(perm_user) -menu $w.m.user
    menu $w.m.user
    $w.m.user add checkbutton -label $t(read) -variable ${handler}(ur)
    $w.m.user add checkbutton -label $t(write) -variable ${handler}(uw)
    $w.m.user add checkbutton -label $t(exec) -variable ${handler}(ux)
    $w.m add cascade -label $t(perm_group) -menu $w.m.group
    menu $w.m.group
    $w.m.group add checkbutton -label $t(read) -variable ${handler}(gr)
    $w.m.group add checkbutton -label $t(write) -variable ${handler}(gw)
    $w.m.group add checkbutton -label $t(exec) -variable ${handler}(gx)
    $w.m add cascade -label $t(perm_other) -menu $w.m.other
    menu $w.m.other
    $w.m.other add checkbutton -label $t(read) -variable ${handler}(or)
    $w.m.other add checkbutton -label $t(write) -variable ${handler}(ow)
    $w.m.other add checkbutton -label $t(exec) -variable ${handler}(ox)
    bind $w <Destroy> {catch "unset b(%W)"}
}

# GetMode --
#
# Returns the mode the user decided via the menus
#
# Arguments:
# handler - The name of a global array we used to store our data

proc GetMode {handler} {
    upvar #0 $handler hd
    return [expr $hd(ur)*0400 + $hd(uw)*0200 + $hd(ux)*0100\
	       + $hd(gr)*0040 + $hd(gw)*0020 + $hd(gx)*0010\
	       + $hd(or)*0004 + $hd(ow)*0002 + $hd(ox)*0001]
}
