#!/usr/bin/wish

# the "gDataDir" variable holds of the of the directory containing the
# general data files used by Tch. Set it to an appropriate value (wherever
# you place the data directory.)
set gDataDir "./data"

########  UTILITY PROCEDURES ############
# Procedures of general use.
#########################################

# copy one array to another

proc copyArray {source dest} {
	global $source $dest

	set names [array names $source]
	foreach name $names {
		set ${dest}($name) [expr "\$${source}($name)"]
	}
}

# Given a filename and a bunch of variable names (from the current 
# context), save the values in these variable to the given file. The 
# values will be saved as a bunch of "set" statements, so just sourcing
# the files in the appropriate context will reload the variables.

proc saveVars {filename args} {

    set fileID [open $filename w]
    foreach varName $args {
	# make each variable in the global env accessible in turn
	# through the local variable "tmp"
	upvar #0 $varName tmp
	if [info exists tmp] {
	    if [array exists tmp] {
		# The first reference to the variable in the save file will be
		# a command to unset it, so that we may be sure of doing 
		# a "clean" load, i.e. we will restore the variable to the
		# same state it was before.
		puts $fileID "if \[info exists $varName\] \{unset $varName\}"

		# Now save each of the array elements.
		foreach name [array names tmp] {
		    set val $tmp($name)
		    puts $fileID "set ${varName}($name) \{$tmp($name)\}"
		}
	    } else {
		puts $fileID "set $varName $tmp"
	    }
	} else { puts stderr "\n$varName not found" }
    }
    flush $fileID
    close $fileID
}

# proc readArrayFromFile sets the elements of array according to 
# the contents of the given file. Each element of the array is represented
# by a line containing the element name, followed by any number of lines 
# which will be concatenated to form the element value (newlines not
# included, followed by one or more blank lines, to indicate the end
# of the element value.

proc readArrayFromFile {arrayName filename} {
    upvar $arrayName localName

    set fileid [open $filename r]

    while {[gets $fileid line] > 0} {
	# Ignore blank lines
	if {[regexp "^\[ \t\n\]*\$" $line match]} {
	    continue
	} else {
	    # the line we just read was following a blank,
	    # so it must be an array element  name
	    # read the name.
	    set name $line

	    # Now read the value for the element
	    set value ""
	    while {[gets $fileid line] > 0 && \
		    ![regexp "^\[ \t\n\]*\$" $line match]} {
		append value $line
	    }
	    
	    set localName($name) $value
	}
    }
    close $fileid
}

# inverse of the above--writes an array to a file, in the same format.
# Make sure none of the array element names contain newlines.

proc writeArrayToFile {arrayName filename} {
    upvar $arrayName localName

    set fileid [open $filename w]

    foreach name [array names localName] {
	puts $fileid $name
	puts $fileid $localName($name)
	puts $fileid {}
    }

    close $fileid
}

# Given the name of a file, return the contents of the file as a
# list of strings, one string for each line of the file. Newlines
# are not kept.
proc readStrings {file} {
    set contents {}
    if [file exists $file] {
	set fileid [open $file]
	while {[gets $fileid line] > 0} {
	    lappend contents $line
	}
	close $fileid
    }
    return $contents
}

# mytail functions like "file tail $pathname", except that if $pathname
# is a path ending in a slash (such as './dir/subdir/'), mytail will
# return 'subdir' rather than '', which would be returned by 'file tail'.

proc mytail {pathname} {
    if [regexp {^(.*)\/$} $pathname match leading] {
	return [file tail $leading]
    } else {
	return [file tail $pathname]
    }
}


######################################################################
# INITIALIZATION PROCS (for starting up Tch)
####################################################################

proc initFonts {} {
    global fStyles gaGFont gFontSizes
    # Create normal, monospaced, and sans-serif fonts.
    makeGenericFonts {Normal Times Mono Courier SS Helvetica} $gFontSizes

    # The fonts used for specific semantic items are stored in the 
    # gaSFont array.
    makeSemanticFonts {Keyword NormalB Variable NormalBI VariableLight NormalI Code Mono}

    # define "styles" for use with insertFText
    set fStyles(B) "-font $gaGFont(NormalBM)"
    set fStyles(I) "-font $gaGFont(NormalIM)"
    set fStyles(S) "-font $gaGFont(NormalBIM)"
    set fStyles(H) "-font $gaGFont(NormalBL)"
    set fStyles(TW) "-font $gaGFont(MonoM)"
    set fStyles(TITW) "-font $gaGFont(NormalBL)"
    set fStyles(TI) "-font $gaGFont(NormalBL)"
    set fStyles(TIS) "-font $gaGFont(NormalIL)"
}

proc initUserVars {} {
    global gDataDir gPREFSDIR env gUserPrefVars gFontSize gSyntaxWinSize \
	    gShowControlHelp gFontSizes

    # set up the paths/files for this user's personal tch files (prefs, etc.)
    # If settings files already exist, read them in.
    set gPREFSDIR $env(HOME)/.tchpref
    if {[file exists $gPREFSDIR] && [file isdirectory $gPREFSDIR]} {
    } elseif {![file exists $gPREFSDIR]} {
	puts stderr "Setting up the '$gPREFSDIR' preferences directory\n"
	exec mkdir $gPREFSDIR
    } else {
	error "The file '$gPREFSDIR' must be a directory for Tch to operate properly\n"
    }

    # gUserPrefVars stores the names of all variables which affect user-set
    # preferences in Tch. The code which provides default initialization for
    # each such var should also append the name of the var to this list.
    # The list is used by Tch to determine which variables to save in the
    # user's prefs file
    set gUserPrefVars {}

    #Initialize settings.
    set gFontSize medium
    set gSyntaxWinSize small
    set gShowControlHelp 1
    lappend gUserPrefVars gFontSize
    lappend gUserPrefVars gSyntaxWinSize
    lappend gUserPrefVars gShowControlHelp

    if [file exists "$gPREFSDIR/settings"] { 
	source "$gPREFSDIR/settings" 
    }
    switch $gFontSize {
	small {set gFontSizes {S 8 M 10 L 12 X 14}}
	medium {set gFontSizes {S 10 M 12 L 14 X 18}}
	large {set gFontSizes {S 12 M 14 L 18 X 20}}
    }
}

# Procedure to read the contents of the master indexes into memory

proc readMasterIndex {dataDir} {
    global gSectionsIndex gCommandsIndex gDescsIndex gSynopsesIndex gNodeToFile

    foreach dir [glob $dataDir/sections/*] {
	regexp {\/([^\/]+)$} $dir match dirname
	foreach file [glob $dir/data/*.dsc] {
	    regexp {\/([^\/]+).dsc$} $file match filename
	    set gCommandsIndex($filename) $dirname
	    set gDescsIndex($filename) $dirname
	}
	set fileid [open $dir/SNINDEX.TCH r]
	while {[gets $fileid line] >= 0} {
	    if [info exists gSectionsIndex([lindex $line 0])] {
		set gSectionsIndex([lindex $line 0]) \
			[lsort [concat $gSectionsIndex([lindex $line 0]) \
			    [lindex $line 1]]
	                ]
	    } else { set gSectionsIndex([lindex $line 0]) [lindex $line 1] }
	}
	close $fileid
	set fileid [open $dir/nodtofil.txt r]
	while {[gets $fileid line] >= 0} {
	    set gNodeToFile([lindex $line 0]) [lindex $line 1]
	}
	close $fileid
    }

    # Now read in the synopses files
    foreach file [lsort [glob $dataDir/sections/*/SYNOPSES.TXT]] {
	readArrayFromFile gSynopsesIndex $file
    }
}
#############################################################
# Tch Constant Values 
#############################################################

# TCheLp/TK understands that some types of option arguments have
# certain preferred or commonly used values. The gaOptArgVals
# array keeps track of this.
set gaOptArgVals(dist) {25 0.5c 0.25i}
set gaOptArgVals(anchorPoint) {n e s w ne nw se sw center}
set gaOptArgVals(side) {left right top bottom}
set gaOptArgVals(fstyle) {x y both}
set gaOptArgVals(cursor) {arrow}
set gaOptArgVals(justification) {left right center}
set gaOptArgVals(orientation) {vertical horizontal}
set gaOptArgVals(relief) {sunken groove flat raised ridge}
set gaOptArgVals(wrapstyle) {none char word}
set gaOptArgVals(state) {normal disabled}

foreach name [array names gaOptArgVals] {
	menu .popup$name -tearoff 0
	foreach entry $gaOptArgVals($name) {
		.popup$name add command -label $entry
	}
}

######################################################################
# Tch TEXT UTILITIES
#####################################################################

# some fonts . . .
set gaFont(Times10) "-*-times-medium-r-*-*-*-100-*-*-*-*-*-*"
set gaFont(Times12) "-*-times-medium-r-*-*-*-120-*-*-*-*-*-*"
set gaFont(Times14) "-*-times-medium-r-*-*-*-140-*-*-*-*-*-*"
set gaFont(Times18) "-*-times-medium-r-*-*-*-180-*-*-*-*-*-*"
set gaFont(Times24) "-*-times-medium-r-*-*-*-240-*-*-*-*-*-*"
set gaFont(TimesB10) "-*-times-bold-r-*-*-*-100-*-*-*-*-*-*"
set gaFont(TimesB12) "-*-times-bold-r-*-*-*-120-*-*-*-*-*-*"
set gaFont(TimesB14) "-*-times-bold-r-*-*-*-140-*-*-*-*-*-*"
set gaFont(TimesB18) "-*-times-bold-r-*-*-*-180-*-*-*-*-*-*"
set gaFont(TimesB24) "-*-times-bold-r-*-*-*-240-*-*-*-*-*-*"
set gaFont(TimesI10) "-*-times-medium-i-*-*-*-100-*-*-*-*-*-*"
set gaFont(TimesI12) "-*-times-medium-i-*-*-*-120-*-*-*-*-*-*"
set gaFont(TimesI14) "-*-times-medium-i-*-*-*-140-*-*-*-*-*-*"
set gaFont(TimesI18) "-*-times-medium-i-*-*-*-180-*-*-*-*-*-*"
set gaFont(TimesI24) "-*-times-medium-i-*-*-*-240-*-*-*-*-*-*"
set gaFont(TimesBI10) "-*-times-bold-i-*-*-*-100-*-*-*-*-*-*"
set gaFont(TimesBI12) "-*-times-bold-i-*-*-*-120-*-*-*-*-*-*"
set gaFont(TimesBI14) "-*-times-bold-i-*-*-*-140-*-*-*-*-*-*"
set gaFont(TimesBI18) "-*-times-bold-i-*-*-*-180-*-*-*-*-*-*"
set gaFont(TimesBI24) "-*-times-bold-i-*-*-*-240-*-*-*-*-*-*"
set gaFont(Courier10) "-*-courier-medium-r-*-*-*-100-*-*-*-*-*-*"
set gaFont(Courier12) "-*-courier-medium-r-*-*-*-120-*-*-*-*-*-*"
set gaFont(Courier14) "-*-courier-medium-r-*-*-*-140-*-*-*-*-*-*"
set gaFont(Helvetica10) "-*-helvetica-medium-r-*-*-*-100-*-*-*-*-*-*"
set gaFont(Helvetica12) "-*-helvetica-medium-r-*-*-*-120-*-*-*-*-*-*"
set gaFont(Helvetica14) "-*-helvetica-medium-r-*-*-*-140-*-*-*-*-*-*"
set gaFont(HelveticaI10) "-*-helvetica-medium-o-*-*-*-100-*-*-*-*-*-*"
set gaFont(HelveticaI12) "-*-helvetica-medium-o-*-*-*-120-*-*-*-*-*-*"
set gaFont(HelveticaI14) "-*-helvetica-medium-o-*-*-*-140-*-*-*-*-*-*"
set gaFont(HelveticaB10) "-*-helvetica-bold-r-*-*-*-100-*-*-*-*-*-*"
set gaFont(HelveticaB12) "-*-helvetica-bold-r-*-*-*-120-*-*-*-*-*-*"
set gaFont(HelveticaB14) "-*-helvetica-bold-r-*-*-*-140-*-*-*-*-*-*"
set gaFont(HelveticaBI10) "-*-helvetica-bold-o-*-*-*-100-*-*-*-*-*-*"
set gaFont(HelveticaBI12) "-*-helvetica-bold-o-*-*-*-120-*-*-*-*-*-*"
set gaFont(HelveticaBI14) "-*-helvetica-bold-o-*-*-*-140-*-*-*-*-*-*"

# Set up generic fonts. A generic font name has the following components:
#     NameBoldstyleItalicstyleSize
# The name of a generic font is not an actual font name, but a "generic"
# name defined below, which is mapped to an actual font name by the 
# gaGenericToFont array. Size is one of S, M, L, X, for small, medium,
# large, and extra-large. Boldstyle is either B or empty, depending 
# on whether the font should be bold, and ItalicStyle is either I or empty,
# depending on whether the font should be italicized. The "default" font
# is considered to be NormalM.

proc makeGenericFonts {names sizes} {
    global gaFont gaGFont

    array set genericToFont $names
    array set fontSizes $sizes
    foreach family [array names genericToFont] {
	foreach size [array names fontSizes] {
	    foreach bstyle {{} B} {
		foreach istyle {{} I} {
		    set fontname $genericToFont($family)$bstyle$istyle$fontSizes($size)
		    if [info exists gaFont($fontname)] {
			set gaGFont($family$bstyle$istyle$size) $gaFont($fontname)
		    }
		}
	    }
	}
    }
}

proc makeSemanticFonts {mappings} {
    global gaGFont gaSFont

    array set sToG $mappings
    foreach size {S M L X} {
	foreach name [array names sToG] {
	    if [info exists gaGFont($sToG($name)$size)] {
		set gaSFont($name$size) $gaGFont($sToG($name)$size)
	    }
	}
    }
}

# Define colours
array set gaColours {dimText gray50 cBackground gray90 cLightGray gray95 cButtonGray gray91 cMedGray gray80}

# The gaTextOpts array holds strings that may be used as options
# when creating text widgets, or configuring text within text widgets.
# each string may specify multiple options and values, and hence
# should be integrated into the command to be executed with a "concat"
# command. Strings which may be used only when creating text widgets
# begin with "w"; options which may be used on tagged text start with
# "t"; and strings which may be used in either instance have no
# prefix.

# Depending on the bit depth of the monitor, set values for 
# some font styles.
if {[winfo depth .] > 1} {
	set fbkHilite "-relief raised \
		-borderwidth 1"
	set fbkNormal "-background {} -relief flat"
} else {
	set fbkHilite "-foreground white -background black"
	set fbkNormal "-foreground {} -background {}"
}

set fbkRaised "-relief raised -borderwidth 1"

# The primary purpose of the utilities in this file is to rapidly
# create formatted text widgets, i.e. text widgets whose text can contain
# bold, italic, etc. The main intent is to be able to display short help
# documents "on the fly".

# The insertFText procedure takes a list of strings and a text widget,
# and inserts the strings into the text widget, doing certain processing
# in the process so as to introduce formatting. Any string that does
# NOT begin with a "^" is inserted into the widget; any string that DOES
# begin with a "^" may undergo further processing. insertFText may be used
# by itself, in which case the user should be sure that the strings arg
# is in a satisfactory format w.r.t. newlines, spaces, and the like.
#
# The meaning of a line beginning with "^" will be determined by the
# remainder of the line, as follows. Generally, the meaning is determined
# by a 'command name' delimited by ^'s, with the line after the last ^ (if
# any) serving as data.
#
#    ^P^ -- (paragraph) insert a newline into the text widget.
#    ^T^ -- insert a tab into the text widget.
#    ^G^ -- (taG) the line after the .G and up to the next space is
#          tag--insert the remainder of the line after it with that tag name.
#    ^#^ -- comment--the line will be discarded
#
# The meanings of the different font styles, such as Bold, Italic, or
# Strong, are not graven in stone. Instead, the "fonts" array contains
# one entry for every character indicating a font style (i.e. B, I, etc.).
# This entry consists of a string which will be interpolated into a call
# to the text insertion command.  Generally, this string will be of the
# form "-font FONTNAME", but since it can contain any of the options for
# insertion into a text widget, it can also define foreground, background,
# etc.

proc insertFText {widget strings {fonts fStyles}} {
    global $fonts

    foreach string $strings {
	# If this is a special line . . .
	if [regexp {^\^([^^]*)\^(.*)$} $string match char text] {
	    # If there is an appropriate string in the fonts array,
	    # tag the text to that effect, so that it can print using 
	    # the options given by that string.
	    if [info exists ${fonts}($char)] {
		$widget insert end $text styleTag$char
	    } else {
		# Otherwise, try to find an appropriate command to follow
		switch $char {
		    P {$widget insert end "\n"; $widget insert end $text}
		    T {$widget insert end "\t"; $widget insert end $text}
		    G {if [regexp {^([^ ]*) (.*)$} $text match tag text] {
			$widget insert end $text $tag
		    } else {
			$widget insert end $text
		    }
		    }
		    '#' {}
		    # If no match, just print the text
		    default {$widget insert end $text}
		}
	    }
	} else {
	    # just print strings which don't begin with a '.'
	    $widget insert end $string
	}
    }
    foreach style [array names $fonts] {
	set tmp [list $widget tag configure styleTag$style]
	eval "$tmp [expr \$${fonts}($style)]"
    }
}

###########################################################################
# READFORM procs
##################################################################

# Read and process a form file for a tcl/tk command. A form file consists
# of a form header, describing how the form should appear, followed by at
# least one blank line, followed by any number of sections describing
# the commands options, each option section separated from the next
# by at least a blank line.
#
# The form header is composed of keywords (denoted by plain strings), 
# variables (which begin with a $), and options (which begin with a -).
# Argument names associated WITH an option are NOT included in the
# form header file--they are defined in the option section for that
# option, so that TCheLp/TK knows to associate them with the option.
# (This could have probably been avoided, but it makes things simpler
# and clearer.)
#
# Tabs and newlines in the form header will be used in the
# onscreen formatting.
#
# Keywords will appear to the user as bolded words which cannot be
# changed or unselected.  Variables defined in the form header will
# appear as italicized words which can be changed but not unselected.
# Options will appear as bold words which can be selected or unselected
# but not changed--they are grey when unselected (default) and black
# when selected. Their associated arguments will appear as italicized
# words which can be changed, and which are selected or unselected 
# as the option is.
#
# An option section consists of a line with the name of the option and
# argument names (if any), and any number of lines which describe the
# option and its argument. Argument names may be reused in the same
# form file.

# As it turns out, we may wish to read forms from a particular
# directory, and not just a particular node, so "readForm" is just
# a shell that figures out a directory from a node path, and then
# calls the actual reader routine--this permits other procs to
# call the reader routine directly with a dir path.

proc readForm {path} {

    readFormFromFile [nodePathToDirPath $path]
}

proc readFormFromFile {formFile} {
    global gaOptionArgs gaOptionHelp gaVarNames

    set form {}

    if [file exists $formFile] {
	# Because variable names may be reused in a form
	# file (they're really just placeholders), we need
	# a better way to track them. Every variable defined
	# in the option sections will be assigned a unique 
	# number, and the gaVarNames array will map that
	# number back to the variable name.
	if [info exists gaVarNames] {unset gaVarNames}
	set varCount 0
	
	set fileid [open $formFile]
	
	# get the form header
	while {[gets $fileid line] > 0 && \
		![regexp "^\[ \t\n\]*\$" $line match]} {
	    # replace tab characters with <TAB> and 
	    # newlines with <CR>. This just makes pattern
	    # matching easier later on.
	    regsub -all "\t" $line <TAB> line
	    set form [concat $form $line {<CR>}]
	}
	
	# Now process the section of the form that describes the options.
	while {[gets $fileid line] > 0} {
	    # Ignore blank lines
	    if {[regexp "^\[ \t\n\]*\$" $line match]} {
		continue
	    } else {
		# the line we just read was following a blank,
		# so it must be the first line of an option section.
		# read and store the option and its args.
		set option [lindex $line 0]
		set args [lrange $line 1 end]
		set newArgs {}
		foreach arg $args {
		    set gaVarNames($varCount) $arg
		    lappend newArgs $varCount
		    incr varCount
		}
		set gaOptionArgs($option) $newArgs
	    }
	    # Now read the description for the option, and store it.
	    # We want the description to start with a heading, which
	    # will be just the name of the option.
	    set help [list "^H^$option" "^P^"]
	    while {[gets $fileid line] > 0 && \
		    ![regexp "^\[ \t\n\]*\$" $line match]} {
		lappend help $line
	    }
	    set gaOptionHelp($option) $help
	}
	close $fileid
    }
    return $form
}

# readLibForm reads the form that is (usually) present at the top
# level in most lib docs.  It is exactly like a regular form EXCEPT
# that the form header (which describes the "form" of the command)
# is missing. Thus, a lib form starts with a blank line, and gives
# a series of options and associated descriptions. The intent is
# that these options are "commonly used" throughout the library, 
# and therefore should be remembered no matter what command is being
# displayed. (A command form may make use of any of these "common options"
# by using them in its form header, without describing them in its list
# of options/descriptions.
#
# readLibForms reads in the top-level options for all of the doc libs,
# if said form file 
# exists. 

proc readLibForms {} {
    global gDataDir gaOptionArgs gaOptionHelp gaVarNames

    foreach libdir [glob $gDataDir/sections/*/] {
	set libname [mytail $libdir]
	if [file exists ${libdir}form] {
	    # get rid of previous contents of global option arrays
	    foreach name {OptionArgs OptionHelp VarNames} {
		if [info exists ga$name] { unset ga$name }
	    }

	    # read in this lib's form file, then copy it to the
	    # appropriate global array
	    readFormFromFile ${libdir}form
	    foreach name {OptionArgs OptionHelp VarNames} {
		# Don't need to make the gaLib... array global because
		# that's handled by copyArray.
		copyArray ga$name gaLib$libname$name
		if [info exists ga$name] {unset ga$name}
	    }
	}
    }
}

##################################################################
# OPTIONS procs
#################################################################
# Given the argument name for an option arg, check to see if it has
# any preferred or standard values in gaOptArgVals. If it does, then
# return the name of the menu (created at startup) containing these
# values, else return 0

proc checkOptArgVals {name} {

    global gaOptArgVals
    
    set base 0
    if [info exists gaOptArgVals($name)] {
	set base $name
    }
    if [regexp {^([a-zA-Z0-9]+)[-_]([a-zA-Z0-9])(...)?$} \
	    $name match first last ellipses] {
	if [info exists gaOptArgVals($last)] {
	    set base $last
	} elseif [info exists gaOptArgVals($first)] {
	    set base $first
	}
    }
    if {$base != 0} {
	return .popup$base
    } else {return 0}
}

proc showOptions {path} {

    global gCommandsIndex

    if [info exists gCommandsIndex($path)] {
	set lib $gCommandsIndex($path)
    } else { set lib {} }
	
    global gOptionsWin gaOptions gaOptionArgs gaOptionHelp gCommandPath \
	    gaVarNames gInName gInVar \
	    gaLib${lib}OptionHelp gaLib${lib}OptionArgs gaLib${lib}VarNames
		
    global gaGFont gaSFont

    # Make sure the arrays used for containing options local to this command
    # are cleared out before we create the contents of the options window
    foreach name {gaOptionArgs gaOptionHelp gaVarNames} {
	if [info exists $name] {unset $name}
    }
	
    $gOptionsWin configure -state normal
    $gOptionsWin delete 0.0 end
    set form [readForm $path]
    while {![regexp {^[ \t\n]*$} $form match]} {
	# Process subheadings
	if [regexp {^#([^#]*)#(.*)$} $form match sub form] {
	    $gOptionsWin insert end "  $sub" subheading
	    continue
	}
	
	# Process <TAB>s.
	if [regexp {^<TAB>(.*)$} $form match form] {
	    $gOptionsWin insert end "\t"
	    continue
	}
	# Proces <CR>s
	if [regexp {^<CR>(.*)$} $form match form] {
	    $gOptionsWin insert end "\n"
	    continue
	}
	# get rid of whitespace--probably not necessary,
	# but just to be safe  . . .
	if [regexp {^ +(.*)$} $form match form] {continue}
	
	# check for a keyword/variable name--leading $ means
	# it's a variable, trailing + means insert ellipses
	# to indicat arg may be repeated
	if [regexp {^(\$?)([a-zA-Z0-9_\-\.]+(\.\.\.)?)(.*)$} \
		$form match lead name trail form] {
	    
	    # It's a variable
	    if [string match $lead {$}] {
		$gOptionsWin insert end "$name " variable
		$gOptionsWin tag configure variable \
			-font $gaSFont(VariableLightM)
		# It's a keyword
	    } else {
		$gOptionsWin insert end "$name " keyword
		$gOptionsWin tag configure keyword \
			-font $gaSFont(KeywordM)
	    }
	    continue
	}

	# Process actual options, which begin with a "-"
	if [regexp {^(-[-a-zA-Z0-9]+)(.*)} $form match option form] {
	    $gOptionsWin insert end "$option " "$option ${option}name"
	    $gOptionsWin tag bind ${option}name <Enter> \
		    "set gInName 1; showOption $option"
	    $gOptionsWin tag bind ${option}name <Leave> \
		    "set gInName 0; showOption $option"
	    
	    if {![info exists gaOptions($option)]} {set gaOptions($option) 0}
	    
	    if [info exists gaOptionArgs($option)] {
		set arg $gaOptionArgs($option)
		if [info exists gaVarNames($arg)] {
		    set varName $gaVarNames($arg)
		} else {set varName {} }
	    } else {
		set arg [libArrayElem $lib OptionArgs $option]
		set varName [libArrayElem $lib VarNames $arg]
	    }
	    $gOptionsWin insert end "$varName " \
		    "$option ${option}var ${option}var$arg"
	    $gOptionsWin tag bind ${option}var <Enter> \
		    "set gInVar 1; showOption $option"
	    $gOptionsWin tag bind ${option}var <Leave> \
		    "set gInVar 0; showOption $option"

	    $gOptionsWin tag bind $option <Enter> "+showOptionHelp $option"
	    $gOptionsWin tag bind ${option}name <1> "clickOption $option %X %Y"
	    $gOptionsWin tag bind ${option}var <1> "clickOption $option %X %Y"
	    $gOptionsWin tag bind $option <Leave> "+showCommandHelp $gCommandPath"
	    showOption $option
	    continue
	}
	break
    }
    $gOptionsWin tag configure subheading -font $gaGFont(NormalIM) \
	    -foreground blue
    $gOptionsWin configure -state disabled
    set gInName 0
    set gInVar 0
}

# This is just a helper function; given the name of a doc lib, a particular
# array, and the elem in that array to look for, this proc returns the
# sought-after element by building the array name according to tch's
# internal naming convention, and then accessing the desired elem.

proc libArrayElem {lib array elem} {
    set arrayName "gaLib${lib}$array"
    global $arrayName
    return [expr "\$${arrayName}(\$elem)"]
}

proc traceOptionsArrayR {name elem op} {

	global $name

	if {![info exists ${name}($elem)]} {
		set ${name}($elem) 0
	}
}

proc showOption {option} {
    global gOptionsWin gaOptions gaOptArgVals gInName gInVar \
	    gaColours gaGFont gaSFont gaColours

    set backgray {}
    set lightgray $gaColours(cButtonGray)

    if {$gaOptions($option)} {
	set colour black
    } else {
	set colour $gaColours(dimText)
    }
    # For some reason just using the $option tag isn't working, so
    # sometimes I have to repeat the same command for ${option}name
    # and ${option}var . . .
    if {!$gInName && !$gInVar} {
	$gOptionsWin tag configure ${option}name -relief flat \
		-foreground $colour -font $gaSFont(KeywordM) -background $backgray
	$gOptionsWin tag configure ${option}var -relief flat \
		-foreground $colour -font $gaSFont(VariableLightM) -background $backgray
    } elseif {!$gaOptions($option)} {
	$gOptionsWin tag configure ${option}name -relief raised -borderwidth 1 \
		-foreground $colour -font $gaSFont(KeywordM) -background $lightgray
	$gOptionsWin tag configure ${option}var -relief raised -borderwidth 1 \
		-foreground $colour -font $gaSFont(VariableLightM) -background $lightgray
    } else {
	set optVarMenu [getOptVals $option]
	if {$optVarMenu == 0} {
	    $gOptionsWin tag configure ${option}name -relief raised \
		    -foreground $colour -font $gaSFont(KeywordM) -background $lightgray
	    $gOptionsWin tag configure ${option}var -relief raised \
		    -foreground $colour -font $gaSFont(VariableLightM) -background $lightgray
	} else {
	    if {$gInName} {
		set raised name
		set flat var
	    } else {
		set raised var
		set flat name
	    }
	    $gOptionsWin tag configure $option$raised -relief raised \
		    -foreground $colour -background $lightgray
	    $gOptionsWin tag configure $option$flat -relief flat \
		    -foreground $colour -background $backgray
	}
    }
}


# Get the name of the variable associated with on option. Because
# we allow duplicate variable names within a command, each var
# name is replaced with a number in the internal representation of
# the command, hence the need for this function. 

proc getOptVarName {option} {

    global gCurrentCommandLib
    set lib $gCurrentCommandLib

    global gaOptionArgs gaLib${lib}OptionArgs \
	    gaVarNames gaLib${lib}VarNames

    if [info exists gaOptionArgs($option)] {
	if [info exists gaVarNames($option)] {
	    return $gaVarNames($gaOptionArgs($option))
	} else {
	    return {} 
	}
    } else { 
	return [libArrayElem $lib VarNames \
	    [libArrayElem $lib OptionArgs $option]] 
    }
}


proc getOptVals {option} {
    
    return [checkOptArgVals [getOptVarName $option]]
}


proc clickOption {option x y} {
    global gaOptions gOptionsWin gCommandPath gaOptionArgs gaVarNames gInVar
    
    if {!$gaOptions($option)} {
	set gaOptions($option) [expr !$gaOptions($option)]
    } else {
	set optVarMenu [getOptVals $option]
	if {$optVarMenu != 0 && $gInVar} {
	    tk_popup $optVarMenu $x $y 0
	} else {
	    set gaOptions($option) [expr !$gaOptions($option)]
	}
    }
    showOption $option
}

##############################################################
# HELP (procs involved in showing help)
#############################################################

proc showLargeMessage {winName filename} {
    global gaGFont

    if [winfo exists $winName] { return }

    toplevel $winName

    frame $winName.bot -bd 2 -relief groove
    pack $winName.bot -side bottom -fill x -padx 5 -pady 5
    button $winName.bot.ok -text OK -command [list destroy $winName]
    pack $winName.bot.ok -anchor center -pady 5

    frame $winName.top
    pack $winName.top -fill both -expand 1 -padx 5 -pady 5
    scrollbar $winName.top.s -orient vert -command [list $winName.top.text yview]
    pack $winName.top.s -side right -fill y -anchor e
    text $winName.top.text -wrap word -font $gaGFont(NormalM) \
	    -yscrollcommand [list $winName.top.s set] -spacing3 10 \
	    -background white -tabs {1c 2c 3c} -selectborderwidth 0 \
	    -selectbackground white -highlightthickness 0
    pack $winName.top.text -fill both -expand 1 -pady 2
    insertFText $winName.top.text [readStrings $filename]
    $winName.top.text configure -state disabled

    tkwait window $winName
}

# This procedure reads in info from the file "$gDataDir/PKG.HLP", and
# leaves it in the global array gaPackageHelp. PKG.HLP consists of a
# set of _control definitions_, each separated from the previous by
# a single newline. The first line of each control definition should
# contain the _key_ under which this entry will be stored in gaPackageHelp.
# Any number of nonblank lines follow, which should be a piece of
# documentation as understood by the textutils tcl proc. This array
# is accessed by the showPackageHelp proc.

proc readPackageHelp {} {
    global gaPackageHelp gDataDir

    set helpfile $gDataDir/PKG.HLP

    if [file exists $helpfile] {
	set fileid [open $helpfile r]
	while {[gets $fileid line] > 0} {
	    # ignore blank lines, except as separators
	    if [regexp {^[ \t]*$} $line] {
	    } else {
		set name $line
		set doc {}
		while {[gets $fileid line] && ![regexp {^[ \t]*$} $line]} {
		    lappend doc $line
		}
		set gaPackageHelp($name) $doc
	    }
	}
    } else {
	puts "\nERROR: file $gDataDir/PKG.HLP not found"
    }
}

# Procedure which causes help for TCheLp/TK controls to be displayed 
# when the cursor is over those controls.

proc showPackageHelp {feature} {
    global gShowControlHelp gaPackageHelp
    
    if {!$gShowControlHelp} {return}
    if {![info exists gaPackageHelp($feature)]} {return}
    
    showHelp $feature $gaPackageHelp($feature)
}

proc showOptionHelp {option} {
    global gCurrentCommandLib
    set lib $gCurrentCommandLib

    global gaOptionHelp gaLib${lib}OptionHelp

    if [info exists gaOptionHelp($option)] {
	set help $gaOptionHelp($option)
    } else {
	set help [libArrayElem $lib OptionHelp $option]
    }
    showHelp $option $help
}

proc setHelpBinding {win feature} {

	global gCommandPath

	bind $win <Enter> "showPackageHelp $feature"
    bind $win <Leave> { if {$gShowControlHelp} {showCommandHelp $gCommandPath} }
}

# This procedure creates a text widget for use in the "Description" area
# of the main Tch window. It's important that all Description windows
# have an identical sizing, to avoid resizing of the window as one Description
# is substituted for another.
proc makeDescWidget {name {background white}} {
    global gaGFont gaSFont

    text $name -height 15 -state normal -background $background \
	-selectbackground white -selectborderwidth 0 \
	-wrap word -spacing3 10 \
	-font $gaGFont(NormalM) -relief flat -bd 0 -highlightthickness 0
}

# title is the title of the text, and will be used in the window name; 
# text is the text to be displayed.
proc showHelp {title text} {
    global gDescWin gDescText gDescScroll
    
    pack forget $gDescText
    set gDescText $gDescWin.help$title
    
    if {![getWinFromCache $gDescText gWinCache]} {
	makeDescWidget $gDescText white
	insertFText $gDescText $text fStyles
	$gDescText configure -state disabled
    }
    pack $gDescText -fill both -expand 1 -padx 5
    $gDescScroll configure -command [list $gDescText yview]
    $gDescText configure -yscrollcommand [list $gDescScroll set]
}

# showCommandHelp is responsible for displaying help text associated
# with entries in the index (commands, topics, etc.)
proc showCommandHelp {path} {
    global gDataDir gCommandsIndex

    set helpfile [descPathToDirPath $path]
    # If we are showing help for a command, add a 'Command' title at
    # the start of the help, otherwise don't.
    if [info exists gCommandsIndex($path)] {
	set desc [readStrings $helpfile]
    } else {
	set desc [readStrings $helpfile]
    }
    showHelp $path $desc
}


########################################################
# These are the procedures and code associated with 
# maintaining and using the "index window" in the tch
# application.
##################################################

# GLOBAL VARIABLES:
#    gIndexWin : the name of the index window
#    gSelectedNode : the currently selected node in the
#        index window, {} if none.
#    gActiveNode : the currently active (i.e. highlighted to indicate
#        a click will select) node in the index win, {} if none. The value
#        of gActiveNode is always the node that would be selected if the
#        active node were to be clicked (or whatever) on. Thus, if a
#        subheading is active, the full node name of that subheading is
#        in gActiveNode, but if a bold headings is active, the node name
#        "one less" than associated with that entry is in gActiveNode.
#    gIndexSubheadings : an ordered list of the subheadings shown in the 
#        index window.
#    gActiveTag : the tag of the index win entry which is the active
#        entry.
#    gaTagsToNodes : each element of gIndexWin will have a numeric tag
#        corresponding to its position (starting with 0). This array
#        maps the tag numbers to the (full) node name which would be 
#        selected if the corresponding item were selected.

set gActiveTag {}

# This procedure sets the current "active node"--this is not the
# node which is selected, but the node which is chosen in the sense
# that a mouse button click or right arrow press will make it the 
# selected node. When all elements in the index win become inactive, 
# this proc will be passed "".

proc setActiveNode {tag} {
    global fbkHilite fbkNormal gActiveTag gIndexWin gaTagsToNodes \
	    gSynopsesIndex gSelectedNode gaLastActiveTagForNode fbkNormal fbkHilite

    #make the last active text flat
    eval [concat [list $gIndexWin tag configure $gActiveTag] $fbkNormal]

    eval [concat [list $gIndexWin tag configure $tag] $fbkHilite]
    if [info exists gaTagsToNodes($tag)] {
	set node $gaTagsToNodes($tag)
	if [info exists gSynopsesIndex($node)] {
	    showSynopsis $node $gSynopsesIndex($node)
	}
    }

    set tagRanges [$gIndexWin tag ranges $tag]
    if {[llength $tagRanges] > 0} {
	$gIndexWin see [lindex $tagRanges 0]
    }

    set gActiveTag $tag
    set gaLastActiveTagForNode($gSelectedNode) $tag
}

set gSynopsisCount 0

proc showSynopsis {title text} {
    global gSynopsisCount gDescWin gDescText gOldDescText gaGFont

    set synopsis $gDescWin.synopsis
    incr gSynopsisCount

    if {![winfo exists $synopsis]} {
	makeDescWidget $synopsis white
    }

    # If the current description text _isn't_ a synopsis, remember
    # it for a possible future restore.
    if {$gDescText != $synopsis} {
	set gOldDescText $gDescText
	pack forget $gDescText
	pack $synopsis -padx 5 -fill both -expand 1
	set gDescText $synopsis
    }

    $synopsis configure -state normal
    $synopsis delete 0.0 end
    $synopsis insert end "Synopsis: " title "$title\n" title
    $synopsis tag configure title -font $gaGFont(NormalBL)
    $synopsis insert end $text
    $synopsis configure -state disabled
    after 2000 {incr gSynopsisCount -1; hideSynopsis}
}

# Hide the last synopsis, if it is still showing, and restore the
# previous description window.

proc hideSynopsis {} {
    global gSynopsisCount gDescText gOldDescText gDescWin

    set synopsis $gDescWin.synopsis
    # We only perform this function if gSynopsisCount indicates
    # no other synopses have been displayed since the one associated
    # with this call, and the synopsis window is still being displayed.
    if {$gSynopsisCount == 0 && [string compare $synopsis $gDescText] == 0 } {
	pack forget $synopsis
	set gDescText $gOldDescText
	pack $gDescText -padx 5
    }
}

set gDisableIndexMouse 0

proc mouseSetActive {tag} {
    global gDisableIndexMouse
    if {$gDisableIndexMouse == 0} {setActiveNode $tag}
}


# Given a path to a COMMAND in the documentation tree, convert that
# into a directory path, representing the directory in which the
# info for that documentation node is stored.

proc nodePathToDirPath {path} {

    global gDataDir gCommandsIndex gNodeToFile

    if [info exists gNodeToFile($path)] {
	set file $gNodeToFile($path)
	if [info exists gCommandsIndex($file)] {
	    set lib $gCommandsIndex($file)
	    return "$gDataDir/sections/$lib/data/$file.frm"
	} else { return {} }
    } else { return {} }
}

proc descPathToDirPath {path} {

    global gDataDir gDescsIndex gNodeToFile

    if [info exists gNodeToFile($path)] {
	set file $gNodeToFile($path)
	if [info exists gDescsIndex($file)] {
	    set lib $gDescsIndex($file)
	    return "$gDataDir/sections/$lib/data/$file.dsc"
	} else { return {} }
    } else { return {} }
}

# Given a path to a node of the documentation tree, find all
# of the subnodes of that node, in all doc libs.

proc subNodes {path} {

    global gSectionsIndex

    # if the path is the empty string, then we do a search on "/",
    # which is the name the toplevel subnodes are stored under
    # in gSectionsIndex

    # If the gSectionsIndex array does not have an entry for
    # $path, then it is (or at least, should be) the name of
    # a command with no subcommands, so just return an empty
    # string, otherwise return the array value.
    
    # Otherwise, we return the found set of subnodes

    if [string match {} $path] {
	return [lsort $gSectionsIndex(/)]
    } elseif {![info exists gSectionsIndex($path)]} {
	return ""
    } else { 
	return [lsort $gSectionsIndex($path)] 
    }
}

# Given the position tag (starting from 0) of an index win entry which
# has just been selected, perform the necessary steps to carry out
# the selection

proc selectEntry {tag} {
    global gIndexWin gaTagsToNodes gActiveTag gSelectedNode gaLastActiveTagForNode

    $gIndexWin tag configure $tag -relief flat
    setIndex $gaTagsToNodes($tag) $gIndexWin
    if [info exists gaLastActiveTagForNode($gSelectedNode)] {
	setActiveNode $gaLastActiveTagForNode($gSelectedNode)
    } else {
	setActiveNode {}
    }
}

# An index, in this context, is a partial path (list of strings) into a
# tree, together with a list of subtree names (list of strings), one
# of which may be chosen to descend one further level in the tree.

# showIndex, given a partial path, subtree list, and text window, 
# displays the appropriate text window permitting the user to choose
# a subtree or go back up the tree, to descend via another subpath.

proc showIndex {path subheadings indexWin} {
    global gCommandPath gCommandsIndex gaTagsToNodes gUpTag gIndexWin
    global gaGFont gaSFont fbkNormal fbkHilite

    # Get ready for the new set of tags.
    if [info exists gaTagsToNodes] { unset gaTagsToNodes }
    set tagCount 0

    set gCommandPath $path

    $gIndexWin configure -state normal
    eval "$gIndexWin tag configure item $fbkNormal"
    $gIndexWin delete 0.0 end

    # If this command (if it is a command) is not from
    # one of the "standard libraries" (those whose directories
    # are prefixed with numbers in the range 20-39), then we
    # wish to include the library name in the displayed text.
    # Here, we figure out what string to include with the
    # displayed name.
    set lib ""
    if [info exists gCommandsIndex($path)] {
	if [regexp {^[^2-3][0-9](.*)$} $gCommandsIndex($path) match lib] {
	    set lib " ($lib)"
	}
    }

    # In order to uniformly perform all selections through the use
    # of tag numbers, we need to know which tag number will be
    # associated with the list entry which takes us "one level up".
    # This info is used for the binding for the left arrow key.
    set gUpTag [expr [llength $path] - 1]

    # Insert each of the elements of path. Use 'for' rather than
    # 'foreach' because we need to keep track of indentation levels.
    for {set i 0} {$i < [llength $path]} {incr i} {

        # insert proper tabbing for the next entry
	for {set tabs 1} {$tabs <= $i} {incr tabs} {
	    $gIndexWin insert end "\t" $tagCount
	}

	# We wish to insert the lib string only if this is the
	# last element of the display path
	if {$i == [llength $path] - 1} {
	    set libstr $lib
	} else {
	    set libstr ""
	}

	$gIndexWin insert end "[lindex $path $i]$libstr\n" $tagCount
	$gIndexWin tag configure $tagCount -font $gaGFont(NormalBM)

	# Normal behaviour is to descend one level in the path by
	# selecting a subheading.  However, if we select the current
	# heading (where we already are), then go UP one level in
	# the path.
        $gIndexWin tag bind $tagCount <Enter> [list mouseSetActive $tagCount]
	$gIndexWin tag bind $tagCount <1> [list selectEntry $tagCount]

	# write the appropriate value to the TagsToNodes array
	set gaTagsToNodes($tagCount) [lrange $path 0 [expr $i - 1]]
        incr tagCount
    }
    if {[llength $subheadings] == 0} {
	for {set tabs 1} {$tabs <= $i} {incr tabs} {
	    $gIndexWin insert end "\t"
	}
	$gIndexWin insert end "(No subcommands)"
    }
    foreach sh $subheadings {
	for {set tabs 1} {$tabs <= $i} {incr tabs} {
	    $gIndexWin insert end "\t" [list $tagCount $sh item]
	}
	set ellipses {}
	if {[llength [subNodes $sh]] > 0} {
	    set ellipses " . . ."
	}
		    
	$gIndexWin insert end "$sh$ellipses\n" $tagCount
	$gIndexWin tag configure $tagCount -font $gaGFont(NormalM)
        $gIndexWin tag bind $tagCount <Enter> \
		[list mouseSetActive $tagCount]
	
	# If we don't unbind existing <Leave> events,
	# then funny things happen, because the deletion
	# of text that happened at the beginning of this
	# routine will force a <Leave> after the command
	# for the calling <1> event has been processed.
	# (The text underneath the cursor has been taken
	# out from under it, so in some sense the cursor
	# has left the text.)
	$gIndexWin tag bind $tagCount <1> [list selectEntry $tagCount]
        set gaTagsToNodes($tagCount) $sh
        incr tagCount
    }
    $gIndexWin configure -state disabled
}

proc setIndex {path indexWin} {
    global gSelectedNode gCurrentCommandLib gCommandsIndex gIndexSubheadings
    set gSelectedNode $path
    if [info exists gCommandsIndex($path)] {
	set gCurrentCommandLib $gCommandsIndex($path)
    } else {
	set gCurrentCommandLib {}
    }

    set gIndexSubheadings [subNodes $path]
    showIndex $path $gIndexSubheadings $indexWin
    showOptions $path
    showCommandHelp $path
}

proc makeIndex {inWindow} {
    global gaGFont gaSFont

    pack $inWindow -side left -fill y
    label $inWindow.label -text "Index:"
    setHelpBinding $inWindow.label Index
    pack $inWindow.label -side top -anchor w
    text $inWindow.text -width 20 -height 10 -tabs {0.5c} -font $gaGFont(NormalM) \
	    -yscrollcommand "$inWindow.scroll set" -cursor top_left_arrow \
	    -state disabled -wrap none
    pack $inWindow.text -side left -fill y
    bind $inWindow.text <Leave> [list setActiveNode {}]
    scrollbar $inWindow.scroll -command "$inWindow.text yview"
    pack $inWindow.scroll -fill y -expand 1
    
    # return the pathname to the text window just created for
    # the index.
    return "$inWindow.text"
}


#################################################################
# MAIN.TCL
################################################################

proc makeMain {inWindow} {

    global gIndexWin gOptionsWin gDescWin gDescText gDescScroll \
	    gaColours gaGFont gUserPrefVars gaOptionsWinSizes gDataDir
    
    # Main Window structure:
    # inWindow: frame
    #   menu: menu
    #	top: frame
    #		text: text
    #	main: frame
    #		index: frame
    #		options: frame
    #			label: label
    #			scroll: scrollbar
    #			text: text
    #		descFrame: frame
    #			descLabel: label
    #			desc: frame
    #				text: text
    #			scroll: scrollbar
    #	bottom: frame
    #		prefs: button
    #		copy: button
    #		save: button
    #		exit: button
    
    #######################
    # MENUS
    #######################
    set menu $inWindow.menu
    set actions $menu.actions
    set prefs $menu.prefs
    set about $menu.about
    set actmenu $actions.menu
    set prefsmenu $prefs.menu
    set aboutmenu $about.menu

    frame $menu -bd 2 -relief raised
    pack $menu -side top -fill x
        menubutton $actions -text Actions -menu $actmenu
        setHelpBinding $actions Actions
        pack $actions -side left
        menu $actmenu -tearoff 0
            $actmenu add command -label "Quit  " -accelerator Alt-q -command quittch
        menubutton $prefs -text Prefs -menu $prefsmenu
	setHelpBinding $prefs Prefs
	pack $prefs -side left

	global gFontSize
	global gSyntaxWinSize
	global gShowControlHelp

	menu $prefsmenu -tearoff 0
	    $prefsmenu add radiobutton -label "Small Fonts" \
		-variable gFontSize -value small
	    $prefsmenu add radiobutton -label "Medium Fonts" \
		-variable gFontSize -value medium
	    $prefsmenu add radiobutton -label "Large Fonts" \
		-variable gFontSize -value large
	    $prefsmenu add separator

            array set gaOptionsWinSizes {small 3 medium 7 large 15 xlarge 22}
            trace variable gSyntaxWinSize w {resizeOptionsWin $gSyntaxWinSize}
	    $prefsmenu add radiobutton -label "Small Options Win " \
		-variable gSyntaxWinSize -value small -accelerator "Alt-s"
	    $prefsmenu add radiobutton -label "Medium Options Win  " \
		-variable gSyntaxWinSize -value medium -accelerator "Alt-m"
	    $prefsmenu add radiobutton -label "Large Options Win " \
		-variable gSyntaxWinSize -value large -accelerator "Alt-l"
	    $prefsmenu add radiobutton -label "XLarge Options Win " \
		-variable gSyntaxWinSize -value xlarge -accelerator "Alt-x"
	    $prefsmenu add separator
	    $prefsmenu add checkbutton -label "Show Control Help" \
		-variable gShowControlHelp -onvalue 1 -offvalue 0

	menubutton $about -text About -menu $aboutmenu
	setHelpBinding $about About
	pack $about -side right
	menu $aboutmenu -tearoff 0
	    $aboutmenu add command -label "Help..." \
		-command [list showLargeMessage .intro $gDataDir/HELP]
	    $aboutmenu add command -label "About Tch..." \
		-command [list showLargeMessage .about $gDataDir/ABOUTTCH]
	    $aboutmenu add command -label "Terms of Use..." \
		-command [list showLargeMessage .copyright $gDataDir/CPYRIGHT]

    ##########################
    # MAIN WINDOW
    ##########################

    # Main part of the window, contains the command index, options
    # window, help windows, etc.
    
    frame $inWindow.main
    pack $inWindow.main -side top -fill both -padx 5 -pady 5 -expand 1
    
    # INDEX WINDOW

    frame $inWindow.main.index
    pack $inWindow.main.index -side left -fill y
    set gIndexWin [makeIndex $inWindow.main.index]
    
    # ARGUMENTS AND OPTIONS WINDOW
    
    set optionsWin $inWindow.main.options
    set optionsScroll $optionsWin.scroll
    frame $optionsWin
    pack $optionsWin -side top -fill x

    set optionsLabel $optionsWin.label
    label $optionsLabel -text "Arguments and Options:"
    pack $optionsLabel -side top -anchor w
    setHelpBinding $optionsLabel Options

    set gOptionsWin $optionsWin.text
    text $gOptionsWin -width 50 -height 10 \
	    -state disabled -cursor top_left_arrow \
	    -yscrollcommand "$optionsScroll set" -wrap word \
	    -tabs {0.5c 1c 1.5c 2c 2.5c} \
	    -selectbackground $gaColours(cBackground) -selectborderwidth 0 \
	    -exportselection 0
    pack $gOptionsWin -side left -fill x -expand 1
    
    scrollbar $optionsScroll -command "$gOptionsWin yview"
    pack $optionsScroll -side right -fill y

    # DESCRIPTION WINDOW

    set descFrame $inWindow.main.descFrame
    frame $descFrame
    pack $descFrame -fill both -side bottom -expand 1 -anchor s

    set gDescWin $descFrame.desc
    set gDescText $descFrame.text
    set gDescScroll $descFrame.scroll
    set descLabel $descFrame.descLabel

    frame $descLabel
    pack $descLabel -side top -anchor nw -fill x
    label $descLabel.txt -text \
	    "Description: (Use Shift-<Arrow> to scroll)"
    pack $descLabel.txt -side left -anchor w
    setHelpBinding $descLabel.txt Description

    scrollbar $gDescScroll -command [list $gDescText yview]
    pack $gDescScroll -side right -fill y

    frame $gDescWin -background white -bd 2 -relief sunken
    pack $gDescWin -side left -fill both -padx 2 -pady 2 -expand 1
    makeDescWidget $gDescText
    $gDescText configure -yscrollcommand [list $gDescScroll set]
    pack $gDescText -side left -fill both -expand 1 -padx 5

    # SELECTION WINDOW -- this widget is never displayed, it is
    # used simply to build the selection that is exported to 
    # requestion applications such as text editors.

    text .sel -wrap none -exportselection 1
    .sel insert end Hello
    selection own .sel
    
    # Set up bindings to the help window can be scrolled using keystrokes.
    # This is necessary because the help window may display info depending
    # on where in the options window the cursor is, so the cursor may not
    # be available to perform scrolling. Because the text in the windows is
    # not enables, the input focus doesn't move from .win, even in response
    # to clicks.
    bind all <Shift-Up> {$gDescText yview scroll -1 pages}
    bind all <Page_Up> {$gDescText yview scroll -1 pages}
    bind all <Shift-Down> {$gDescText yview scroll 1 pages}
    bind all <Page_Down> {$gDescText yview scroll 1 pages}
    bind all <Shift-Left> {$gDescText yview scroll -1 units}
    bind all <Shift-Right> {$gDescText yview scroll 1 units}
    
    bind all <Alt-a> {setIndex {} $gIndexWin}

    bind all <Alt-q> quittch

    bind all <Alt-s> {set gSyntaxWinSize small}
    bind all <Alt-m> {set gSyntaxWinSize medium}
    bind all <Alt-l> {set gSyntaxWinSize large}
    bind all <Alt-x> {set gSyntaxWinSize xlarge}

    bind all <Key-Up> {moveActiveNode -1}
    bind all <Key-Down> {moveActiveNode 1}
    bind all <Key-Right> {descendNode}
    bind all <Key-Left> {ascendNode}
}

# args are just the arguments supplied by the trace routine, and are
# irrelevant as far as we are concerned.
proc resizeOptionsWin {args} {
    global gOptionsWin gaOptionsWinSizes gSyntaxWinSize

    $gOptionsWin configure -height $gaOptionsWinSizes($gSyntaxWinSize)
}


##################################################################
# CACHE.TCL
##########################################################

# The procedures in this file implement "caches"--the basic idea is that
# for some data structures (such as windows), we can recreate the windows
# as necessary, but would prefer to keep a certain number of previously
# created windows around, since if they are rereferenced, this will be
# faster than recreating them.

# getWinFromCache

proc getWinFromCache {name cache} {

    set cbname ${cache}byWinName 
    set cbnum ${cache}byAccessNum 
    set cvar ${cache}Index

    global $cbname $cbnum gWinCacheSize $cvar

    # setup stuff
    if {![info exists $cvar]} {
	set $cvar 0
    }
    if {![info exists gWinCacheSize]} {
	set gWinCacheSize 10
    }

    incr $cvar
    set cvarVal [expr \$$cvar]

    # If the window is recorded in the cache, then no problem
    if [info exists ${cbname}($name)] {
	# get rid of this window's previous entry in 
	# cbnum
	set oldNum [expr \$${cbname}($name)]
	unset ${cbnum}($oldNum)
	set returnVal 1
    } else {
	set returnVal 0
    }

    # set the new index for the window
    set ${cbname}($name) $cvarVal
    set ${cbnum}($cvarVal) $name


    # If the cache is now too large, get rid of the oldest
    # window
    set oldNum [expr $cvarVal - $gWinCacheSize]
    if [info exists ${cbnum}($oldNum)] {
	set oldName [expr \$${cbnum}($oldNum)]
	destroy $oldName
	unset ${cbname}($oldName)
	unset ${cbnum}($oldNum)
    }

    return $returnVal
}


###########################################################
# COMMAND PROCEDURES -- procedures which are called in response
# to a key-driven binding, menu activation, button press, etc.
###########################################################

# Code to execute before shutting tch down.

proc quittch {} {
    global gPREFSDIR gUserPrefVars
    # Save the user's prefs.
    eval saveVars $gPREFSDIR/settings $gUserPrefVars
    flush stdout
    exit
}

# procs which move the active selection in the index window, intended to be
# bound to keypresses.
proc moveActiveNode {movement} {
    global gActiveTag gaTagsToNodes gDisableIndexMouse

    incr gDisableIndexMouse

    if [string match $gActiveTag ""] {
	setActiveNode 0
    } else {
	set nextTag [expr $gActiveTag + $movement]
	if [info exists gaTagsToNodes($nextTag)] {
	    setActiveNode $nextTag
	}
    }
    after 100 {incr gDisableIndexMouse -1}
}

proc descendNode {} {
    global gDisableIndexMouse gActiveTag

    if {[string length $gActiveTag] > 0} {
	incr gDisableIndexMouse
	selectEntry $gActiveTag
	after 100 {incr gDisableIndexMouse -1}
    }
}

proc ascendNode {} {
    global gUpTag gDisableIndexMouse

    if {$gUpTag > -1} { 
	incr gDisableIndexMouse
	selectEntry $gUpTag
	after 100 {incr gDisableIndexMouse -1}
    }
}


#######################################################################
######################################################################
#
# The Main Program
#
####################################################################
####################################################################

# Don't want to see the window wish initially puts up
wm withdraw .

initUserVars
initFonts

# Read in the indexes for the documentation
readMasterIndex $gDataDir

# For this alpha version, show a copyright and about statement
# on each startup.
showLargeMessage .copyright $gDataDir/CPYRIGHT
showLargeMessage .about $gDataDir/ABOUTTCH

# Create and initialize the main window
toplevel .win
makeMain .win
wm title .win "Tch"

# read in all the lib top-level forms
readLibForms

# read the help describing the features of the program
readPackageHelp

if {[llength $argv] == 0} {
    setIndex {} $gIndexWin
} elseif {[llength $argv] !=2} {
	puts "\nWrong number of command line arguments: $argv\n\
Usage: tch -c commandName or tch -k keyword"
} else {
	set option [lindex $argv 0]
	set arg [lindex $argv 1]

	if [string match $option "-c"] {
		setIndex $arg $gIndexWin
	} elseif [string match $option "-k"] {
		puts "\nDoing keyword search"
	} else {puts "\nError: Unknown option $option"}
}










