#!/usr/local/bin/wish -f
### $Id: omnimoni.tcl,v 0.24 1995/03/04 06:25:24 Rainer_Mager Exp $
### $Author: Rainer_Mager $
### OmniMoni verion:  $Revision: 0.24 $

################################################################################
################################################################################
###
### OmniMoni is a highly configurable, realtime, information monitoring system.
### OmniMoni, Copyright (C) 1995  Rainer Mager
### 
### This program is free software; you can redistribute it and/or modify it
### under the terms of the GNU General Public License as published by the Free
### Software Foundation; either version 2 of the License, or (at your option)
### any later version.
### 
### This program is distributed in the hope that it will be useful, but WITHOUT
### ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
### FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
### more details.
### 
### You should have received a copy of the GNU General Public License along with
### this program; if not, write to the Free Software Foundation, Inc., 675 Mass
### Ave, Cambridge, MA 02139, USA.
### 
################################################################################
################################################################################
###
### Notes:
###
### - This file works best with a tab size of 4 and a window width of 120.
### - I used a ;# for comments because a # alone following a set command fails.
### - Variables starting with capitals in the form "First" or "First_Second"
###   are global.
### - All of my functions are in the capitalization form of "firstSecond",
###   except some special functions like Debug.
###
################################################################################
################################################################################



#####################################
#####################################
### Set up some global variables ####
#####################################
#####################################


#########################
### Global variable list:
###
### OM_title			The title of the program, used in the window's title.
### OM_legal			The basic legal information, used in the help and the intro window.
### Debug				The debug level.  Check the Debug procedure for more info.
### Elapsed_Seconds		The number of seconds the program has been running so far.
### Update_Delta		The number of milliseconds between checking for possible updates.
### OmniRcFile			The file name of the configuration file.
### Indent				The level of indention for debug level 2.
### Indent_Chars		The characters for indenting debug level 2.
### Unpacked			The name of who an unpacked widget is packed after.
### Toggle_Pack			The status of whether a widget is unpacked or not.
### Times				The next update time for each widget.
### Stripped_Rc			The configuration file after it has been stripped.
### Graph_Maxes			The maximum value for each plot for each line.
### Graph_Mins			The minimum value for each plot for each line.
### Graph_Scale			The value this graph has been scaled.
### Last_Output			The output from the previous running of "program".
### To_Unpack_List	   	The list used during setup to remember who is defaulting to unpacked.
### Substitutes			The list of substitution variables for the configuration file.



##################################################################
### Initialize those global variables that require initial values.

proc doGlobals {} {
	global OM_title Debug Elapsed_Seconds Indent Update_Delta OmniRcFile
	global Indent_Chars OM_legal Unpacked To_Unpack_List
	set revision {$Revision: 0.24 $}
	set OM_title "OmniMoni v[lindex [string trim $revision {$}] 1]"
	set OM_legal1 "$OM_title, Copyright (C) 1995  Rainer Mager"
	set OM_legal2 "OmniMoni comes with ABSOLUTELY NO WARRANTY; for details use \"--legal\" option."
	set OM_legal "$OM_legal1\n$OM_legal2\n"
	set Elapsed_Seconds 0
	set Update_Delta 1000
	set OmniRcFile "~/.omnirc"
	set Indent_Chars "    "
	set Debug 0
	set Indent 0
	set Unpacked(temp) temp														;# this forces Unpacked to...
	unset Unpacked(temp)														;# ...be an array
	set To_Unpack_List {}
	set Substitutes(temp) temp													;# this forces Sucstitutes to...
	unset Substitutes(temp)														;# ...be an array
}

### END doGlobals
#################



###############################################
###############################################
### Error checking and debugging procedures ###
###############################################
###############################################



#########################################################################
### Handle miscellaneous tk errors.  Hopefully, this'll never get called.

proc tkerror { err_msg } {
	puts stderr "A Tk error occurred, program execution will continue."
	Debug 16 "\nThe actual Tk error was:\n$err_msg\n"
	flush stdout
}

### END tkerror
###############



######################################
### Indent a line any number of times.

proc Indent { indent } {
	global Indent_Chars
	for {set temp 0} {$temp < $indent} {incr temp} {							;# for each indent level
		puts -nonewline $Indent_Chars
	}
}

### END Indent
##############



#########################################
### Set the debug level given an integer.

proc setDebug { mode } {
	global Debug
	set Debug [expr $Debug ^ 1<<[expr $mode - 1]]								;# calculate new debug mode
	puts "Debug mode set to $Debug."
}

### END setDebug
################



################################################
### Check debug level and maybe print a message.

proc Debug { level string } {
	global Debug Indent
	;# Debug levels are:
	;# 1 - displays what is being done while parsing the Rc and Stripped Rc files
	;# 2 - displays the stripped Rc file as it is stripped
	;# 4 - makes all labels static and displays the label code instead of the result in the widgets
	;# 8 - displays what widgets are Unpacked and Repacked
	;# 16 - displays actual Tk error messages
	;# 32 - displays variable substitutions
	if {$string != {}} {
		if {$Debug & $level} {													;# if level bit in set on
			if {[set first_half [string first "->" $string]] > 0} {				;# if there is a -> in the string
				puts -nonewline [string range $string 0 $first_half]
				puts -nonewline [format "%3d>" [incr Indent]]
				puts -nonewline [string range $string [expr $first_half + 2] end]
			} elseif {[set first_half [string first "<-" $string]] > 0} {		;# elseif there is a <- in the string
				puts -nonewline [string range $string 0 $first_half]
				puts -nonewline [format "%3d-" $Indent]
				puts -nonewline [string range $string [expr $first_half + 2] end]
				incr Indent -1
			} elseif {[string index $string 0] == "\{"} {						;# elseif \{ starts string
				puts -nonewline [Indent $Indent]$string							;# indent the line
				incr Indent
			} elseif {[string index $string 0] == "\}"} {						;# elseif \} starts string
				puts -nonewline [Indent [incr Indent -1]]$string				;# indent the line
			} else {															;# else nothing special in string
				puts -nonewline $string
			}
		}
	} else {																	;# else not a string
		return [expr $Debug & $level]											;# return whether level was set
	}
}

### END Debug
#############



###############################################################################
### Catch executing a procedure and do something with a possible error message.

proc myCatch { command err_msg } {
	global Substitutes
	if {[catch $command out]} {													;# if there was an error
		Debug 16 "The actual error message was:\n\n$out\n\n"
		switch [string index $err_msg [expr [string length $err_msg] - 1]] {
			"!" {																;# if the message ended in "!"
				puts stderr $err_msg											;# display just my error message
				exit 1
			}
			":" {																;# if the message ended in ":"
				puts stderr "$err_msg  $out"									;# display my and Tk's error messages
				exit 1
			}
		}
		puts stderr $err_msg													;# disply just my error message
	}
	return $out
}

### END myCatch
###############



##########################################################################################################
### Check a list recursively to see if it matches Tcl's preliminary checks, and do variable substitutions.

proc checkList { list } {
	myCatch "llength {$list}" "Error in a section of the Rc file:"				;# getting a llength checks the list
}

### END checkList
#################



###############################################################################
### Check a string for variables and if any exist go through substituting them.

proc varSub { string } {
	global Substitutes
	set to_return ""
	while {[regexp -indices {\$[A-Z]} $string index]} {							;# while there are more variables
		if {[string index $string [expr [lindex $index 0] - 1]] == "\\"} {		;# if it is backslash quoted
			append to_return [string range $string 0 [lindex $index 1]]
			set string [string range $string [expr [lindex $index 1] + 1] end]
		} else {
			set begin [lindex $index 0]
			set count $begin
			while {[regexp {[A-Z]} [string index $string [incr count]]]} {}		;# while more letters in this one
			set end [expr $count - 1]
			set var_name [string range $string [expr $begin + 1] $end]
			if {[info exists Substitutes($var_name)]} {
				Debug 32 "Variable $var_name set to $Substitutes($var_name).\n"
				append to_return [string range $string 0 [expr $begin - 1]]		;# remember what's before variable
				append to_return $Substitutes($var_name)						;# remember variable
				set string [string range $string [expr $end + 1] end]			;# reset to rest for next check
			} else {
				puts stderr "Error, variable $var_name referenced with no definition!"
				exit
			}
		}
	}
	append to_return $string
	return $to_return
}

### END varSub
##############



#############################################
#############################################
### Get the information we need to begin. ###
#############################################
#############################################



######################################################################
### Check and get any of the environmental variables that I recognize.

proc getENVs {} {
	global OmniRcFile Indent_Chars Update_Delta env
	set env_list {OMNIMONI_RC OMNIMONI_INDENT OMNIMONI_DELTA}					;# the list of possible vars
	foreach var $env_list {
		if {[info exists env($var)]} {
			switch $var {
				OMNIMONI_RC {													;# if it's the RC file
					set OmniRcFile $env(OMNIMONI_RC)
				}
				OMNIMONI_INDENT {												;# if it's the indents chars
					set Indent_Chars $env(OMNIMONI_INDENT)
				}
				OMNIMONI_DELTA {												;# if its the update time
					set Update_Delta $env(OMNIMONI_DELTA)
				}
			}
		}
	}
}

### END getENVs
###############


###############################################################################
### Check the command line arguments for the one's I want and maybe print help.

proc getCLAs {} {
	global argv0 argc argv Debug Update_Delta OmniRcFile Indent_Chars OM_title OM_legal Substitutes
	if {$argc > 0} {
		for {set count 0} {$count < $argc} {incr count} {
			switch -regexp -- [lindex $argv $count] {
				--set {
					set var_name [lindex $argv [incr count]]
					if {[regexp {[^A-Z]} $var_name]} {
						puts stderr "Variable names must only contain capital letter, ignoring \"$var_name\"."
						incr count
					} else {
						set Substitutes($var_name) [lindex $argv [incr count]]
					}
				}
				--d {
					if {![catch "expr [lindex $argv [incr count]] + 1"]} {		;# if it's a number
						set Debug [lindex $argv $count]
					}
				}
				--i {
					set Indent_Chars [lindex $argv [incr count]]
				}
				--r {
					set OmniRcFile [lindex $argv [incr count]]
				}
				--u {
					set Update_Delta [lindex $argv [incr count]]
				}
				--help {
					puts stderr $OM_legal
					puts stderr "Usage: $argv0 \[OPTION\]...\n"
					puts stderr " --set name val set a variable to be referenced in the configuration file"
					puts stderr " --d 0-7        the or'd debug level(s) out of possible 3                \[0\]"
					puts stderr " --i x          the characters used for indents during debugging      \[    \]"
					puts stderr " --r rcfile     load the file \"rcfile\" instead of the default    \[~/.omnirc\]"
					puts stderr " --u m_secs     the number of milliseconds between checks for updates \[1000\]"
					puts stderr "\n  -help         help for possible wish arguments"
					puts stderr " --help         show this help information"
					puts stderr " --legal        show legal notices about the program"
					exit
				}
				--legal {
					puts stderr {
OmniMoni is a highly configurable, realtime, information monitoring system.
OmniMoni, Copyright (C) 1995  Rainer Mager

This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your option)
any later version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
more details.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 675 Mass
Ave, Cambridge, MA 02139, USA.
					}
					exit
				}
				default {
					puts stderr $OM_legal
					puts stderr "Unknown option:  [lindex $argv $count]\n"
					puts stderr "Usage: $argv0 \[--set name val\] \[--d 0-7\] \[--i x\] \[--r rcfile\]"
					puts stderr "       \[--u m_secs\] \[-help\] \[--help\] \[--legal\]"
					exit
				}
			}
		}
	}
}

### END getCLAs
###############


###################################
### Load in the configuration file.

proc loadRcFile { file_name } {
	if {[file readable $file_name]} {											;# if file is readable
		set file [open $file_name]												;# open it
		while {![eof $file]} {
			append temp [read $file 1024]										;# add the next K to the var
		}
		close $file
		return $temp
	} else {
		puts stderr "Error, could not find or read the file \"$file_name\"!"
		exit 1
	}
}

### END loadRcFile
##################



####################################################
####################################################
### Do the first pass on the configuration file. ###
####################################################
####################################################



###############################################################
### Change any global configuration options in the config file.

proc configOptions { opt_list } {
	global OmniRcFile Update_Delta
	foreach opt $opt_list {
		Debug 1 "Configured option    >---< $opt\n"
		switch [lindex $opt 0] {
			"\#" -
			comment {
			}
			OMNIMONI_DELTA {
				set Update_Delta [lindex $opt 1]
			}
			wm {
				myCatch "$opt" "Error doing a wm configuration."
			}
			default {
				myCatch "option add [lindex $opt 0] [lindex $opt 1]" \
					"Error setting option \"[lindex $opt 0]\" to \"[lindex $opt 1]\"."

			}
		}
	}
}

### END configOptions
#####################



##################################
### Create a text label (message).

proc createLabel {label_string label_name} {
	global Last_Output
 	set label_text [lindex $label_string 0]
 	set label_side [lindex $label_string 1]
 	set label_args [lindex $label_string 2]
	myCatch "message $label_name -justify center -aspect 5000" "Error creating label $label_name!"
	bind $label_name <Button-2> "Unpack $label_name"							;# mouse b3 unpack this label
	if {[string match {*[$%][0-9s]*} $label_text]} {							;# if label's dynamic
		if {[string match {*=*} $label_text]} {									;# if there's an = sign in it
			set Last_Output($label_name) {}										;# add it to the Last_Val array
		}
		set to_return 1
		Debug 1 "Packed dynamic label >---< $label_name\n"
		bind $label_name <Button-1> "demandUpdate $label_name"					;# mouse B1 demands update on label
 	} else {
		set to_return 0
		Debug 1 "Packed static label  >---< $label_name\n"
	}
	foreach arg $label_args {
		myCatch "$label_name configure [lindex $arg 0] \{[lindex $arg 1]\}" \
	   		"Error configuring label $label_name."
	}
	myCatch "pack $label_name -fill both -expand yes -side $label_side" \
		"Error packing label $label_name at side \"$label_side\"!"
	myCatch "$label_name configure -text \{$label_text\}" \
		"Error configuring label $label_name."
 	return $to_return
}

### END createLabel
###################



###########################################################################
### Recursive procedure to check a section and do initial pass stuff on it.

proc parseSection { section frame_level } {
	global Times Stripped_Rc Toggle_Pack To_Unpack_List Graph_Maxes Graph_Mins Graph_Scale
	switch -- [myCatch "lindex {$section} 0" "Error in Rc file:"] {
	    "\#" -
		comment {
			Debug 1 "Comment\n"
		}
		!group -
		group {
			set frame_name $frame_level.[string tolower [lindex $section 1]]
			checkList [set label_string [lindex $section 2]]
			checkList [set command_string [lindex $section 3]]
			set refresh [lindex $section 4]
			set pack_side [lindex $section 5]
			checkList [set extra_args [lindex $section 6]]
			set count 7
			set l_length [myCatch "llength {$section}" "Error in Rc file:"]
			set Times($frame_name) 0
			if {[string index [lindex $section 0] 0] == "!"} {					;# if it starts with !
				lappend To_Unpack_List $frame_name								;# remember it for later
			}
			if {$l_length > $count} {											;# if there are children
				Debug 1 "Entered group        -> $frame_name\n"
				myCatch "expr $refresh + 1" "Error, update time must be a number for $frame_name!"
				myCatch "frame $frame_name" "Error creating group frame $frame_name!"
				bind $frame_name <Button-2> "Unpack $frame_name"				;# mouse b3 unpack this label
				.pack$frame_level add cascade -label [lindex $section 1] -menu .pack$frame_name
				menu .pack$frame_name
				.pack$frame_name add checkbutton -label Frame -variable Toggle_Pack($frame_name) \
					-command "TogglePack $frame_name"
				set Toggle_Pack($frame_name) 1
				set future_label_string $label_string							;# init future_label_string
				if {$label_string != {}} {
					if {![createLabel $label_string $frame_name.label]} {		;# if it's static
						set future_label_string {}								;# set label to {} for future passes
					}
					.pack$frame_name add checkbutton -label [lindex $section 1] \
						-variable Toggle_Pack($frame_name.label) -command "TogglePack $frame_name.label"
					set Toggle_Pack($frame_name.label) 1
				}
				append Stripped_Rc "\{[string tolower [lrange $section 0 1]]"
				append Stripped_Rc " {$future_label_string}"
				append Stripped_Rc " [lrange $section 3 [expr $count - 1]]\n"
				Debug 2 "\{[string tolower [lrange $section 0 1]]"
				Debug 2 " {$future_label_string}"
				Debug 2 " [lrange $section 3 [expr $count - 1]]\n"
				while {$count < $l_length} {									;# while there are more children
					set pack_list [parseSection [lindex $section $count] $frame_name] ;# recurse children
					foreach frame $pack_list {									;# go through all the returned frames
						myCatch "pack $frame -fill both -expand yes -side $pack_side" \
							"Error packing $frame on side \"$pack_side\"!"
						Debug 1 "Packed group         <- $frame\n"
						foreach arg $extra_args {
							set old_config [myCatch "$frame configure [lindex $arg 0]" \
								"Error configuring $frame with [lindex $arg 0] [lindex $arg 1]... skipping."]
							if {[info exists old_config] && \
								   [lindex $old_config 3] == [lindex $old_config 4]} {;# if was config'd explicitly
								myCatch "$frame configure [lindex $arg 0] [lindex $arg 1]" \
								   "Error configuring $frame with [lindex $arg 0] [lindex $arg 1]... skipping."
								unset old_config								;# forget this one
							}
						}
					}
					incr count
				}
				append Stripped_Rc "\}\n"
				Debug 2 "\}\n"
				return $frame_name
			} elseif {$label_string != {}} {									;# els bo children, but label
				Debug 1 "Entered group        -> $frame_name\n"
				if {[createLabel $label_string $frame_name]} {					;# if label's dynamic
					append Stripped_Rc "\{[string tolower [lrange $section 0 1]]"
					append Stripped_Rc " {$label_string}"
					append Stripped_Rc " [lrange $section 3 [expr $count - 1]]\n\}\n"
					Debug 2 "\{[string tolower [lrange $section 0 1]]"
					Debug 2 " {$label_string}"
					Debug 2 " [lrange $section 3 [expr $count - 1]]\n"
					Debug 2 "\}\n"
				}
				.pack$frame_level add checkbutton -label [lindex $section 1] -variable Toggle_Pack($frame_name) \
					-command "TogglePack $frame_name"
				set Toggle_Pack($frame_name) 1
				return $frame_name
			}
		}
		!graph -
		graph {
			;###### Inititialization stuff #####
			set frame_name $frame_level.[string tolower [lindex $section 1]]
			set label_string [lindex $section 2]
			set command_string [lindex $section 3]
			set width [lindex $section 4]
			set height [lindex $section 5]
			set hash [lindex $section 6]
			set zero_color [lindex $section 7]
			set hash_color [lindex $section 8]
			foreach color "\"$zero_color\" \"$hash_color\"" {					;# check both colors
				if {$color != "" && [catch ". configure -bg \"$color\""]} {
					puts stderr "Error, coslor in $frame_name.title, $color, is invalid!"
					exit 1
				}
			}
			set refresh [lindex $section 9]
			set history [lindex $section 11]
			myCatch "expr $width + $height + $hash + $refresh + $history" \
				"Error with options to $frame_name!"							;# check if they're all numbers
			set direction [lindex $section 10]
			if {$direction != "left" && $direction != "right" && $direction != "up" && $direction != "down"} {
				puts stderr "Error in direction for $frame_name."
				exit 1
			}
			set extra_args [lindex $section 12]
			set count 13														;# set pointer to next arg
			set l_length [myCatch "llength {$section}" "Error in Rc file:"]
			;##### Create the frame #####
			set Times($frame_name) 0											;# init the refresh time
			if {[string index [lindex $section 0] 0] == "!"} {					;# if it starts with !
				lappend To_Unpack_List $frame_name								;# remember it for later
			}
			myCatch "frame $frame_name" "Error creating graph frame $frame_name!"
			bind $frame_name <Button-2> "Unpack $frame_name"					;# mouse b3 unpack this label
			.pack$frame_level add cascade -label [lindex $section 1] -menu .pack$frame_name
			menu .pack$frame_name
			.pack$frame_name add checkbutton -label Frame -variable Toggle_Pack($frame_name) \
				-command "TogglePack $frame_name"
			set Toggle_Pack($frame_name) 1
			set future_label_string $label_string								;# init future_label_string
			;##### Create the label #####
			if {$label_string != {}} {
				if {![createLabel $label_string $frame_name.label]} {			;# if it's static
					set future_label_string {}									;# set label to {} for future passes
				}
				.pack$frame_name add checkbutton -label [lindex $section 1] \
					-variable Toggle_Pack($frame_name.label) -command "TogglePack $frame_name.label"
				set Toggle_Pack($frame_name.label) 1
			}
			;##### Create the canvas #####
			myCatch "canvas $frame_name.graph -width $width -height $height -scrollincrement 1" \
				"Error creating graph canvas $frame_name.graph!"
			bind $frame_name.graph <Button-1> \
				"[list scrollCanvas $frame_name.graph mark $direction $width $height $history %x %y]"
			bind $frame_name.graph <B1-Motion> \
				"[list scrollCanvas $frame_name.graph dragto $direction $width $height $history %x %y]"
			if {$direction == "left" || $direction == "right"} {
				bind $frame_name.graph <Double-1> "[list $frame_name.graph xview 0]"
			} else {
				bind $frame_name.graph <Double-1> "[list $frame_name.graph yview 0]";# set reset value
			}
			bind $frame_name.graph <Button-2> "Unpack $frame_name"				;# bind b2 unpacking the canvas
			set Graph_Maxes($frame_name) 0
			set Graph_Mins($frame_name) 0
			set Graph_Scale($frame_name) "1.0 1.0"
			foreach arg $extra_args {
				myCatch "$frame_name.graph configure [lindex $arg 0] [lindex $arg 1]" \
					"Error configuring graph, $frame_name.graph."
			}
			menu .pack$frame_name.graph
			.pack$frame_name add checkbutton -label Graph -variable Toggle_Pack($frame_name.graph) \
				-command "TogglePack $frame_name.graph"
			set Toggle_Pack($frame_name.graph) 1
			pack $frame_name.graph
			Debug 1 "Packed graph         >---< $frame_name\n"
			append Stripped_Rc "\{[string tolower [lrange $section 0 1]]"
			append Stripped_Rc " {$future_label_string}"
			append Stripped_Rc " [lrange $section 3 [expr $count - 1]]\n"
			Debug 2 "\{   [string tolower [lrange $section 0 1]]"
			Debug 2 " {$future_label_string}"
			Debug 2 " [lrange $section 3 [expr $count - 1]]\n"
			;##### Check the lines to graph #####
			while {$count < $l_length} {										;# while there are still more of them
				set title [lindex [lindex $section $count] 0]
				if {$title != "\#" && $title != "comment"} {
					set default 1
					if {[string index $title 0] == "!"} {						;# if it starts with !
						set default 0											;# init it to off
					}
					set color [lindex [lindex $section $count] 2]
					if {$color != "" && [catch ". configure -bg \"$color\""]} {	;# if the color's not valid
						puts stderr "Error, color in $frame_name.title, $color, is invalid!"
						exit 1
					}
					set type [lindex [lindex $section $count] 3]
					if {$type != "solid" && $type != "lined"} {
						puts stderr "Error, graph, $frame_name.$title, type must"
						puts stderr "be \"solid\" or \"lined\", not \"$type\"."
						exit 1
					}
					.pack$frame_name add checkbutton -label $title \
						-variable Toggle_Pack($frame_name.graph.$title) \
						-command "ToggleGraph \"$frame_name.graph\" \"$title\" \"$color\""
					set Toggle_Pack($frame_name.graph.$title) $default			;# init to on or off
					append Stripped_Rc "\{[lindex $section $count]\}\n"
					Debug 2 "\{[lindex $section $count]\n"
					Debug 2 "\}\n"
				}
				incr count
			}
			append Stripped_Rc "\}\n"
			Debug 2 "\}\n"
			pack $frame_name -fill both
		}
		configure {
			configOptions [lrange $section 1 end]
		}
		default {
			Debug 1 "Default\n"
			if {$section == [lindex $section 0]} {								;# if this piece is a single piece
				puts stderr "Error, found a section beginning with \"$section\"."
			} else {
				set count 0
				while {$count < [llength $section]} {							;# while there are more
					set temp [parseSection [lindex $section $count] $frame_level];# recurse the children
					foreach frame $temp {
						myCatch "pack $frame -fill both -expand yes" \
							"Error packing $frame!"
						Debug 1 "Packed default       <- $frame\n"
					}
					append pack_list $temp
					incr count
				}
				return $pack_list
			}
		}
	}
}

### END parseSection
####################



####################################################
####################################################
### Support procedures for updating the display. ###
####################################################
####################################################



#################################################################
### Cause widget (and all ancestors) to do an update immediately.

proc demandUpdate { frame_name } {
	global Times Stripped_Rc
    while { $frame_name != {} } {
		set frame_name [string range $frame_name 0 [expr [string last . $frame_name] - 1]] ;# strip from last . on
		set Times($frame_name) 0
	}
	updateSection $Stripped_Rc "" ""
}

### END demandUpdate
####################



###############################################################################
### Unpack a widget and remember who it was packed after before being unpacked.

proc Unpack { widget_name } {
	global Unpacked Toggle_Pack
	bind $widget_name <Button-2> {}												;# stop the bindings while I do this
	set Toggle_Pack($widget_name) 0
	set pack_info [pack newinfo $widget_name]
	set real_parent [string range $widget_name 0 [expr [string last . $widget_name] - 1]];# find its parent
	if {$real_parent == {}} {													;# if it is a top widget
		set parent .															;# set its parent to just a .
	} else {
		set parent $real_parent
	}
	set list_index [lsearch -exact [pack slaves $parent] $widget_name]			;# find its position in the pack list
	if {$list_index == 0} {														;# if it was the first widget
		set packed_after first$real_parent										;# it was packed after nothing
	} else {
		set packed_after [lindex [pack slaves $parent] [expr $list_index - 1]]	;# find what it was packed after
	}
	foreach var [array names Unpacked] {										;# for each widget unpacked so far
		if {[lindex $Unpacked($var) 0] == $packed_after} {						;# was it unpacked after $packed_after
			set packed_after $var												;# then we should be unpacked after it
			break
		}
	}
	set Unpacked($widget_name) "$packed_after $pack_info"						;# remember the side and who after
	pack forget $widget_name
	update
	Debug 8 "Unpacked $widget_name\n"
	bind $widget_name <Button-2> "Unpack $widget_name"
}

### END Unpack
##############



###############################################################
### Repack and unpacked widget back to where it was originally.

proc Repack { widget_name } {
	global Unpacked
	set pack_after [lindex $Unpacked($widget_name) 0]
	set pack_info [lrange $Unpacked($widget_name) 1 end]
	while {[info exists Unpacked($pack_after)]} {								;# check if after has been unpacked
		set pack_after [lindex $Unpacked($pack_after) 0]						;# then point to after's after
	}
	set parent [string range $widget_name 0 [expr [string last . $widget_name] - 1]]
	if {$pack_after == "first$parent"} {										;# if it needs to be packed first
		if {$parent == {}} {													;# if parent is a top widget
			set parent .														;# set parent to just a .
		}
		set pack_before [lindex [pack slaves $parent] 0]						;# find first in pack list
		if {$pack_before == {}} {
			eval "pack $widget_name -expand yes $pack_info"
		} else {
			eval "pack $widget_name $pack_info -before $pack_before"
		}
	} else {
		eval "pack $widget_name $pack_info -after $pack_after"
	}
	unset Unpacked($widget_name)
	Debug 8 "Repacked $widget_name\n"
}

### END Repack
##############


	
################################################
### Toggle unpacked or packed state of a widget.

proc TogglePack { widget_name } {
	global Toggle_Pack
	if {$Toggle_Pack($widget_name)} {											;# if has been Unpacked
		Repack $widget_name
	} else {
		Unpack $widget_name
	}
}

### END TogglePack
##################



#####################################################
### Toggle showing or not showing, a line in a graph.

proc ToggleGraph { frame_name id color } {
	global Toggle_Pack
	if {!$Toggle_Pack($frame_name.$id)} {										;# if was unmarked
		Debug 8 "Unpacked $id\n"
		$frame_name itemconfigure $id -fill ""									;# change the color to clear
	} else {
		Debug 8 "Repacked $id\n"
		$frame_name itemconfigure $id -fill $color
	}
}

### END ToggleGraph
###################



################################################
### Scroll the graph with the left mouse button.

proc scrollCanvas { frame_name type direction width height history x y } {
	switch $direction {
		up {
			$frame_name scan $type 0 $y											;# mark or dragto the new y position
			if {[$frame_name canvasy 0] > 0} {									;# if it dragged too far up
				$frame_name yview 0
				$frame_name scan mark 0 $y
			}
			if {[$frame_name canvasy 0] < [expr $height - $history]} {			;# if it dragged too far down
				$frame_name yview [expr $height - $history]
				$frame_name scan mark 0 $y
			}
		}
		down {
			$frame_name scan $type 0 $y											;# mark or dragto the new y position
			if {[$frame_name canvasy 0] < 0} {									;# if we dragged too far down
				$frame_name yview 0
				$frame_name scan mark 0 $y
			}
			if {[$frame_name canvasy 0] > [expr $history - $height]} {
				$frame_name yview [expr $history - $height]
				$frame_name scan mark 0 $y
			}
		}
		left {
			$frame_name scan $type $x 0											;# mark or dragto the new x position
			if {[$frame_name canvasx 0] > 0} {									;# if we dragged too far
				$frame_name xview 0
				$frame_name scan mark $x 0
			}
			if {[$frame_name canvasx 0] < [expr $width - $history]} {			;# if we dragged too far
				$frame_name xview [expr $width - $history]
				$frame_name scan mark $x 0
			}
		}
		right {
			$frame_name scan $type $x 0											;# mark or dragto the new x position
			if {[$frame_name canvasx 0] < 0} {									;# if we dragged too far
				$frame_name xview 0
				$frame_name scan mark $x 0
			}
			if {[$frame_name canvasx 0] > [expr $history - $width]} {			;# if we dragged too far
				$frame_name xview [expr $history - $width]
				$frame_name scan mark $x 0
			}
		}
	}
}

### END scrollCanvas
####################



################################################
### Redraw all hash marks to new scale of graph.

proc reHash { frame_name height width direction history hash_value zero_color hash_color } {
	global Graph_Maxes Graph_Mins Graph_Scale
	$frame_name.graph addtag below_hashes below hashes
	$frame_name.graph delete hashes
	switch $direction {
		up -
		down {
			if {$direction == "up"} {
				set start_y [expr $height - $history]
				set end_y $height
			} else {
				set start_y 0
				set end_y $history
			}
			for {set hash $Graph_Mins($frame_name)} {$hash <= [expr $Graph_Maxes($frame_name) + $hash_value]} \
					{set hash [expr $hash + $hash_value]} {						;# for min val to max val
				set mark [expr -($hash - fmod($hash, $hash_value))]				;# find the val rounded to hash_val
				if {$mark != 0} {
					$frame_name.graph create line $mark $start_y $mark $end_y \
						-fill $hash_color -tags hashes
				}
			}
			$frame_name.graph create line 0 $start_y 0 $end_y -fill $zero_color -tags hashes
		}
		left -
		right {
			if {$direction == "left"} {
				set start_x [expr $width - $history]
				set end_x $width
			} else {
				set start_x 0
				set end_x $history
			}
			for {set hash $Graph_Mins($frame_name)} {$hash <= [expr $Graph_Maxes($frame_name) + $hash_value]} \
					{set hash [expr $hash + $hash_value]} {						;# for min val to max val
				set mark [expr -($hash - fmod($hash, $hash_value))]				;# find the val rounded to hash_val
				if {$mark != 0} {
					$frame_name.graph create line $start_x $mark $end_x $mark \
						-fill $hash_color -tags hashes
				}
			}
			$frame_name.graph create line $start_x 0 $end_x 0 -fill $zero_color -tags hashes
		}
	}
	if {[$frame_name.graph gettags below_hashes] != ""} {						;# if there was anything below hashes
		$frame_name.graph raise hashes below_hashes								;# put these hashes above them
		$frame_name.graph dtag below_hashes
	} else {
		$frame_name.graph raise hashes											;# put these on the bottom
	}
}

### END reHash
##############




###############################################################
### Evaluate the special symbols in a dynamic label or a graph.

proc evalSpecial { symbols output frame_name suffix } {
	global Last_Output Times Elapsed_Seconds
	set label_name $frame_name$suffix
	if {![Debug 4 ""]} {
		foreach piece $symbols {												;# for each part of the math equation
			switch -- [string index $piece 0] {
				"$" {
					if {[string index $piece 1] == "s"} {						;# if it is "$s"
						set piece [expr $Elapsed_Seconds - $Times($frame_name)]	;# find the last seconds delta
					} elseif {[string index $piece [expr [string length $piece] - 1]] == "\}"} {;# else if has a {...}
						set part [string trimleft [string range $piece 0 [expr [string first "\{" $piece] - 1]] "\$"]
						if {![catch "expr $part + 1"]} {						;# if it's a number
							set new_symbols [string trim [string range $piece [string first "\{" $piece] end] "\{\}"]
							set piece [evalSpecial $new_symbols [lindex $output $part] $frame_name $suffix]
						}
					} else {													;# else it's a normal $n
						set inx [string trimleft $piece \$]
						if {![catch "expr $inx + 1"]} {							;# if it's a number
							set piece [lindex $output $inx]						;# find what the $number points to
						}
					}
				}
				"%" {
					if {[string index $piece 1] == "s"} {						;# if it is "%s"
						set piece [expr $Elapsed_Seconds - $Times($frame_name)]	;# find the last seconds delta
					} elseif {[string index $piece [expr [string length $piece] - 1]] == "\}"} {;# else if has a {...}
						set part [string trimleft [string range $piece 0 [expr [string first "\{" $piece] - 1]] "%"]
						if {![catch "expr $part + 1"]} {						;# if it's a number
							set new_symbols [string trim [string range $piece [string first "\{" $piece] end] "\{\}"]
							set piece [evalSpecial $new_symbols [lindex $output \
								[expr [llength $output] - ($part + 1)]] $frame_name $suffix]
						}
					} else {													;# else it's a normal %n
						set inx [string trimleft $piece %]
						if {![catch "expr $inx + 1"]} {							;# if it's a number
							set piece [lindex $output [expr [llength $output] - ($inx + 1)]];# find field from right
						}
					}
				}
				":" {
					set trim [string trimleft $piece ":"]
					set split [split [string trimleft $trim "\$%"] "\$%"]
					set num1 [lindex $split 0]
					set num2 [lindex $split 1]
					if {![catch "expr $num1 + $num2"]} {						;# if both are numbers
						if {[string index $trim 0] == "\$"} {
							set b $num1
						} elseif {[string index $trim 0] == "%"} {
							set b [expr [llength $output] - $num1]
						}
						if {[string first "\$" [string trimleft $trim "\$%"]] != -1} {
							set e $num2
						} elseif {[string first "%" [string trimleft $trim "\$%"]] != -1} {
							set e [expr [llength $output] - ($num2 + 1)]
						}
						if {[info exists b] && [info exists e]} {
							set piece [lrange $output $b $e]
						}
					}
				}
				"|" {
					;# can't just use Tcl's split because that'll create null fields if two split chars back to back.
					set each_char [split $output {}]
					set output {}
					foreach char $each_char {
						if {$char != [string index $piece 1]} {
							append output $char
						} else {
							append output " "
						}
					}
					set piece {}
				}
				"=" {
					set trim [string trimleft $piece "="]
					set piece [evalSpecial $trim $Last_Output($label_name) $frame_name $suffix];# recurse to next piece
					set Last_Output($label_name) $output						;# remember old output
				}
			}
			append math $piece
		}
		if {[catch "expr $math" result]} {										;# if parsing the equation fails
			set return_val $math
		} else {
			set return_val $result
		}
	} else {																	;# else is debug level 4
		set return_val $symbols
	}
	return $return_val
}

### END evalSpecial
###################



########################################
### Compare to numbers' absolute values.

proc absSort { first second } {
	set first [expr abs([lindex $first 0])]
	set second [expr abs([lindex $second 0])]
	if {$first == $second} {
		return 0
	} elseif {$first > $second} {
		return 1
	} else {
		return -1
	}
}

### END absSort
###############



###########################################################
### Recursively go through a section updating as necessary.

proc updateSection { section frame_level output } {
	global Times Elapsed_Seconds Unpacked Toggle_Pack
	global Graph_Maxes Graph_Mins Graph_Scale Graph_Last Graph_History
	switch -- [lindex $section 0] {
		!group -
		group {
			set frame_name $frame_level.[lindex $section 1]
			set label_string [lindex $section 2]
			set command_string [lindex $section 3]
			set refresh [lindex $section 4]
			set count 7
			set l_length [llength $section]
			set command_out $output												;# pass on $output if not regenerated
			if {$l_length > $count && ![info exists Unpacked($frame_name)]} {	;# if children and not unpacked
				Debug 1 "Enter  -> [lindex $section 1]\n"
				if {$label_string != {}} {
					myCatch "$frame_name.label configure \
						-text \{[evalSpecial [lindex $label_string 0] $output $frame_name .label]\}" \
						"Error updating label text for $frame_name.label."
				}
				if {$Times($frame_name) <= $Elapsed_Seconds} {					;# if it is update time for children
					if {$command_string != {} && ![Debug 4 ""]} {
						foreach command $command_string {
							append command_out [myCatch "exec sh -c [list $command]" \
							    "Error executing command:  $command!"] "\n"
						}
					}
					set Times($frame_name) [expr $Elapsed_Seconds + $refresh]
					while {$count < $l_length} {
						updateSection [lindex $section $count] $frame_name $command_out
						incr count
					}
				}
				Debug 1 "Leave  <- [lindex $section 1]\n"
			} elseif {$label_string != {}} {									;# else no children, but label
				if {[pack slaves $frame_name] == ""} {							;# if it's not a parent
					myCatch "$frame_name configure \
						-text \{[evalSpecial [lindex $label_string 0] $output $frame_name ""]\}" \
						"Error updating label text for $frame_name."
				} else {
					myCatch "$frame_name.label configure \
						-text \{[evalSpecial [lindex $label_string 0] $output $frame_name .label]\}" \
						"Error updating label text for $frame_name.label."
				}
				Debug 1 "Update >---< [lindex $section 1]\n"
			}
		}
		!graph -
		graph {
			set frame_name $frame_level.[lindex $section 1]
			if {$Times($frame_name) <= $Elapsed_Seconds} {						;# if it is update time
				set command_string [lindex $section 3]
				set width [expr double([lindex $section 4])]
				set height [expr double([lindex $section 5])]
				set hash_value [expr double([lindex $section 6])]
				set zero_color [lindex $section 7]
				set hash_color [lindex $section 8]
				set refresh [lindex $section 9]
				set direction [lindex $section 10]
				set history [lindex $section 11]
				set count 13
				set l_length [llength $section]
				set Times($frame_name) [expr $Elapsed_Seconds + $refresh]		;# set time for next update
				Debug 1 "Graph  >---< [lindex $section 1]\n"
				set local_max 0
				set local_min 0
				if {![info exists Unpacked($frame_name.graph)] && ![Debug 4 ""]} {
					set old_max $Graph_Maxes($frame_name)
					set old_min $Graph_Mins($frame_name)
					eval "$frame_name.graph scale all 0 0 $Graph_Scale($frame_name)";# unscale from previous time
					;# go through all lines plotting them unscaled (at full value)
					while {$count < $l_length} {
						set current [lindex $section $count]
						set id [lindex $current 0]
						set expression [lindex $current 1]
						set color [lindex $current 2]
						if {!$Toggle_Pack($frame_name.graph.$id)} {
							set color ""
						}
						set type [lindex $current 3]
						set value [evalSpecial $expression $output $frame_name .$id]
						if {![catch "expr $value + 1"]} {						;# if it's a number
							if {$value > $local_max} {
								set local_max $value
							}
							if {$value < $local_min} {
								set local_min $value
							}
							if {$value > $Graph_Maxes($frame_name)} {
								set Graph_Maxes($frame_name) $value
								reHash $frame_name $height $width $direction $history \
									$hash_value $zero_color $hash_color
							}
							if {$value < $Graph_Mins($frame_name)} {
								set Graph_Mins($frame_name) $value
								reHash $frame_name $height $width $direction $history \
									$hash_value $zero_color $hash_color
							}
							if {![info exists Graph_Last] || \
								    ![info exists Graph_Last($frame_name.$id)]} {
								set Graph_Last($frame_name.$id) $value			;# find point plotted last time
							}
							switch $direction {
								up -
								down {
									if {$direction == "up"} {
										set y1 [expr $height - 1]
										set y2_lined [expr $height - 2]
										set ymove -1
									} else {
										set y1 0
										set y2_lined 1
										set ymove 1
									}
									set x1 [expr -$value]
									if {$type == "solid"} {
										set y2 $y1
										set x2 0
									} else {
										set y2 $y2_lined
										set x2 [expr -$Graph_Last($frame_name.$id)]
									}
									set Graph_Last($frame_name.$id) $value		;# remember this value for next time
									set xmove 0
								}
								left -
								right {
									if {$direction == "left"} {
										set x1 [expr $width - 1]
										set x2_lined [expr $width - 2]
										set xmove -1
									} else {
										set x1 0
										set x2_lined 1
										set xmove 1
									}
									set y1 [expr -$value]
									if {$type == "solid"} {
										set x2 $x1
										set y2 0
									} else {
										set x2 $x2_lined
										set y2 [expr -$Graph_Last($frame_name.$id)]
									}
									set Graph_Last($frame_name.$id) $value		;# remember this value for next time
									set ymove 0
								}
							}
							$frame_name.graph move $id $xmove $ymove
							set temp [$frame_name.graph create line $x1 $y1 $x2 $y2 -fill $color -tags $id]
							lappend local_sort($type) "$value $temp"
							lappend local_group $temp
						}
						incr count
					}
					if {[info exists local_sort(solid)]} {						;# if there were solid lines
						foreach item [lsort -command absSort $local_sort(solid)] {
							$frame_name.graph lower [lindex $item 1]			;# lower it to bottom
						}
					}
					if {[info exists local_sort(lined)]} {						;# if there were lined lines
						foreach item [lsort -command absSort $local_sort(lined)] {
							$frame_name.graph raise [lindex $item 1]			;# raise it to top
						}
					}
					lappend Graph_History($frame_name) "$local_max $local_min \{$local_group\}";# remember extremes
					if {[llength $Graph_History($frame_name)] > $history} {		;# if we're over history
						set leaving_max [lindex [lindex $Graph_History($frame_name) 0] 0]
						set leaving_min [lindex [lindex $Graph_History($frame_name) 0] 1]
						set leaving_group [lindex [lindex $Graph_History($frame_name) 0] 2]
						set Graph_History($frame_name) [lrange $Graph_History($frame_name) 1 end];# forget who leaving
						eval $frame_name.graph delete $leaving_group			;# delete everyone who's leaving
						set local_max 0
						set local_min 0
						if {$leaving_max >= $Graph_Maxes($frame_name) || \
								$leaving_min <= $Graph_Mins($frame_name)} {		;# if leaving was a max or min
							foreach old_one $Graph_History($frame_name) {		;# go through everyone still left
								if {[lindex $old_one 0] > $local_max} {
									set local_max [lindex $old_one 0]
								}
								if {[lindex $old_one 1] < $local_min} {
									set local_min [lindex $old_one 1]
								}
							}
							set Graph_Maxes($frame_name) $local_max
							set Graph_Mins($frame_name) $local_min
						}
					}
					;# find new scale values depending on max or mins from above
					switch $direction {
						up -
						down {
							set yscale 1
							if {[set g_width [expr $Graph_Maxes($frame_name) - $Graph_Mins($frame_name)]] == 0} {
								set xscale 1									;# if no width then no change
							} else {
								set xscale [expr ($width - 2) / $g_width]
								set Graph_Scale($frame_name) "[expr 1.0 / $xscale] 1.0"
							}
							set xmove [expr int(($Graph_Maxes($frame_name) * -$xscale) - 1)]
							$frame_name.graph xview $xmove
						}
						left -
						right {
							set xscale 1
							if {[set g_height [expr $Graph_Maxes($frame_name) - $Graph_Mins($frame_name)]] == 0} {
								set yscale 1									;# if no height then no change
							} else {
								set yscale [expr ($height - 2) / $g_height]
								set Graph_Scale($frame_name) "1.0 [expr 1.0 / $yscale]"
							}
							set ymove [expr int(($Graph_Maxes($frame_name) * -$yscale) - 1)]
							$frame_name.graph yview $ymove
						}
					}
					$frame_name.graph scale all 0 0 $xscale $yscale
				}
			}
		}
		default {
			set count 0
			if {[llength [lindex $section 0]] == 1} {							;# if this piece is one piece
				puts stderr "Error, found a section beginning with [lindex $section 0]."
			} else {
				while {$count < [llength $section]} {
					updateSection [lindex $section $count] "" ""
					incr count
				}
				return
			}
		}
	}
}

### END updateSection
#####################



###################################################
###################################################
### Start everything and enter the timing loop. ###
###################################################
###################################################



####################################################################
### Update everything and then wait the update time and do it again.

proc updateSeconds { old_update_delta } {
	global Elapsed_Seconds Stripped_Rc Update_Delta
	if {$old_update_delta == $Update_Delta} {
		set Elapsed_Seconds [expr $Elapsed_Seconds + ($Update_Delta / 1000.0)]
		Debug 1 "$Elapsed_Seconds Seconds\n[info level]\n"
		updateSection $Stripped_Rc "" ""
		after $Update_Delta "updateSeconds $Update_Delta"
	}
}

### END undateSeconds
#####################



#########################################
### Initialize everything and then begin.

proc initAll {} {
	global OM_title Colors OmniRcFile To_Unpack_List OM_title OM_legal Stripped_Rc Update_Delta
	doGlobals
	getENVs
	getCLAs
	wm title . $OM_title
	wm iconname . OmniMoni
	wm protocol . WM_DELETE_WINDOW exit
	message .intro -justify center -aspect 450 -text "$OM_legal\nPlease wait.  Setting up..."
	.intro configure -bg \#116 -fore \#c34
	pack .intro
	update
	menu .pack
	parseSection [varSub [loadRcFile $OmniRcFile]] ""
	foreach item $To_Unpack_List {
		Unpack $item
	}
	bind all <Button-3> ".pack post %X %Y"
	bind Menu <Button-3> ".pack unpost"
	bind all <Q> "exit"
	bind all <Z> "wm iconify ."
	bind all <plus> {
		set Update_Delta [expr $Update_Delta + 1000]
		puts "Update delta set to $Update_Delta."
		after $Update_Delta "updateSeconds $Update_Delta"
	}
	bind all <Control-plus> {
		set Update_Delta [expr $Update_Delta + 60000]
		puts "Update delta set to $Update_Delta."
		after $Update_Delta "updateSeconds $Update_Delta"
	}
	bind all <minus> {
		set Update_Delta [expr $Update_Delta - 1000]
		if {$Update_Delta < 1} {
			set Update_Delta 1
		}
		puts "Update delta set to $Update_Delta."
		after $Update_Delta "updateSeconds $Update_Delta"
	}
	bind all <Control-minus> {
		set Update_Delta [expr $Update_Delta - 60000]
		if {$Update_Delta < 1} {
			set Update_Delta 1
		}
		puts "Update delta set to $Update_Delta."
		after $Update_Delta "updateSeconds $Update_Delta"
	}
	bind all <KeyPress-1> "setDebug %K"
	bind all <KeyPress-2> "setDebug %K"
	bind all <KeyPress-3> "setDebug %K"
	bind all <KeyPress-4> "setDebug %K"
	bind all <KeyPress-5> "setDebug %K"
	bind all <KeyPress-6> "setDebug %K"
	. config -bg \#116
	pack forget .intro
	destroy .intro
	if {[info exists Stripped_Rc]} {
		updateSeconds $Update_Delta
	} else {
		puts stderr "Error, nothing valid in configuration file!"
		exit 1
	}
}

### END initAll 
###############


#################
### Here we go...

initAll

### END OmniMoni
################


### Emacs variables...
###
### Local Variables:
### mode:tcl
### tab-width:4
### comment-start: ";# "
### comment-start-skip: ";#+ *"
### comment-column:80
### minormode:line-number
### End: