#######################################################################
# RCS: @(#) $Id: user.tcl,v 5.5 2004/01/09 20:53:56 scholly Exp $
#
#  User-defined customization routines
#  ===================================
#
# Copyright (C) 2004 Electronic Data Systems Corporation.  All Rights Reserved.
#
#  Although this file has the instructions (and many examples) for 
#  adding customizations such as user-defined converters, user-defined 
#  command-line options, and user-defined printing procedures, we 
#  recommend that you put any new code into a file called custom*.tcl 
#  and not in here so that future upgrades go smoothly (the * means any
#  string; custom.tcl, custom1.tcl, and custom-printing.tcl are all
#  valid examples that would be loaded when the application starts).
#
#  Adding a conversion for a new format:
#
#  (For examples, see the prpConv- routines in this file and in the 
#  conv*.tcl files)
#
#  Step 1: Define a unique internal format name (typically 3 letters 
#		   (lower case)).  'moc' will be used as an example in the 
#		   following discussion.
#  Step 2: Add a TCL procedure to perform a conversion from this format 
#		   to a format already supported by the application.
#		   Use one of the procedures below or any of the procedures in
#		   conv*.tcl as template and change as needed. Your procedure
#		   requires the following 4 parameters (in order) and, in this
#		   example, would have to be called prpConv-moc:
#			   infile - the input file of format moc to be converted
#			   outfilename - proposed output file name. Can be ignored 
#				   if not practical. If you are using it, please use 
#				   the support proc prpOutputFileName the same as done 
#				   in conv*.tcl.
#			   targetformat - the format that should eventually be generated.
#				   Your proc is only responsible for one step towards that
#				   format. If you need to know whether you are going to a
#				   raster format, use prpIsRaster. If it is "-" then it goes
#				   to the most convenient PreVIEW native format.
#			   cvp - conversion parameters.  See the notes below for 
#				   more information.
#		   All files generated by these routines must be put in the temp
#		   file list if they are only intermediate results so that proper
#		   file clean-up happens (use utlAddTempFile to do this).  The 
#		   examples in this file and the prpConv- routines in the 
#		   conv*.tcl files should be very helpful in demonstrating how to 
#		   write your custom proc.
#  Step 3: Make sure that your new converter is registered everytime 
#		   the application is started. Use the proc prpRegisterFormat
#		   (outside the procedure definition) with the following parameters:
#			   description - text to be displayed in user messages
#			   extension - file extension that should be used to identify
#				   files of this format as well as flag in the Prepare
#				   command line. If you have several extensions handled
#				   by the same proc, register them in separate calls using
#				   the same internal format name.
#			   internalname - name defined in step 1.
#			   conversions - all the formats that can be generated by this
#				   proc in one conversion step. Use the internal format names
#				   (see query.tcl) and build a list if necessary.
#			   nativeformat - the preferred format for loading this format
#				   into PreVIEW (e.g. dwf, rvf, tif, mlr, ovl). Use "-" if
#				   the format depends on the input.
#  Step 4 (optional): Add a custom file-recognition routine.  This should be
#		   named "prpCheckFileType-[your format]" and takes 1 parameter,
#		   a file name.  It must return 1 if the format of the given file is 
#		   recognized as this type, 0 otherwise.
#  Step 5: Test your new converter. The easiest way to do this is to use 
#		   prepare if you have it available. See the notes below for more 
#		   information.
#
#  Adding a command-line option:
#
#  (For an example, see proc prpCustomCommand-te in this file)
#
#  Step 1: Define a unique command-line name.  You may want to look over
#		   the main application proc (for example, proc prepare in
#		   prepare.tcl) and the manual to make sure that this name will 
#		   not interfear with existing options.  The name
#		   can be shortened to an abbreviation where the existance or 
#		   lack of the additional characters do not make a difference.
#		   For example, "-test" could be shortened to "-te".
#  Step 2: Add a TCL procedure that takes the action required.	This
#		   often will involve modifying the cvp list.  The name should
#		   should be prpCustomCommand-(your shortcut name). The 
#		   following parameters are required:
#			  args - the arguments from the command-line (minus a few
#				 that are processed ahead of time).
#			  index - the index to the current position in the args
#			  cvp - the conversion parameter list.
#		   The following must be returned as a list: (see the notes
#			  below for more information)
#			  index - the index to the current position in the args (since
#				 this may have been modified)
#			  cvp - the conversion parameter list (since this may have
#				 been modified).  See the notes below for more information.
#			  action - either "done" or "continue" to indicate the next
#				 action the application should take.  "done" will stop the
#				 application, which is needed when the action the
#				 option has indicated is already completed or there is
#				 an error.
#		   The desired action will often involve communicating something
#		   to existing or seperate custom code.  Adding or modifying a
#		   key in cvp is the easiest way to do this (use utlSet).
#		   utlInfo can be used to output messages.	It is best to use
#		   utlError for error messages.  The index parameter and the
#		   args list can be used to retrieve parameters to the new
#		   command.  Be sure to increment the index so that the app does
#		   not try to process these parameters again as seperate commands.
#  Step 3: Register the new command.  Use prpRegisterCustomCommand and the
#		   shortcut name.  For -test and prpCustomCommand-te this would
#		   be "prpRegisterCustomCommand te".  This takes place at startup
#		   and should be outside of the proc definition.
#  Step 4: Test the new option.  
#  
#  Adding a filter:
#
#  (For an example, see proc prpFilterSpace2Linefeed in this file)
#
#  Step 1: Define a unique filter name.
#  Step 2: Add the filter procedure.  The name is not restricted.  The
#		   following parameters are required are infile, outfile, 
#		   targetformat, and cvp.  For more information, please see the 
#		   above discussion on adding a conversion.  The return value
#		   should be a list of the following:
#			  outformat - the format produced by the filter.  This can
#				 be the same as the input format.
#			  outfile - the file produced by the filter
#			  cvp (optional) - the conversion parameters.  These may
#				 be modified and returned by the filter.
#  Step 3: Register the filter with the prpRegisterFilter command.	This
#		   should be done outside the proc definition and requires the
#		   following parameters:
#			  filtername - the name of the filter, as defined in step 1.
#			  informat - the format to use with this filter (* means
#				 any, but the filter is still applied just once).
#			  procname - the name of the filter proc, as defined in
#				 step 2.
#  Step 4: Test the filter.  See the notes below for more information.
#
#  Adding a custom print procedure:
#
#  (For an example, see proc testPrintProc in this file)
#
#  Step 1: Create a printing procedure.  The name is not restricted.
#		   The following parameters are required (in order):
#			  viewlist - the list of input files in an internal
#				 viewlist structure (see the example, "testPrintProc",
#				 for an example of how to get the file names from this).
#			  pname - the printer name
#			  cvp - the conversion parameters.	See the notes below
#				 for more information.
#		   The proc must return 1 if it did the printing.  If additional
#		   conditions were checked before using this routine's printing
#		   mechanism, and these conditions determined that standard 
#		   printing should be used instead, then the proc should return 0.
#  Step 2: Map all the appropriate printers to this routine.  This is
#		   done by adding a resource entries for each printer that should
#		   use this proc.  The entry is names as [printer]PrintProc,
#		   where "printer" is the actual name of the printer that can
#		   be specified when using pvprint.  It may contain spaces.  The
#		   value should be the name of the custom print proc.  If all
#		   available printers should use this proc, then the entry
#		   "defaultPrintProc" may be used to map all printers to the new
#		   proc.  All other [printer]PrintProc specifications will
#		   still take precedence, however.	Please note that
#		   this means that no printer may actually be named "default".
#  Step 3: Test the new custom print proc.	See the notes below for
#		   more information.
#
#  Notes regarding all custom code:
#
#  Testing: You should test your new code.	Adding "debug" commands 
#		   into the code should help with doing this.  Debug messages 
#		   show up when the -debug or -debugf command-line options are 
#		   used.  If you use the customization from Preview as part of 
#		   an external conversion, add -debugf [filename] to the cmdargs 
#		   setting for [PVExt] (for windows) or Preview* (for unix) to 
#		   generate a debug file (since command-line options can't be 
#		   directly specified for pvext).
#  cvp:    The conversion parameters. This is a keyed list where the
#		   keys often corrospond to the keywords used in the 
#		   application's command line.	There is no restriction on 
#		   adding new keys, which may be helpful in communicating with 
#		   other existing and custom procs when cvp is one of the return 
#		   values.	The following commands are available for manipulating
#		   cvp:
#			  utlHas cvp [key]: Indicates if the key exists with 0 or 1.
#			  utlGet cvp [key]: Get the value of the specified key.
#			  utlSet cvp [key] [value]: Set the key to the value.  The
#				 key may already exist.
#  Multiple return values: If the custom proc requires multiple return
#		   values, format the arguments together as a list by using the
#		   "list" command (for example: return [list $index $cvp "done"]).	
#		   Please see the examples or your tcl documentation for more 
#		   information on the list command.
#  Utility procedures: util.tcl and query.tcl have a number of procedures
#		   available which may be helpful in writting your custom 
#		   procedure.  Please see those files for more information.
# 
#  For more information on the TCL scripting language, see "TCL and the
#  TK Toolkit" by John Ousterhout (Addison Wesley, # 63337), available in
#  technical bookstores. You can also find out more at the comp.lang.tcl
#  news group on the Internet and several Web sites with extensive background
#  information on TCL (e.g. http://web.cs.ualberta.ca).
#######################################################################

#######################################################################
#
# Setup routines for custom code
#

# Routine to look for user-defined TCL files called custom*.tcl and load
# them (names can be custom.tcl, custom1.tcl, custom-printing.tcl, etc).
# These must be in the installation directory's lib subdirectory.  To
# specify files in other locations, use the "CustomCode" resource setting.
# Seperate file specifications by a space.  A specification may contain
# a wildcard "*".

proc UserTCLCode {} {
	global PVDIR

	set customfiles [utlLS $PVDIR/lib/custom*.tcl]
	set customspecs [utlDefault CustomCode]
	foreach customspec $customspecs {
		set newfiles [utlLS $customspec]
		if {$newfiles != ""} {
			set customfiles [concat $customfiles $newfiles]
		}
	}
	foreach customfile $customfiles {
		if {$customfile != ""} {
			if {[catch {source $customfile} msg]} {
				utlError "in loading [file tail $customfile]: $msg"
			} else {
				debug "loaded [file tail $customfile]"
			}
		}
	}
}


# Routine to register custom commands

proc prpRegisterCustomCommand {command} {
	global CUSTOM_COMMANDS

	lappend CUSTOM_COMMANDS $command
}


###################################################################
#
# User-defined converters
#
###################################################################

#######################################################################
# Conversion of custom multi-page MIL-R (CALS) files to list of single 
# page files.
#
# NOTES:
# 1) Uses external utility 'scals' which must be in current path
# 2) For SCALS we have chosen the extension mpc because there is no
# standard convention.  If this is not the desired extension, please 
# modify the following command (the real one further down, not this 
# comment): 
#
# 'prpRegisterFormat "Custom multi-page CALS raster" mpc mpc mlr mlr'
#
# Replace the first 'mpc' in that line by the desired extension. If 
# there are more than one that will be used, copy the line and create 
# a call for each of the extensions.  Do not replace the second 'mpc' 
# because it is the internal name.  The external conversion driver 
# assumes that the converter program is named 'scals' and is in the 
# system path.  If that is not the case, then change the line 'set 
# results [utlExec scals $file]' to reflect your setup. Finally, the 
# SCALS implementation assumes that you want to convert the CALS files 
# to native raster.  To change this default result format, you need to
# change the last 'mlr' to [desired format] in the 'prpRegisterFormat' 
# call.

proc prpConv-mpc { file outfilename targetformat cvp } {
	global PVDIR

	# Change to outfile directory if possible, run in new directory
	set file [utlFullPath $file]
	set curdir [utlPWD]
	cd [file dirname $outfilename]
	set workdir [utlTempMkdir mpc]
	cd $workdir

	# Run "scals" tool on file; scals must be in path
	set scals "scals"
	if {![utlIsExec scals]} {
		set scals ${PVDIR}/scals
	}
	if {[utlSysname] == "MSWindows"} {
		winexec "$scals [winshortname [utlNativeFileName $file]]" 0 wait
	} else {
		utlExec $scals $file
	}
	utlYield -always

	set outfiles {}
	foreach f [utlLS] {
		utlMoveFile $f ..
		cd ..
		lappend outfiles [utlFullPath [file tail $f]]
		cd $workdir
	}

	if {[llength $outfiles] > 1} {
		set outfiles [prpFixOutputNames [file rootname [file tail $file]] \
			"mlr" "-" $outfiles [file rootname $file].mlr]
	} else {
		set outfiles [prpFixOutputNames [file rootname [file tail $file]] \
			"mlr" "-" $outfiles]
	}

	cd $curdir
	utlRM $workdir
	if { [string length $outfiles] == 0} {
		error "'scals' did not produce any files"
	}

	# find out whether results are temporary
	if { $targetformat != "-" && $targetformat != "mlr" } {
		utlAddTempFile $outfiles
	}

	return [list mlr $outfiles]
}

# register format
prpRegisterFormat "Custom multi-page CALS raster" mpc mpc mlr mlr


#######################################################################
# Loading list of files as one document (enables range printing for it)

proc prpConv-fls { infile outfilename targetformat cvp } {

	set infile [utlFullPath $infile]
	debug ">prpConv-fls $infile $outfilename $targetformat $cvp"
	
	# now collect all file names
	set result {}
	if {[file size $infile] >= 100000} {
		# this must be wrong, return error
		utlError "file list $infile too large to process"
		return
	}
	
	foreach file [utlReadFile $infile] {
		if {[string length $file] > 0} {
			set file [utlUnixFileName $file]
			set file [utlFullPath $file [file rootname $infile]]
			if {[file exists $file]} {
				set curformat [prpFileType $file]
				if {$targetformat != $curformat &&
					($targetformat != "-" || ![prpIsNativeFormat $curformat])} {
					utlFields [prpConvert $file $curformat "" $targetformat $cvp] _ convfiles
					set result [concat $result $convfiles]
				} else {
					set tmpfile [utlTempFile $file]
					utlCopyFile $file $tmpfile
					lappend result $tmpfile
				}
			}
		}
	}
	
	# Return results
	debug "<prpConv-fls -> $result"
	return [list - $result]
}

# register format
prpRegisterFormat "File list" fls fls - -


#######################################################################
# TMP files -- used in pvext

proc prpConv-tmp { infile outfilename targetformat cvp } {
	set infile [utlFullPath $infile]
	debug ">prpConv-tmp $infile $outfilename $targetformat $cvp"
	return [list - $infile]
}

prpRegisterFormat "Delete File When Done" erase tmp - -


#######################################################################
# DEL files -- used in pvext

proc prpConv-del { infile outfilename targetformat cvp } {
	set infile [utlFullPath $infile]
	debug ">prpConv-del $infile $outfilename $targetformat $cvp"

	# now collect all file names
	set result {}
	if {[file size $infile] >= 100000} {
		# this must be wrong, return error
		utlError "file list $infile too large to process"
		return
	}
	if {[string match "[utlReadFile $infile 7]*" "#DELETE FILE LIST"]} {
		# this must be wrong, return error
		utlError "delete file list $infile not valid"
		return
	}
	foreach file [utlReadFile $infile] {
		if {[string length $file] > 0} {
			set file [utlUnixFileName $file]
			set file [utlFullPath $file [file rootname $infile]]
			if {[file exists $file]} {
				set curformat [prpFileType $file]
				if {$targetformat != $curformat &&
					($targetformat != "-" || ![prpIsNativeFormat $curformat])} {
					utlFields [prpConvert $file $curformat "" $targetformat $cvp] _ convfiles
					set result [concat $result $convfiles]
					utlRM $file
				} else {
					lappend result $file
				}
			}
		}
	}
	utlRM $infile
	debug "<prpConv-del -> $result"
	return [list - $result]
}

prpRegisterFormat "Delete File List When Done" del del - -


###################################################################
#
# User-defined command-line options
#
###################################################################

###################################################################
# Custom command example: "-test"
#
# This is an example of a custom command.  You can use "-test" or 
# anything starting with "-te" to invoke this routine from the command 
# line.

proc prpCustomCommand-te {args index cvp} {
	utlInfo "This is a custom command test"

	return [list $index $cvp "done"]
}

prpRegisterCustomCommand te


###################################################################
#
# User-defined filters - applied to format before conversion
# (use the "-filter" command)
#
###################################################################

###################################################################
# Sample filter

proc prpFilterDummy  {infile outfilename targetformat cvp} {
	utlInfo "    Applying dummy filter to $infile"
	return [list [prpFileType $infile] [list $infile]]
}

prpRegisterFilter dummy ps prpFilterDummy


###################################################################
# Testing filter: This can be used to leave a temp file in the
# temp directory to verify that the TempSubDirectory system is 
# working.

proc prpFilterTempFileTest	{infile outfilename targetformat cvp} {
	global TMPDIR
	dbg {>prpFilterTempFileTest-tif: $infile $outfilename $targetformat $cvp}

	# Create an extra temp file
	set tempfile [utlOutFile test tmp]
	utlInfo "    Leaving a temp file ($tempfile) in the temp directory"
	dbg {Temp directory contents=[utlLS $TMPDIR]}

	# Return the results
	set result [list [prpFileType $infile] [list $infile]]
	dbg {<prpFilterTempFileTest-tif: $result}
	return $result
}

prpRegisterFilter tempfiletest * prpFilterTempFileTest


###################################################################
# Filter to patch PS files having faulty Mac Word generated EPS
# The PS created by MSWord 7.0 from these MSWord 4.0 for Mac files
# that contain embedded graphics using EPS generate bad PS.
# It can be patched up by the replacements shown below.
# It seems all bad EPS can be found by looking either for two
# consecutive binary 0 or around the string MSEPS.
# Hint: use xd to get binary dump of PS file and look for above
# problem spots. RTI built-in TCL proc filesubst created for this purpose.
# Also, some problems with output naming. Hence used a different extension.

proc Nfilesubst {p1 p2 p3 p4} {
	set c [filesubst $p1 $p2 $p3 $p4]
	debug "filesubst $p2 done $c time"
	return $c
}


proc prpFilterMacWordEPS  {infile outfilename targetformat cvp} {
	set outfile [prpOutputFileName $infile eps nonum $outfilename \
								[expr {$targetformat == "ps" }]]
	set tempfile [utlTempFile]
	if {[Nfilesubst $infile " de@0d@0a" " def@0a" $tempfile] > 0} {
		utlInfo "        Applying macwordeps/Win95 filter to $infile"
		Nfilesubst $tempfile " restor@0d@0a" " restore@0a" $outfile
		Nfilesubst $outfile " en@00@00" " end@0a" $tempfile
		if {[Nfilesubst $tempfile "@09currentp@00@00" " currentpoi" $outfile] > 0} {
			# indicator of second group of bad EPS
			Nfilesubst $outfile " 39@00@001069" " 395.1069" $tempfile
			Nfilesubst $tempfile "2@00@00.8029" "247.8029" $outfile
			Nfilesubst $outfile "26@00@003176" "263.3176" $tempfile
			utlMoveFile $tempfile $outfile
		}
		return [list ps [list $outfile]]
	} elseif {[Nfilesubst $infile "pp_save rest@00%" "pp_save restore@0a" $tempfile] > 0} {
		utlInfo "        Applying macwordeps/NT filter to $infile"
		Nfilesubst $tempfile "    p?@00@00" " end@0a" $outfile
		Nfilesubst $outfile   "/pp_by2 463 ??"	"/pp_by2 463 def@0a" $tempfile
		Nfilesubst $tempfile  "/pp_by2 413 ??"	"/pp_by2 413 def@0a" $outfile
		# Nfilesubst $outfile  "/pp_by2 463 f@99"	"/pp_by2 463 def@0a" $tempfile
		# Nfilesubst $tempfile "/pp_by2 463 @01@40" "/pp_by2 463 def@0a" $outfile
		# Nfilesubst $outfile  "/pp_by2 463 @00@04" "/pp_by2 463 def@0a" $tempfile
		# Nfilesubst $tempfile	"/pp_by2 463 @21@18" "/pp_by2 463 def@0a" $outfile
		if {[Nfilesubst $outfile "curren@00@00@00@00" " currentpoi" $tempfile] > 0} {
			# indicator of second group of bad EPS
			Nfilesubst $tempfile "@00@00@00@001069" "395.1069" $outfile
			Nfilesubst $outfile "@00@00@00@003176" "263.3176" $tempfile
			Nfilesubst $tempfile "@00@00@00@00.8029" " 247.8029" $outfile
			# utlMoveFile $tempfile $outfile
		}
		return [list ps [list $outfile]]
	} else {
		return [list ps [list $infile]]
	}
}

prpRegisterFilter macwordeps ps prpFilterMacWordEPS


###################################################################
# Filter to change all spaces in line feeds for debugging

proc prpFilterSpace2Linefeed  {infile outfilename targetformat cvp} {
	set outfile [prpOutputFileName $infile psd nonum $outfilename 1]
	filesubst $infile " " "@0a" $outfile
	return [list ps [list $outfile]]
}

prpRegisterFilter space2linefeed ps prpFilterSpace2Linefeed


###################################################################
#
# Custom printing procedures
#
###################################################################

###################################################################
#
# Custom printing procedure "test"
#
# The routine will take control for printing if a printer name is
# mapped to this routine.  This can be done in the resource/pvwm.ini
# file using the "${printername}PrintProc" pvprint entry.
#
# For example: (windows)
# [PVPrint]
# test printerPrintProc=testPrintProc

proc testPrintProc {viewlist pname cvp} {
	utlInfo "Printing using test print procedure:"

	foreach viewset $viewlist {
		foreach sheet $viewset {
			set file [lindex $sheet 1]
			utlInfo "    Would print $file now to printer \"$pname\"..."
		}
	}

	utlInfo "Done."

	return 1
}


###################################################################
#
# Custom UI code
#
###################################################################

###################################################################
# Unix-like commands for convenient interactive use
# (windows and unix console)

proc ls {{pattern *}} {
	foreach file [utlLS $pattern] {
		utlInfo [file tail $file]
	}
}

proc cp {from to} {utlCopyFile $from $to}

proc mv {from to} {utlMoveFile $from $to}

proc rm args {foreach file $args {foreach file2 [utlLS $file] {utlRM $file2}}}

proc more {file {cnt 20}} {
	set fd [utlOpen $file r]
	while {![eof $fd] && $cnt > 0} {
		gets $fd line
		utlInfo $line
		incr cnt -1
	}
	utlClose $fd
}

proc h {} { history }


###################################################################
# Special console commands
# (windows only)

proc traceon {{file ""}} {
	global DEBUG DEBUGVARS
	set DEBUG *
	set DEBUGVARS(FILE) $file
}

proc traceoff {} {
	global DEBUG DEBUGVARS
	set DEBUG ""
	set DEBUGVARS(FILE) ""
}

# Automatically type the given previous command without hitting return so
# editing can be done.
# The argument can be a number to indicate an index position, or a string
# to indicate a substring to search for.
proc edit {event {arg1 ""}} {
	set chars [history event $event]
	if {$arg1 != ""} {
		if {[regexp {^[1-90]+$} $arg1]} {
			set pos $arg1
		} else {
			set pos [string first $arg1 $chars]
			if {$pos < 0} {
				utlError "Substring \"$arg1\" not found in \"$chars\""
				return
			}
		}
		set chars [string range $chars 0 $pos]
	}
	utlSendChars "STRING [list $chars]" self
}

# Load TCL files from all lib directories
proc ld args {
	global auto_path

	# Construct the list of files to source
	if {$args == ""} { # Load all tcl files
		set files ""
		foreach libdir $auto_path {
			set newfiles [utlLS $libdir/*.tcl]
			set newfiles [utlSubList "$libdir/main.tcl $libdir/client.tcl" $newfiles]
			set files [concat $files $newfiles]
		}
	} else { # Load only the selected tcl files
		set files ""
		foreach arg $args {
			foreach libdir $auto_path {
				set newfiles [utlLS $libdir/$arg*]
				set files [concat $files $newfiles]
			}
		}
	}

	# Source all appropriate files
	foreach file $files {
		# do not load files ending in ~ (emacs backup files)
		if {![regexp {.*~$} $file]} {
			utlInfo "Loading $file..."
			uplevel 0 "source \"$file\""
		}
	}
}

# Stops watch mode or current process
# NOTE: DO NOT stop the current process if stopping watch mode; this interrupts
# the process of stopping watch mode.
proc stop {} {
	utlInfo "Stopping watch mode and/or current job"
	if {[utlWatchModeControl -status]} {
		utlWatchModeControl -stop
	} else {
		utlProcessControl -stop
	}
}


###################################################################
# Custom menu commands
# (windows only)

# Adds a custom menu item
proc addmenu {top {item ""} {command ""}} {
	global CustomMenuVars

	if {![info exists CustomMenuVars(id)]} {
		# 2000 as the starting ID is arbitrary and should be adjusted later
		set CustomMenuVars(id) 2000
		set CustomMenuVars(toppos) 2
		set CustomManuVars(commands) ""
		set CustomMenuVars(itempos) 0
	}

	# Add a top-level menu
	if {$item == ""} {
		insertmenuitem $top $CustomMenuVars(id) $CustomMenuVars(toppos)
		incr CustomMenuVars(toppos)

	# Add an item to an existing menu
	} else {
		insertmenuitem $item $CustomMenuVars(id) $CustomMenuVars(itempos) $top
		incr CustomMenuVars(itempos)
	}

	# Associate a command
	if {$command != ""} {
		utlSet CustomMenuVars(commands) $CustomMenuVars(id) $command
	}

	set result $CustomMenuVars(id)
	incr CustomMenuVars(id)

	return $result
}

# This is the callback for addmenu
proc userMenu {id} {
	global CustomMenuVars
	
	set command [utlGet CustomMenuVars(commands) $id]
	if {$command != ""} {
		eval $command
	}
}


###################################################################
# Custom keyboard commands
# (windows only)

proc userKey {key} {
	if {[info proc userKey-$key] != ""} {
		userKey-$key
	} else {
# Uncomment for determining key mapping:
#		puts "Key: $key"
	}

	return ""
}

# F4
proc userKey-115 {} {
	usrSaveBuffer

	return ""
}

# F5
proc userKey-116 {} {
	usrPostBuffer

	return ""
}

# Control-d
proc userKey-C68 {} {
	usrPartialLS
}

# tab
proc userKey-9 {} {
	usrAutoComplete

	return ""
}

# Save current line of text
proc usrSaveBuffer {} {
	global UserKeyVars

	set UserKeyVars(SAVEBUFFER) [console_getbuffer]

	return ""
}

# Get saved line of text
proc usrGetBuffer {} {
	global UserKeyVars

	if [info exists UserKeyVars(SAVEBUFFER)] {
		return $UserKeyVars(SAVEBUFFER)
	} else {
		return ""
	}
}

# Post saved line of text to the console input buffer
proc usrPostBuffer {} {
	set chars [usrGetBuffer]

	utlSendChars "STRING [list $chars]" self

	return ""
}

# Complete file name based on the buffer and contents of cwd
# Continue editing same command line after file name
proc usrAutoComplete {} {

	set buffer [console_getbuffer]

	# Get file list
	set pattern ""
	regexp {([^ ]+)$} $buffer _ pattern
	set filelist [utlLS ${pattern}*]

	# Attempt to get remaining characters
	set chars ""
	if {[llength $filelist] == 1} {
		set file [file tail [lindex $filelist 0]]
		set pattern [file tail $pattern]
		regexp "^${pattern}(.*)$" $file _ chars
	}

	# If there was a match, post remaining characters
	if {$chars != ""} {
		utlSendChars "STRING [list $chars]" self
	}
}

# Do a ls based on the buffer and contents of cwd
# Continue editing same command line at same point
proc usrPartialLS {} {

	set buffer [console_getbuffer]

	# Get file list
	set pattern ""
	regexp {([^ ]+)$} $buffer _ pattern
	set filelist [utlLS ${pattern}*]

	# Display file list
	console_puts -nonewline "\n"
	foreach file $filelist {
		utlInfo [file tail $file]
	}
	utlInfo ""

	# Redisplay contents of current buffer
	console_puts -nonewline $buffer

	return ""
}
