########################################################################
#
#  TreeBrowser
#
#  The Browser class implements a graphical browser that can be used to
#  browse a hierarchy of items.  It is meant to be a superclass to other
#  classes and should not be directly instantiated.
#
########################################################################

proc TreeBrowser {} {}
rename TreeBrowser {}

itcl_class TreeBrowser {

    # ------------------------------------------------------------------
    #  CONSTRUCTOR - create new browser
    # ------------------------------------------------------------------
    constructor {config} {
	set tree(0) 0 ; unset tree(0)
	if {! $constructTreeBrowser} { return }

	#
	#  Create a window with the same name as this object
	#
	set class [$this info class]
	::rename $this $this-tmp-
	::frame $this -class $class
	catch {::rename $this-win- ""}
	::rename $this $this-win-
	::rename $this-tmp- $this
	set top $this

	#
	# Initialize all the widgets
	#

	$this _initvars
	$this _initwidgets
    }

    # ------------------------------------------------------------------
    #  DESTRUCTOR - destroy window containing widget
    # ------------------------------------------------------------------
    destructor {
	::rename $this-win- {}
	destroy $this
    }

    # ------------------------------------------------------------------
    #  METHOD:  _initvars - used to initialize variables from subclasses
    # ------------------------------------------------------------------
    method _initvars {} {
	set multi_select 0
    }

    # ------------------------------------------------------------------
    #  METHOD:  _initwidgets - create all the widgets used by the browser
    # ------------------------------------------------------------------
    method _initwidgets {} {
	#
	# Generic config options
	#

	set pads {}
	set bttn_pads {}
	
	#
	# Labels
	#

	frame $top.title
	grid $top.title -gridy 0 -gridwidth 0

	#
	# List Boxes
	#
	
	scrollbar $top.vsbar -relief sunken -command "$top.list yview"

	tkListboxBind {}
	listbox $top.list -relief sunken -width 30 -height 30 \
	    -exportselection 0 \
	    -xscrollcommand "$top.hsbar set" \
	    -yscrollcommand "$top.vsbar set" \
	    -selectmode single

	scrollbar $top.hsbar -orient horizontal -relief sunken \
	    -command "$top.list xview"

	set select [ format {
	    set index [lindex [%%W curselection] 0]
	    %s adjustButtons $index
	    %s adjustPath $index
	} $this $this ]

	set activate [ format {
	    %s adjust [lindex [%%W curselection] 0]
	} $this ]

	bind $top.list <Button-1> $select
	bind $top.list <space> $select
	bind $top.list <Double-Button-1> $activate
	bind $top.list <Double-space> $activate

	#  Ignore extra clicks
	bind $top.list <Triple-Button-1> { }

	eval grid $top.vsbar -gridheight 1 -fill y $pads
	eval grid $top.list -gridwidth 0 -fill both -weightx 1 -weighty 1 $pads
	eval grid $top.hsbar -gridx 1 -gridwidth 0 -fill x $pads
	

	#
	# Expand button
	#

	frame $top.bttnFrame
	grid $top.bttnFrame -gridwidth 0 -fill x
	button $top.bttnFrame.expand -width [string length Collapse] \
	    -textvariable $top-expandLabel \
	    -command [ format {
		set index [lindex [%s curselection] 0]
		if {$index != ""} {
		    %s adjust $index
		}
	    } $top.list $this ]
	global $top-expandLabel
	set $top-expandLabel Expand
	button $top.bttnFrame.rescan -text Rescan -command "$this rescan 0"
	eval grid $top.bttnFrame.expand $top.bttnFrame.rescan $bttn_pads
	$this _initbuttons $top.bttnFrame $bttn_pads
	
	#
	# Pathname Label and Entry
	#

	frame $top.pathFrame
	label $top.pathLabel -text Path
	entry $top.pathEntry -textvariable $top-path -relief sunken
	focus $top.pathEntry
	bind $top.pathEntry <Return> [ format {
	    %s setpath [set %s] } $this $top-path ]

	grid $top.pathFrame -gridwidth 0 -fill x
	eval grid $top.pathLabel -gridy 0 -in $top.pathFrame \
	    	-padx 2 -pady 2
	eval grid $top.pathEntry -gridy 0 -in $top.pathFrame \
		-fill x -weightx 1 -padx 2 -pady 2
    }

    # ------------------------------------------------------------------
    #  METHOD:  _initbuttons - subclasses can add extra buttons here
    # ------------------------------------------------------------------
    method _initbuttons {parent pads} {
	# Put extra buttons here
    }

    # ------------------------------------------------------------------
    #  METHOD:  init
    # ------------------------------------------------------------------
    method init {clientData} {
	$this reset
	set tree(0) [ list {} 0 1 $clientData $root 0 ]
	$top.list insert 0 $root
	$top.list selection set 0
	$top.list activate 0
	$this adjust 0
    }

    # ------------------------------------------------------------------
    #  METHOD:  reset
    # ------------------------------------------------------------------
    method reset {} {
	if { [array size tree] } {
	    areplace tree 0 [expr [array size tree] - 1]
	}
	set expandList {}
	$top.list delete 0 end
    }

    # ------------------------------------------------------------------
    #  METHOD:  path - return the current path
    # ------------------------------------------------------------------
    method path {} {
	set index [lindex [$top.list curselection] 0]
	set line $tree($index)
	set path $sep[join [lindex $line $nameIndex] $sep]
	return $path
    }

    # ------------------------------------------------------------------
    #  METHOD:  setpath - set the current path for the browser
    # ------------------------------------------------------------------
    method setpath {path} {
	set contents [split [string trim $path $sep] $sep]

	set valid [$this _validPath $contents]
	if {$valid == 0} {
	    error "$NoSuchEntry: $path"
	}

	set pathlist ""
	set expanding 0
	set tryRescan 1

	# Make sure the root directory is opened
	set contents [eval list {{}} $contents]

	foreach i $contents {
	    #  Don't append the empty list to the pathlist
	    if {$i != ""} {
	        lappend pathlist $i
	    }

	    #  Check to see if this path is already expanded.  If it is,
	    #  then keep looping.  If it isn't, then start expanding things
	    #  from here on.
	    if { [lsearch -exact $expandList $pathlist] == -1 } {
		#  Expand pathlist
		set index [$this findIndex $pathlist]
		if { $index == -1 } {
		    set depth [llength $pathlist]
		    incr depth -2
		    if { $depth >= 0 } {
			set parentPath [lrange $pathlist 0 $depth]
		    } else {
			set parentPath ""
		    }
		    set thisIndex [$this findIndex $parentPath]

		    if { $sparse } {
			incr thisIndex
			if { [lindex $tree($thisIndex) $expandedIndex] } {
			    $this collapse $thisIndex
			    set index [$this findIndex $pathlist]
			}
			incr thisIndex -1
		    }
		    if {$index == -1 && $tryRescan} {
			$this rescan $thisIndex
			set index [$this findIndex $pathlist]
		    }
		}

		#  Give up
		if {$index == -1 } {
		    error "$NoSuchEntry: $path"
		}

		#  Expand the path
		if {[lindex $tree($index) $dirCodeIndex] != 0} {
		    $this expand $index
		}

		#  Since we just expanded the path, there is no point in
		#  attempting a rescan on the next iteration
		set tryRescan 0
	    }
	}
	set index [$this findIndex $pathlist]
	$top.list selection clear 0 end
	$top.list selection set $index
	$top.list activate $index
	$this adjustButtons $index
	$this adjustPath $index
	$this _makeSelectedVisible
    }

    # ------------------------------------------------------------------
    #  METHOD:  findIndex - find the index in "array" corresponding to
    #  the given path list (the pathlist is compared with the nameIndex
    #  element of each list stored in the array).
    # ------------------------------------------------------------------
    method findIndex {pathlist} {
	set size [array size tree]
	set index -1

	for {set i 0} {$i < $size} {incr i} {
	    set line $tree($i)
	    set namelist [lindex $line $nameIndex]

	    if {$namelist == $pathlist} {
		set index $i
		break
	    }
	}
	return $index
    }

    method rescan {index} {
	set old_index [lindex [$top.list curselection] 0]
	set index [$this collapse $index]
	$this expand $index

	if {$old_index != "" && $old_index < [$top.list size]} {
	    $top.list selection clear 0 end
	    $top.list selection set $old_index
	    $top.list activate $old_index
	    $this adjustButtons $old_index
	    $this adjustPath $old_index
	}
    }

    method _makeSelectedVisible {} {
	set selectedItem [lindex [$top.list curselection] 0]
	if {$selectedItem == ""} {return}

	set topItem [$top.list nearest 0]
	set bottomItem [$top.list nearest 1000000]
	set viewHeight [expr $bottomItem - $topItem]

	if {$selectedItem < $topItem \
	    || $selectedItem > ($topItem + $viewHeight)} {
	    set yview [expr $selectedItem - ($viewHeight / 2)]
	    if {$yview < 0} { set yview 0 }
	    $top.list yview $yview
	}
    }

    method deleteIndex {index} {
	set line $tree($index)
	
	if { [ lindex $line $dirCodeIndex ] != 0 && 
	     [ lindex $line $expandedIndex ] } {
	    $this collapse $index
	}
	$top.list delete $index $index

	set bogus(0) 0 ; unset bogus(0)
	areplace tree $index $index bogus

	if {[llength [$top.list curselection]] == 0} {
	    set $top-expandLabel Expand
	    $top.bttnFrame.expand configure -state disabled
	}
    }

    # ------------------------------------------------------------------
    #  METHOD:  adjust - expand or collapse the given index
    # ------------------------------------------------------------------
    method adjust {index} {
	if { ! [array size tree] } { return }
	set line $tree($index)
	if {$line == "" } {
	    error "Bad index passed to TreeBrowser::adjust: $index"
	}

	if { [ lindex $line $dirCodeIndex ] == 0 } {
   if {[lindex [$top.bttnFrame.expand configure -state] 4] != "disabled" } {
		$this activate $index
	    }
	} else {
	    if { [lindex $line $expandedIndex] } {
		$this collapse $index
	    } else {
		$this expand $index
	    }
	    $this _makeSelectedVisible
	}
    }

    # ------------------------------------------------------------------
    #  METHOD:  adjustButtons - adjust the expand button label
    # ------------------------------------------------------------------
    method adjustButtons {index} {
	if { ! [array size tree] } { return }

	global $top-expandLabel
	global $top.bttnFrame.expand
	set line $tree($index)
	$top.bttnFrame.expand configure -state normal

	if {$line == "" } { return }
	if { [lindex $line $expandedIndex] } {
	    set $top-expandLabel Collapse
	} else {
	    if { ! [lindex $line $dirCodeIndex] } {
		set $top-expandLabel $activate_label
		if {[isActive $index] != 1} {
		    $top.bttnFrame.expand configure -state disabled
		}
	    } else {
		set $top-expandLabel Expand
	    }
	}
    }

    # ------------------------------------------------------------------
    #  METHOD:  adjustPath - adjust the path entry
    # ------------------------------------------------------------------
    method adjustPath {index} {
	if { ! [array size tree] } { return }
	set line $tree($index)

	global $top-path
	set $top-path $sep[join [lindex $line $nameIndex] $sep]
    }

    # ------------------------------------------------------------------
    #  METHOD:  activate - do something with the entry at the given index
    # ------------------------------------------------------------------
    method activate {index} {
	set line $tree($index)
	set path $sep[join [lindex $line $nameIndex] $sep]
	if {$activate_callback != ""} {
	    uplevel #0 $activate_callback $path
	}
    }

    # ------------------------------------------------------------------
    #  METHOD:  isActive - determine whether index is active
    # ------------------------------------------------------------------
    method isActive {index} {
	set line $tree($index)
	set path $sep[join [lindex $line $nameIndex] $sep]
	if {$isActive_callback != ""} {
	    uplevel #0 $isActive_callback $path
	}
    }

    # ------------------------------------------------------------------
    #  METHOD:  expand - expand the given index
    # ------------------------------------------------------------------
    method expand {index} {
	if { ! [array size tree] } { return }
	set line $tree($index)

	set dircode [lindex $line $dirCodeIndex]
	if {$dircode == 0} {
	    if {[isActive $index] == 1} {
		$this activate $index
	    }
	    return
	}

        set parentPath [lindex $line $nameIndex]
	set clientData [lindex $line $clientDataIndex]


	#
	#  This entire section handles the case where the user
	#  double-clicked for the first time on a symbolic link.
	#
	#  Note:  The symbolic link is an example of a situation
	#  where it is too expensive for the _descendants routine
	#  to decide if a thing is a container (directory) or an
	#  entry (file).  A dirCode with the value of "2" can be
	#  used to flag any entry where this is the case.
	#
	if { [lindex $line $dirCodeIndex] == 2 } {
	    # Get the new file and directory codes
	    busy {
		set filecode [$this _fileCode $parentPath $clientData]
		set dircode [$this _dirCode $parentPath $clientData]
	    }

	    # Get the new display name
	    set dpyname \
	        [$this _displayName $parentPath $filecode $dircode $clientData]
		
	    # Update the list
	    $top.list delete $index
	    $top.list insert $index $dpyname
	    $top.list selection clear 0 end
	    $top.list selection set $index
	    $top.list activate $index

	    # Mark the node with the new dircode, filecode and displayname
	    set line [lreplace $line $fileCodeIndex $fileCodeIndex $filecode]
	    set line [lreplace $line $dirCodeIndex $dirCodeIndex $dircode]
	    set line [lreplace $line $displayIndex $displayIndex $dpyname]
	    set tree($index) $line

	    # Don't go any farther if the node isn't a container
	    if { $dircode != 1 } {
		$this adjustButtons $index
		$this adjustPath $index
		return
	    }
	}	    

	#
	# If we are in sparse mode, move all the tree entries at the
	# current depth into the sparse array and delete them from the
	# tree.  But don't delete the container that we are currently
	# expanding.
	#
	set preOffset 0
	if {$sparse && $index != 0} {
	    set depth [llength [lindex $line $nameIndex]]
	    set length [array size tree]

	    # Get the pre and post indicies
	    for {set i [expr $index - 1]} {$i > 0} {incr i -1} {
		set thisLine $tree($i)
		set thisDepth [llength [lindex $thisLine $nameIndex]]
		if {$thisDepth != $depth} {
		    break
		}
	    }
	    set preIndex [expr $i + 1]

	    for {set i [expr $index + 1]} {$i < $length} {incr i} {
		set thisLine $tree($i)
		set thisDepth [llength [lindex $thisLine $nameIndex]]
		if {$thisDepth != $depth} {
		    break
		}
	    }
	    set postIndex [expr $i - 1]

	    # Delete all files from the current depth except the
	    # file that we are expanding
	    set lineArray(0) $line
	    areplace tree $preIndex $postIndex lineArray

	    # Keep the display in sync with the tree list
	    $top.list delete $preIndex $postIndex
	    $top.list insert $preIndex [lindex $line $displayIndex]
	    $top.list selection clear 0 end
	    $top.list selection set $preIndex
	    $top.list activate $preIndex

	    # Set the offset here so that the expandables code at the
	    # end of this function will work properly
	    set preOffset [expr $preIndex - $index]
	    set index $preIndex
	}

	#
	#  Get the descendants of the container and the format this list
	#  so that it can be inserted into the tree list and the listbox.
	#
	#  Note:  This needs to be done after the "sparse" code so that the
	#  expandables indexes are set correctly.
	#
	set contents(0) 0 ; unset contents(0)
	busy {$this _descendants $parentPath $clientData contents}

	# Initialize "treeInsert" to a zero length array
	set treeInsert(0) 0 ; unset treeInsert(0)

	# Initialize "listInsert" and "expandables" to zero length lists
	set listInsert ""
	set expandables ""

	set j [expr $index+1]
	set k 0
	set size [array size contents]
	for {set i 0} {$i < $size} {incr i} {
	    set child $contents($i)

	    # Store the full path name
	    set fullPath [lindex $child $nameIndex]
	    if {$parentPath != ""} {
		set fullPath [concat $parentPath $fullPath]
	    }
	    set child [lreplace $child $nameIndex $nameIndex $fullPath]

	    # Mark as not exanded
	    lappend child 0

	    set treeInsert($k) $child
	    lappend listInsert [lindex $child $displayIndex]

	    if { [lsearch -exact $expandList $fullPath ] != -1 } {
		lappend expandables $j
	    }
	    incr j
	    incr k
	}

	#
	#  Mark the node as expanded
	#
	set line [lreplace $line $expandedIndex $expandedIndex 1]
	set tree($index) $line

	#
	#  Adjust the current node by updating the buttons and the
	#  current path.  This needs to happen after the node has
	#  been marked as expanded.
	#
	$this adjustButtons $index
	$this adjustPath $index

	#  Insert the children into the tree and into the scrolling list
	incr index
	ainsert tree $index treeInsert
	eval $top.list insert $index $listInsert

	#  Add the current path to the expanded list
	if { [lsearch -exact $expandList $parentPath] == -1 } {
	    lappend expandList $parentPath
	}

	#  Expand all additional expansion indexes
	set offset 0
	foreach i $expandables {
	    incr offset [ $this expand [expr $i+$offset] ]
	}
	if { $sparse } {
	    set length 1
	} else {
	    set length [array size contents]
	}
	return [expr $offset + $preOffset + $length]
    }


    # ------------------------------------------------------------------
    #  METHOD:  collapse - collapse the given index
    # ------------------------------------------------------------------
    method collapse {index} {
	if { ! [array size tree] } { return -1 }

	set line $tree($index)
	set length [array size tree]

        set fullpath [lindex $line $nameIndex]
	set depth [llength [lindex $line $nameIndex]]

	#  Figure out which elements need to be deleted.
	busy {
	    for {set i [expr $index+1]} {$i < $length} {incr i} {
		set thisLine $tree($i)
		set thisDepth [llength [lindex $thisLine $nameIndex]]
		if {$thisDepth <= $depth} {
		    break
		}
	    }
	    incr i -1
	}

	# Mark this node as not expanded
	set line [lreplace $line $expandedIndex $expandedIndex 0]
	set tree($index) $line

	#  Delete the elements that need deleting
	set incrIndex [expr $index+1]
	if {$incrIndex <= $i} {
	    areplace tree $incrIndex $i
	    $top.list delete $incrIndex $i
	}

	# Delete the current path from the expanded list
	set delIndex [lsearch -exact $expandList $fullpath]
	if {$delIndex != -1} {
	    set expandList [lreplace $expandList $delIndex $delIndex]
	}

	#
	#  If we are in sparse mode, re-expand the current level.
	#
	if {$sparse && $index != 0} {
	    $top.list delete $index $index
	    incr index -1
	    $this expand $index

	    set index [$this findIndex $fullpath]
	    if { $index == -1 } { return -1 }
	    $top.list selection clear 0 end
	    $top.list selection set $index
	    $top.list activate $index
	}

	#
	#  Adjust the current node by updating the buttons and the
	#  current path.  This needs to happen after the node has
	#  been marked as collapsed.
	#
	$this adjustButtons $index
	$this adjustPath $index

	return $index
    }

    # ------------------------------------------------------------------
    #  These are some default methods for sub-classes
    # ------------------------------------------------------------------
    method _descendants {pathlist clientData array} {
	return -1
    }

    method _fileCode {pathlist clientData} {
	return 0
    }

    method _displayName {name filecode dircode clientData} {
	set padding ""
	set length [llength $pathlist]
	incr length -1
	for {set i 0} {$i < $length} {incr i} {
	    append padding "    "
	}
	set name $padding[lindex $pathlist $length]

	switch $dircode {
	    0 { return $name }
	    1 { return $name/ }
	}
    }

    method _validPath {pathlist} {
	return 1
    }


    # ------------------------------------------------------------------
    #  DATA
    # ------------------------------------------------------------------

    public title {Tree Browser}
    public activate_label {Activate}
    public activate_callback {}
    public isActive_callback {}
    public sparse {0}

    protected top
    protected root
    protected sep
    protected multi_select {0}

    protected tree
    protected expandList {}

    protected nameIndex {0}
    protected fileCodeIndex {1}
    protected dirCodeIndex {2}
    protected clientDataIndex {3}
    protected displayIndex {4}
    protected expandedIndex {5}

    protected constructTreeBrowser {1}
    protected NoSuchEntry {Entry does not exist}
}


########################################################################
#
#  TreeBrowserPopup
#
#  The TreeBrowserPopup is identical to the TreeBrowser except that it
#  creates the browser as a toplevel window rather than as a frame window.
#
########################################################################

proc TreeBrowserPopup {} {}
rename TreeBrowserPopup {}

itcl_class TreeBrowserPopup {
    inherit TreeBrowser

    # ------------------------------------------------------------------
    #  CONSTRUCTOR - create new browser popup
    # ------------------------------------------------------------------
    constructor {config} {

	if {! $constructTreeBrowserPopup } { return }
	set constructTreeBrowser 0

	#
	#  Create a window with the same name as this object
	#
	set class [$this info class]
	::rename $this $this-tmp-
	::toplevel $this -class $class
	catch {::rename $this-win- ""}
	::rename $this $this-win-
	::rename $this-tmp- $this
	set top $this
	wm minsize $this 64 64

	#
	# Initialize all the widgets
	#
	$this _initvars
	$this _initwidgets
    }

    protected constructTreeBrowserPopup {1}
}


########################################################################
#
#  TreeFileBrowser
#
#  The TreeFileBrowser is used to browse the file system
#
########################################################################

proc TreeFileBrowser {} {}
rename TreeFileBrowser {}

itcl_class TreeFileBrowser {
    inherit TreeBrowser

    method _initvars {} {
        set title "File Browser"
	set sep /
	set root /
	set sparse 1
	set NoSuchEntry {No such file or directory}
    }

    method _initbuttons {parent pads} {
	button $parent.cancel -text Cancel -command "$this cancel"
	eval grid $parent.cancel -gw 0 $pads -fill x
    }

    method cancel {} {
	uplevel #0 $cancel_callback
    }

    # ------------------------------------------------------------------
    # Override setpath to do tilde expansion
    # ------------------------------------------------------------------
    method setpath {path} {
	set path [string trim $path " "]
	if {[string index $path 0] == "~"} {
	    set tildeList [split $path /]
	    set path \
    "[glob [lindex $path 0]]${sep}[join [lrange $tildeList 1 end] $sep]"
	}
	TreeBrowser::setpath $path
    }
    
    # ------------------------------------------------------------------
    #  METHOD:  _descendants
    #  Returns a list of the form
    #  { {name filecode dircode displayname} ... }
    # ------------------------------------------------------------------
    method _descendants {pathlist clientData array} {
	upvar $array contents

	set pathname /[join $pathlist /]
	if { $pathname == "/" } { set pathname {} }
	set list [lsort [glob -nocomplain $pathname/*]]

	# Padding
	set padding ""
	set length [llength $pathlist]
	for {set i 0} {$i < $length} {incr i} {
	    append padding "    "
	}

	set j 0
	foreach i $list {
	    set name [file tail $i]

	    if {$name != "." && $name != ".."} {
		set filecode 0
		set dircode 0
		set type [file type $i]

		if {$type == "link"} {
		    set dircode 2
		} else {
		    set dircode [file isdirectory $i]
		    if {! $dircode} {
			if { [file executable $i] } {
			    set filecode 1
			}
		    }
		}
		set dpyname $padding[CodeToName $name $filecode $dircode]
		set contents($j) [list $name $filecode $dircode -1 $dpyname]
	    }
	    incr j
	}
    }

    method _fileCode {pathlist clientData} {
	set pathname /[join $pathlist /]
	if { [file executable $pathname] } {
	    return 1
	} else {
	    return 0
	}
    }

    method _dirCode {pathlist clientData} {
	set pathname /[join $pathlist /]
	return [file isdirectory $pathname]
    }

    method _displayName {pathlist filecode dircode clientData} {
	# Padding
	set padding ""
	set length [llength $pathlist]
	incr length -1
	for {set i 0} {$i < $length} {incr i} {
	    append padding "    "
	}

	set name [lindex $pathlist $length]
	return $padding[ CodeToName $name $filecode $dircode ]
    }

    method _validPath {pathlist} {
	set pathname /[join $pathlist /]
	return [file exists $pathname]
    }

    proc CodeToName {name filecode dircode} {
	switch $dircode {
	    0 {
		switch $filecode {
		    0 { return $name }
		    1 { return $name* }
		    2 { return "$name@ #broken link#" }
		}
	    }
	    1 { return $name/ }
	    2 { return $name@ }
	}
    }

    public cancel_callback {}
}


########################################################################
#
#  TreeTkBrowser
#
#  The TreeTkBrowser is used to browse the hierarchy of tk widgets
#
########################################################################

proc TreeTkBrowser {} {}
rename TreeTkBrowser {}

itcl_class TreeTkBrowser {
    inherit TreeBrowser
    
    method _initvars {} {
        set title "Tk Window Browser"
	set sep .
	set root /
    }
    
    # ------------------------------------------------------------------
    #  METHOD:  _descendants
    #  Returns a list of the form
    #  { {name filecode dircode displayname} ... }
    # ------------------------------------------------------------------
    method _descendants {pathlist clientData array} {
	upvar $array contents

	# Padding
	set padding ""
	set length [llength $pathlist]
	for {set i 0} {$i < $length} {incr i} {
	    append padding "    "
	}

	set pathname .[join $pathlist .]
	set list [lsort [winfo children $pathname]]
	set filelist ""

	set j 0
	foreach i $list {
	    regexp {\.([^\.]*)$} $i dummy name
	    set filecode 0
	    set dircode 0
	    if { [winfo children $i] != "" } {
		set dircode 1
	    }
	    if {$dircode} {
		set dpyname $padding$name/
	    } else {
		set dpyname $padding$name
	    }

	    set contents($j) [list $name $filecode $dircode -1 $dpyname]
	    incr j
	}
	return $filelist
    }

    method _dirCode {pathlist clientData} {
	set pathname .[join $pathlist .]
	if { [winfo children $pathname] != "" } {
	    return 1
	} else {
	    return 0
	}
    }
}
