#!/bin/sh
#\
exec cmlogwish -f "$0" ${1+"$@"}
#

######################
#
# Some global items
#
######################
global env argv argv0 argc auto_path options
set auto_path "./ $auto_path $env(CODA)/common/lib/daq"
set tcl_precision 15
#DIALOG Dlog

# Following is an array used to assign colors for various statuses.
# Just add a new line for another official status.
# The color can be any string in rgb.txt or a number in the form #RGB (see book),
# and the relief can be flat, raised, sunken, groove, or ridge.
set status_tags(DEBUG)  [list cyan    flat]
set status_tags(STATUS) [list white   raised]
set status_tags(ERROR)  [list red     raised]
set status_tags(error)  [list orange  flat]
set status_tags(WARN)   [list yellow  raised]
set status_tags(INFO)   [list green   flat]
set status_tags(START)  [list purple  flat]
set status_tags(STOP)   [list magenta flat]
set status_tags(CLAS)   [list blue    raised]

wm withdraw .

######################
#
# Some Useful Routines
#
######################
# isInt checks for decimal integer (no 00 or 06 etc)
proc isInt {num} {
    if {[regexp {^(0|[1-9][0-9]*)$} $num] != 1} {return 0}
    return 1
}

proc isNum {num} {
    if {[regexp {^[+-]?([0-9]+[.]*|[0-9]*\.[0-9]+)([eE][+-]?[0-9]+)*$} $num] != 1} {return 0}
    return 1
}

############################################################
#
#    Class CONFIG
#	
#    Purpose:
#	This class is used to read and write configuration files.
#
#    Variables:
#	file - file name
#	mode - read or write
#	viewers - Stores all data read from a file.
#	viewers(names) - list of viewer names
#	viewers($name,type) - data type (cmlog or msql)
#	viewers($name,screen)  - screen for display
#	viewers($name,update)  - automatic viewing of new messages (0=off, 1=on)
#	viewers($name,bfilter) - filter condition for data viewing - browser
#	viewers($name,sfilter) - filter condition for data viewing - server
#	viewers($name,bview)   - filter's "view only" or "filter out" -browser
#	viewers($name,sview)   - filter's "view only" or "filter out" -server
#	viewers($name,geometry) - geometry of viewer window
#	viewers($name,mapped) - viewer window mapped or unmapped
#	viewers($name,source) - if cmlog it's the port, if msql
#				it's a list of table, db, and host
#	viewers($name,col)    - number of columns in viewer $name
#	viewers($name,col,$N) - list of title, tag, and width for column N
#	viewers($name,maxlines) - max number of lines viewable when monitoring
#				- (=0 means all lines viewable)
#
#    Methods:
#	get         - get all data
#	readConfig  - get data from config file
#	writeConfig - write data to config file
#	parseLine   - returns a list of all items on a file's line.
#		      All between {}'s is 1 item. White space separation.
#	parseAsList - returns a list of 2 items from a file's line. This is
#		      necessary to parse filter conditions. The second item is
#		      after white space and contains all the rest of the line.
#
#    Config File Format:
#	All lines apply to viewer $name until next "viewer $name2" statement
#	is found. If no initial "viewer" statement, the viewer name defaults
#	to "view_[pid]".
#	Column data is entered as "column $title $tag $width".
#	Type data is "type cmlog" or "type msql".
#	Source data is "source $port" for cmlog or "source $table $db $host".
#	Filter data is "bfilter $filterlist" or "sfilter $filterlist".
#	Filterview data is "browser(server)view 0 (or 1)". 1 = view only, 0 = filter out.
#	Geometry data is "geometry widthxheight+-x+-y" (eg. 101x20+12-35)
#	Map data is "mapped 0 (or 1)". 0 = unmapped window, 1 = mapped
#	Screen data is "screen $displayhost:0.0".
#	Update data is "update 0 (or 1)". 0 = monitor off, 1 = monitor on
#	Maximun line data is "maxlines <number>". 0 = view all lines
#	White space is ignored. 
#
#	Anything other than the keywords (col column filter filterview geometry
#	mapped maxlines screen source type update viewer) or any capitalizations
#	are ignored.
#
############################################################

class CONFIG {
    constructor {_file _mode} {}
    destructor  {}
    
    private variable viewers
    private variable file   ""
    private variable mode   ""
   
    private method isKeyWord   {word} {}
    private method parseLine   {line} {}
    private method parseAsList {line} {}
    
    public method get         {option} {}    
    public method readConfig  {} {}
    public method writeConfig {	nameList 
				typeList
				screenList
				updateList
				sourceList
				mappedList
				maxlinesList
				geomList
				colList
				filterListB
				filterListS
				filterviewListB
				filterviewListS} {}
}

body CONFIG::get {option} {
    # return values of all variables
    switch -- $option {
	-file     { return $file }
	-mode     { return $mode }
	-viewers  { return $viewers(names) }
	-types    { set types ""
		    foreach i $viewers(names) {
			lappend types $viewers($i,type)
		    }
		    return $types
		  }
	-screens  { set screens ""
		    foreach i $viewers(names) {
			lappend screens $viewers($i,screen)
		    }
		    return $screens
		  }
	-updates  { set updates ""
		    foreach i $viewers(names) {
			lappend updates $viewers($i,update)
		    }
		    return $updates
		  }
	-bfilters  { set filters ""
		    foreach i $viewers(names) {
			lappend filters $viewers($i,bfilter)
		    }
		    return $filters
		  }
	-bviews  { set fviews ""
		    foreach i $viewers(names) {
			lappend fviews $viewers($i,bview)
		    }
		    return $fviews
		  }
	-sfilters  { set filters ""
		    foreach i $viewers(names) {
			lappend filters $viewers($i,sfilter)
		    }
		    return $filters
		  }
	-sviews  { set fviews ""
		    foreach i $viewers(names) {
			lappend fviews $viewers($i,sview)
		    }
		    return $fviews
		  }
	-sources  { set sources ""
		    foreach i $viewers(names) {
			lappend sources $viewers($i,source)
		    }
		    return $sources
		  }
	-geometrys { set geoms ""
		    foreach i $viewers(names) {
			lappend geoms $viewers($i,geometry)
		    }
		    return $geoms
		  }
	-maps     { set maps ""
		    foreach i $viewers(names) {
			lappend maps $viewers($i,mapped)
		    }
		    return $maps
		  }
	-maxlines { set maxlines ""
		    foreach i $viewers(names) {
			lappend maxlines $viewers($i,maxlines)
		    }
		    return $maxlines
		  }
	-columns  { set columns ""
		    foreach i $viewers(names) {
			set subColList ""
			for {set j 1} {$j <= $viewers($i,col)} {incr j} {
			    lappend subColList $viewers($i,col,$j)
			}
			lappend columns $subColList
		    }
		    return $columns
		  }
    }
    error "CONFIG::get bad option \"$option\""
}

body CONFIG::constructor {_file _mode} {
    global env
    
    set file "$_file"
    set mode "$_mode"
    set viewers(names) ""
    
    if {[file isdirectory $file]} {
   	 error "\"$file\" is a directory"
    } elseif {"$file" == ""} {
   	 error "file needs to be specified"
    }
    
    if {"$mode" == "read"} {
	if {[file readable $file] == 0} {
	    error "File \"$file\" not readable"
	}
    } elseif {"$mode" == "write"} {
	if {([file exists $file] == 1) && ([file writable $file] == 0)} {
	   error "File \"$file\" not writable" 
	}
    } else {
	error "mode argument must be \"read\" or \"write\""
    } 
}

body CONFIG::writeConfig {nameList typeList screenList updateList
			  sourceList mappedList maxlinesList geomList colList
			  filterListB filterListS filterviewListB filterviewListS} {
    if {[file exists $file]} {
	set ret [tk_dialog .err WARNING "The file \"$file\" exists. Do you want to overwrite it?" {} 1 YES NO]
	if {$ret == 1} {
	    return
	}
    }
	
    if {[catch {set fid [open $file w]} msg]} {
	error "Cannot open file \"$file\""
    }
    
    puts $fid "This file is a configuration for the TJNAF CMLOG database browser\n"
    
    set length [llength $nameList]
    for {set i 0} {$i < $length} {incr i} {
	puts $fid "viewer       [lindex $nameList $i]"
	puts $fid "  type       [lindex $typeList $i]"
	puts $fid "  screen     [lindex $screenList $i]"
	puts $fid "  update     [lindex $updateList $i]"
	puts $fid "  source     [lindex $sourceList $i]"
	puts $fid "  mapped     [lindex $mappedList $i]"
	puts $fid "  maxlines   [lindex $maxlinesList $i]"
	puts $fid "  geometry   [lindex $geomList $i]"
	puts $fid "  browserfilter [lindex $filterListB $i]"
	puts $fid "  serverfilter  [lindex $filterListS $i]"
	puts $fid "  browserview   [lindex $filterviewListB $i]"
	puts $fid "  serverview    [lindex $filterviewListS $i]"
	set columns [lindex $colList $i]
	foreach j $columns {
	    puts $fid "  column $j"
	}
	puts $fid ""
    }

    close $fid
}

body CONFIG::isKeyWord {word} {
    set isOne 0
    set kwlist [list browserfilter browserview col column \
		     geometry mapped maxlines screen serverfilter \
		     serverview source type update viewer]
    foreach i $kwlist {
	if {"$word" == "$i"} {
	   set isOne 1
	   break
	}
    }
    return $isOne
}

body CONFIG::readConfig {} {
    if {[catch {set fid [open $file r]} msg]} {
	error "Cannot open file \"$file\""
    }

    unset viewers
    set viewers(names) ""
    
    # read config file lines
    set i 1
    while {[gets $fid data] >= 0} {
	set line($i) $data
	incr i
    }
    set limit $i
    close $fid

    set init 0
    set key ""
    for {set i 1} {$i < $limit} {incr i} {
#puts "LINE $i, $line($i)"
	set items    [parseLine $line($i)]
	set numItems [llength $items]
	set key      [string tolower [lindex $items 0]]

	if {($numItems < 2) || ([isKeyWord $key] == 0)} {
	    continue
	}
	
	if {($init == 0) && ("$key"!="viewer")} {
	    # No viewer name given (Jie's default config file),
	    # so make one up.
	    set name view_[pid]
	    set init 1
	    lappend viewers(names) $name
	    set colNum 1
	} elseif {"$key"=="viewer"} {
	    set name [lindex $items 1]
	    set init 1
	    lappend viewers(names) $name
	    set colNum 1
	    continue
	}
#puts "key = $key"
	
	switch "$key" {
	    col -
	    column {
			if {$numItems == 4} {
			    set title [lindex $items 1]
			    set tag   [lindex $items 2]
			    set width [lindex $items 3]
			    set viewers($name,col,$colNum) [list $title $tag $width]
			    set viewers($name,col) $colNum
#puts "col: $title, $tag, $width"
#puts "col: num = $viewers($name,col)"
			    incr colNum
			} else {
			    continue
			}
	    }
	    browserfilter {
			# reparse line
			set items [parseAsList $line($i)]
			set viewers($name,bfilter) [lindex $items 1]
#puts "browserfilter: [lindex $items 1]"
	    }
	    browserview {
			set viewers($name,bview) [lindex $items 1]
#puts "browserview: [lindex $items 1]"
	    }
	    serverfilter {
			# reparse line
			set items [parseAsList $line($i)]
			set viewers($name,sfilter) [lindex $items 1]
#puts "serverfilter: [lindex $items 1]"
	    }
	    serverview {
			set viewers($name,sview) [lindex $items 1]
#puts "serverview: [lindex $items 1]"
	    }
	    type {
			set type [string tolower [lindex $items 1]]
			set viewers($name,type) $type
#puts "type: $type"
	    }
	    screen {
			set viewers($name,screen) [lindex $items 1]
#puts "screen: [lindex $items 1]"
	    }
	    update {
			set viewers($name,update) [lindex $items 1]
#puts "update: [lindex $items 1]"
	    }
	    mapped {
			set viewers($name,mapped) [lindex $items 1]
#puts "mapped: [lindex $items 1]"
	    }
	    maxlines {
			set viewers($name,maxlines) [lindex $items 1]
#puts "maxlines: [lindex $items 1]"
	    }
	    geometry {
			set viewers($name,geometry) [lindex $items 1]
#puts "geometry: [lindex $items 1]"
	    }
	    source {
			if {$numItems == 4} {
			    set table  [lindex $items 1]
			    set db     [lindex $items 2]
			    set host   [lindex $items 3]
			    set viewers($name,source) [list $table $db $host]
#puts "source: $table, $db, $host"
			} elseif {$numItems == 2} {
			    set port [lindex $items 1]
			    if {[isInt $port]} {
				set viewers($name,source) [list $port]
			    }
#puts "source: $port"
			} else {
			    tk_dialog .err WARNING "Wrong # of args in \"source\" config file statement for viewer \"$name\"" {error} 0 DISMISS
			}
	    }
	}
    }
	
    # See if proper info exists for a viewer. If not, remove it.
    set names $viewers(names)
    foreach name $names {
	set deleteIt 0
	if {[info exists viewers($name,type)] == 0} {
	    set viewers($name,type) cmlog
	} elseif {("$viewers($name,type)" != "cmlog") && ("$viewers($name,type)" != "msql")} {
	    tk_dialog .err WARNING "Need to specify a data type for viewer \"$name\" of either \"cmlog\" or \"msql\"" {error} 0 DISMISS
	    set deleteIt 1  
	}
	
	if {[info exists viewers($name,source)] == 0} {
	    set viewers($name,source) 0
	    #tk_dialog .err WARNING "Need to specify a data source for viewer \"$name\" in config file" {error} 0 DISMISS
	    #set deleteIt 1
	}
	
	if {[info exists viewers($name,bfilter)] == 0} {
	    set viewers($name,bfilter) {}
	}
    
	if {[info exists viewers($name,bview)] == 0} {
	    # 1= view only, 0=filter out
	    set viewers($name,bview) 1
	}
    
	if {[info exists viewers($name,sfilter)] == 0} {
	    set viewers($name,sfilter) {}
	}
    
	if {[info exists viewers($name,sview)] == 0} {
	    set viewers($name,sview) 1
	}
    
	if {[info exists viewers($name,screen)] == 0} {
	    set viewers($name,screen) ""
	}
    
	if {[info exists viewers($name,update)] == 0} {
	    # 1= monitor on, 0=monitor off
	    set viewers($name,update) 0
	}
    
	if {[info exists viewers($name,geometry)] == 0} {
	    set viewers($name,geometry) {}
	}
    
	if {[info exists viewers($name,mapped)] == 0} {
	    # 1= mapped, 0=unmapped
	    set viewers($name,mapped) 1
	}
    
	if {[info exists viewers($name,maxlines)] == 0} {
	    # 0 = view all lines when monitoring
	    set viewers($name,maxlines) 0
	}

	if {$deleteIt == 1} {
	    set arrayElements [array names viewers "$name,*"]
	    foreach i $arrayElements {
		unset viewers($i)
	    }
	    set indx [lsearch -exact $viewers(names) $name]
	    if {$indx != -1} {
		set viewers(names) [lreplace $viewers(names) $indx $indx]
	    }
	}
    }

    if {"$viewers(names)" == ""} {
	tk_dialog .err {HEADS UP} "No valid data in config file" {error} 0 DISMISS
    }
}

body CONFIG::parseLine {line} {
    set line [string trim $line]
    set notdone 1
    set items ""
    
    while {$notdone} {
	if {"[string index $line 0]" == "\{"} {
	    set endIndex [string first \} $line]
	    if {$endIndex == -1} {
		return -1
	    } else {
		set item [string range $line 1 [expr $endIndex - 1]]
		set line [string range $line [expr $endIndex + 1] end]
		set line [string trimleft $line]
		lappend items $item
	    }
	} else {
	    if {[scan $line "%s" item] < 0} {
		set notdone 0
	    } else {
		set length [string length $item]
		set line   [string range $line $length end]
		set line   [string trimleft $line]
		lappend items $item
	    }
	}
	
	if {"$line" == ""} {set notdone 0}
    }
#puts "parseLine: $items"
    return $items
}

body CONFIG::parseAsList {line} {
    set line [string trim $line]
    scan $line "%s" key
    set length [string length $key]
    set line   [string range $line $length end]
    set line   [string trimleft $line]
#puts "parseAsList [list $key $line]"
    return [list $key $line]
}

proc parsemsql {} {
    if {[catch {msql connectPort msq $port $host} msg]} {
	error "Cannot connect to msql server on $host"
    }

    # get config info from msql database

    set dblist [msq get databases]
    if {[lsearch -exact $dblist $dbase] == -1} {
	error "Database \"$dbase\" not found"
    }

    msq set database $dbase

    set tablelist [msq get tables]
    if {[lsearch -exact $tablelist $table] == -1} {
	error "Table \"$table\" not found"
    }
}


############################################################
#
#    Class MAINWIN
#	
#    Purpose:
#	This class is used to create/control the initial/main window
#
#    Variables:
#	configObject - object to read/save config files
#	display - force windows to use this screen if not ""
#	viewers - list of all the viewer names (not object names).
#	widget  - array of widget names
#	entry   - array used to store viewer creation information
#
#    Methods:
#	get        - get class variable values
#	exitcmd    - "Do you really wanna quit?" window
#	loadConfig - get data from config file and create a viewers
#	saveConfig - get data from viewers and create a config file
#	addViewer  - window to help adding a viewer
#	removeViewer    - deletes a viewer
#	createViewer    - creates a viewer
#	createViewerCmd - used by "addViewer" to properly call createViewer.
#			  It avoids getting into quoting hell.
#	postDelViewers  - defines the menu options to delete viewers
#	configFileWin   - window to choose config file name
#
#
############################################################

class MAINWIN {
    constructor {configObject {file ""} {screen ""}} {}
    destructor  {}
    
    private variable configObj ""
    private variable display   ""
    private variable viewers   ""
    private variable widget    ""
    private common   entry

    public  method get            {option} {}
    public  method loadConfig     {file}   {}
    public  method saveConfig     {file}   {}
    
    public  method addViewer       {}  {}
    public  method removeViewer    {v} {}
    private method createViewer    {name type screen update bfilter bview sfilter sview dataObjList mapped maxlines columnList geometry} {}
    private method createViewerCmd {} {}
    private method postDelViewers  {} {}
    
    private method configFileWin {mode} {}
    private method exitcmd       {}     {}    
}

body MAINWIN::get {option} {
    # return values of all variables
    switch -- $option {
	-configObj {return $configObj}
	-widget    {return $widget}
	-viewers   {return $viewers}
    }
    error "MAINWIN::get bad option \"$option\""
}

body MAINWIN::constructor {configObject {file ""} {screen ""}} {
# configObject: name of object to read/save config files
# screen:       force display to this screen (from command line)
# file:         name of config file (from command line)

    global env images_library
    
    set configObj $configObject
    set w .cmsglog
    set widget $w
    wm withdraw .

    if {[catch {set thisDisplay $env(DISPLAY)} msg] == 1} {
	if {[catch {set host [exec hostname]} msg] != 1} {
	    set thisDisplay "$host:0.0"
	} else {
	    error "Set DISPLAY environment variable"
	}
    }
    
    toplevel $w -screen $thisDisplay
    wm withdraw $w
    wm iconname $w "cmsglog"
    wm minsize  $w 300 35
    wm geometry $w +0+0    
    update
    set images_library $env(CODA)/common/images
    set coda_logo [image create photo -file $images_library/gif/RCLogo.gif]
    
    menubutton $w.file -bg gray80 -text "File" -bd 1 -relief raised -menu $w.file.m -width 8
    menu $w.file.m
    $w.file.m add com -label "Load Config File" -command [code $this configFileWin load]
    $w.file.m add com -label "Save Config File" -command [code $this configFileWin save]

    menubutton $w.view -bg gray80 -text "Viewers" -bd 1 -relief raised -menu $w.view.m -width 8
    menu $w.view.m 
    $w.view.m add com -label "Create" -command [code $this addViewer]
    $w.view.m add cas -label "Delete" -menu $w.view.m.del
    $w.view.m add sep

    menu $w.view.m.del -postcommand [code $this postDelViewers]
        
    button $w.but  -bg gray80 -text "Exit" -command [code $this exitcmd]
    label  $w.logo -padx 0 -pady 0 -image $coda_logo

    pack $w.logo -side left  -padx 5
    pack $w.file -side left  -padx 5
    pack $w.view -side left  -padx 5
    pack $w.but  -side right -padx 5

    wm deiconify $w
    update
    
    if {"$screen" != ""} {
 	set display $screen
    }
    if {"$file" != ""} {
	$this loadConfig $file 
    }
}


body MAINWIN::removeViewer {v} {
    set index [lsearch -exact $viewers $v]
    if {$index == -1} {return}
    set viewers [lreplace $viewers $index $index]
    $widget.view.m     delete $v
    $widget.view.m.del delete $v
}

body MAINWIN::postDelViewers {} {
    $widget.view.m.del delete 0 last
    foreach i $viewers {
	$widget.view.m.del add com\
		-label "$i"\
		-command [code "$this removeViewer $i
				delete object view_$i"]
    }
}

body MAINWIN::configFileWin {mode} {
    global env
    if {"[string tolower $mode]" == "load"} {
	set mode load
	set Mode Load
    } elseif {"[string tolower $mode]" == "save"} {
	set mode save
	set Mode Save
    } else {
	tk_dialog .err WARNING "usage: configFile load\n       configFile save" {error} 0 DISMISS
	return
    }
    
    set w .fbox
    if {[winfo exists $w]} {
	wm withdraw  $w
	wm title $w "$Mode Config File"
	$w.file config -command [code $this ${mode}Config]
	$w.bot.mode config -text " $Mode"
	wm deiconify $w
	return
    }
    
    set file ""
    set configfile ""
    catch {set configfile $env(CMLOG_CONFIG)}
    if {"$configfile" != ""} {
	if {[file readable "$configfile"]} {
	    set file "$configfile"
	}
    } else {
	if {[file readable ./.cmlog]} {
	    set file ./.cmlog
	} else {
	    set home ""
	    catch {set home $env(HOME)}
	    if {"$home" != ""} {
		if {[file readable "$home/.cmlog"]} {
		    set file "$home/.cmlog"
		}
	    }
	}
    }
    
    toplevel $w 
    wm protocol $w WM_DELETE_WINDOW "wm withdraw $w"
    wm title $w "$Mode Config File"
    
    tixFileSelectBox $w.file\
	-dir "[file dirname $file]"\
	-pattern "*[file extension $file]"\
	-command [code $this ${mode}Config]
    pack $w.file -fill both -expand 1
    
    frame  $w.bot
    button $w.bot.mode   -text " $Mode" -command "$w.file invoke;wm withdraw $w"
    button $w.bot.cancel -text "Cancel" -command "wm withdraw $w"
    
    pack $w.bot.mode $w.bot.cancel -side left -fill x -expand 1
    pack $w.bot -fill both
}

body MAINWIN::saveConfig {file} {
    catch {delete object con}
    foreach i $viewers {
	set v view_$i
	lappend nameList     $i
	lappend screenList  [$v get -screen]
	lappend updateList  [$v get -update]
	lappend typeList    [$v get -dataType]
	lappend filterListB [$v get -bfilter]
	lappend filterListS [$v get -sfilter]
	lappend filterviewListB [$v get -bfilterview]
	lappend filterviewListS [$v get -sfilterview]
	lappend sourceList   [$v get -dataSource]
	lappend mappedList   [$v get -mapped]
	lappend maxlinesList [$v get -maxlines]
	lappend geomList     [$v get -geometry]

	set titles [$v get -titles]
	set tags   [$v get -tags]
	set widths [$v get -widths]
	set length [llength $titles]
	set cols   ""
	for {set j 0} {$j < $length} {incr j} {
	    lappend cols [list [lindex $titles $j] [lindex $tags $j] [lindex $widths $j]]
	}
	lappend colList $cols
    }
    
    # Create object to save config file
    if {[catch {CONFIG con $file write} msg] == 1} {
	tk_dialog .err WARNING "saveConfig: $msg" {error} 0 DISMISS
	return
    }
    if {[catch {con writeConfig \
		$nameList \
		$typeList \
		$screenList \
		$updateList \
		$sourceList \
		$mappedList \
		$maxlinesList \
		$geomList \
		$colList \
		$filterListB \
		$filterListS \
		$filterviewListB \
		$filterviewListS} msg] == 1} {
	tk_dialog .err WARNING "saveConfig: $msg" {error} 0 DISMISS
    }
}

body MAINWIN::loadConfig {file} {
    # Create object to read config file
    catch {delete object con}
    if {[catch {CONFIG con $file read} msg] == 1} {
	tk_dialog .err WARNING "loadConfig: $msg" {error} 0 DISMISS
	return
    }
    if {[catch {con readConfig} msg] == 1} {
	tk_dialog .err WARNING "loadConfig: $msg" {error} 0 DISMISS
	return
    }
    
    # get config data
    set index 0
    set configViewers [con get -viewers]
    foreach i $configViewers {
	set type     [lindex [con get -types]     $index]
	set screen   [lindex [con get -screens]   $index]
	set update   [lindex [con get -updates]   $index]
	set bfilter  [lindex [con get -bfilters]  $index]
	set sfilter  [lindex [con get -sfilters]  $index]
	set bview    [lindex [con get -bviews]    $index]
	set sview    [lindex [con get -sviews]    $index]
	set source   [lindex [con get -sources]   $index]
	set mapped   [lindex [con get -maps]      $index]
	set maxlines [lindex [con get -maxlines]  $index]
	set geometry [lindex [con get -geometrys] $index]
	set colList  [lindex [con get -columns]   $index]
	if {"$display" != ""} {
	    set screen $display
	}
	if {[catch {createViewer $i $type $screen $update $bfilter $bview $sfilter $sview $source $mapped $maxlines $colList $geometry} msg] == 1} {
	    tk_dialog .err WARNING "loadConfig: $msg" {error} 0 DISMISS
	}
	incr index
    }
}

body MAINWIN::exitcmd {} {
    set ret [tk_dialog .err WARNING "Do you really want to quit?" {} 1 YES NO]
    if {$ret == 0} {exit}
}


body MAINWIN::addViewer {} {
    # definition of the "Add Viewers" window
     
    set w .addview
    if {[winfo exists $w]} {
	wm withdraw  $w
	wm deiconify $w
	return
    }
    
    toplevel $w    
    wm title $w "Add A Viewer"
    wm protocol $w WM_DELETE_WINDOW "wm withdraw $w"

    frame $w.top
    label $w.top.title -text "Add A Viewer"\
	-justify center -anchor center -bg white -bd 2 -relief groove
    tixComboBox $w.top.name -label "Name: " \
	    -editable true \
	    -history 1 \
	    -options {
		entry.width 15
		label.padY 5
		label.width 10
		label.anchor e
	    }
    tixComboBox $w.top.screen -label "Screen: " \
	    -editable true \
	    -history 1 \
	    -options {
		entry.width 15
		label.padY 5
		label.width 10
		label.anchor e
	    }

    label $w.top.type -text "Data Types:" -bd 2 -relief groove\
	-justify center -anchor center -bg gray95 
    
    pack $w.top.title  -fill x -ipady 8
    pack $w.top.name   -pady 2 -anchor center
    pack $w.top.screen -pady 2 -anchor center
    pack $w.top.type   -fill x -ipady 4
    
    frame $w.mid
    frame $w.mid.f1 -bd 2 -relief groove -width 150 -height 175
    frame $w.mid.f2 -bd 2 -relief groove -width 150 -height 175
    pack $w.mid.f1 $w.mid.f2 -side left -fill both
    pack propagate $w.mid.f1 0
    pack propagate $w.mid.f2 0
    
    radiobutton $w.mid.f1.r1\
    	-bd 2\
    	-relief groove\
    	-bg gray95\
    	-variable [scope entry(type)]\
	-value cmlog -text "cmlog"\
	-command "$w.mid.f1.tc1 config -state normal
		  $w.mid.f2.cb1  config -state disabled
		  $w.mid.f2.cb2  config -state disabled
		  $w.mid.f2.cb3  config -state disabled"
    set entry(type) cmlog
    
    label $w.mid.f1.l1 -justify center -anchor center -text "Port Number"
    tixControl $w.mid.f1.tc1\
	-integer 1\
	-min 0\
	-max 10000\
	-value 0\
	-variable [scope entry(port)]
   
    pack $w.mid.f1.r1 -fill x
    pack $w.mid.f1.l1\
	 $w.mid.f1.tc1 -fill x -padx 10 -pady 2
	 
    radiobutton $w.mid.f2.r1\
    	-bd 2\
    	-relief groove\
    	-bg gray95\
	-value msql -text "msql "\
	-variable [scope entry(type)]\
	-command "$w.mid.f1.tc1 config -state disabled
		  $w.mid.f2.cb1  config -state normal
		  $w.mid.f2.cb2  config -state normal
		  $w.mid.f2.cb3  config -state normal"
    $w.mid.f2.r1 config -state disabled
		  
    label $w.mid.f2.l1 -justify center -anchor center -text "Host"
    tixComboBox $w.mid.f2.cb1
    $w.mid.f2.cb1 appendhistory clon00
    $w.mid.f2.cb1 appendhistory alcor
    $w.mid.f2.cb1 appendhistory alioth
    label $w.mid.f2.l2 -justify center -anchor center -text "Database"
    tixComboBox $w.mid.f2.cb2
    label $w.mid.f2.l3 -justify center -anchor center -text "Table"
    tixComboBox $w.mid.f2.cb3
		  
    pack $w.mid.f2.r1 -fill x
    pack $w.mid.f2.l1\
	 $w.mid.f2.cb1\
	 $w.mid.f2.l2\
	 $w.mid.f2.cb2\
	 $w.mid.f2.l3\
	 $w.mid.f2.cb3\
	 -fill x -padx 10 -pady 2

    frame $w.bot
    button $w.bot.but1 -text "OK"     -width 10 -command [code "$this createViewerCmd"]
    button $w.bot.but2 -text "Cancel" -width 10 -command "destroy $w"
    
    pack $w.bot.but1 $w.bot.but2 -side left -fill x -expand 1
    
    pack $w.top -fill x
    pack $w.mid -fill both
    pack $w.bot -fill x
    
    $w.mid.f2.cb1 config -state disabled
    $w.mid.f2.cb2 config -state disabled
    $w.mid.f2.cb3 config -state disabled
}

body MAINWIN::createViewerCmd {} {
    set w .addview
    set name   [$w.top.name   cget -selection]
    set screen [$w.top.screen cget -selection]
    regsub -all { } $name {_} name
    
    set type $entry(type)
    if {"$type" == "cmlog"} {
	set dataObjList $entry(port)
    } elseif {$type == "msql"} {
	set host  [$w.mid.f2.cb1 cget -selection]
	set db    [$w.mid.f2.cb2 cget -selection]
	set table [$w.mid.f2.cb3 cget -selection]
	set dataObjList [list $host $db $table]
    } else {
	return
    }
    
    if {[catch {createViewer $name $type $screen 0 {} 1 {} 1 $dataObjList 1 {} {}} msg] == 1} {
	tk_dialog .err WARNING "createViewerCmd: $msg" {error} 0 DISMISS
    }
    destroy $w
}

body MAINWIN::createViewer {name type screen update bfilter bview sfilter sview dataObjList mapped maxlines columnList geometry} {
    global env
    # first create data object
    
    if {"$name" == ""} {
	error "Need to specify a viewer name"
    }
    if {[lsearch -exact $viewers $name] != -1} {
	error "The viewer name \"$name\" already exists"
    }
    
    set dataObjName   data_$name
    set viewerObjName view_$name
    set cmdName	      cmd_$name
    set arrayName     array_$name

    if {"$type" == "cmlog"} {
	set port $dataObjList
	CMLOGDATA ::$dataObjName $cmdName $arrayName $port
	if {[catch {$dataObjName connect} msg] == 1} {
	    delete object ::$dataObjName
	    error "createViewer: $msg"
	}
    } elseif {"$type" == "msql"} {
	set table [lindex $dataObjList 0]
	set db    [lindex $dataObjList 1]
	set host  [lindex $dataObjList 2]
	MSQLDATA ::$dataObjName $cmdName $arrayName $table $db $host
    } else {
	error "The viewer has an unknown data type"
    }
#puts "createviewer: $name $bfilter $bview $sfilter $sview $dataObjName $mapped $maxlines \{$columnList\} $geometry $update $screen"
    if {[catch {VIEWER ::$viewerObjName $name $bfilter $bview $sfilter $sview $dataObjName $mapped $maxlines $columnList $geometry $update $screen} msg] == 1} {
#puts "createviewer error: delete object $dataObjName"
	delete object $dataObjName
	error "createViewer: $msg"
    }
    
    # put stuff into mainwindow menu
    $widget.view.m add com\
	-label "$name"\
	-command "$viewerObjName map 1"
    $widget.view.m.del add com\
	-label "$name"\
	-command [code "$this removeViewer $name
			delete object $viewerObjName"]

    lappend viewers $name
    return 0
}

#####################################################################
#
#    Class DATA
#	
#    Purpose:
#	This is the standard interface used by the VIEWER objects
#	to get their data. Each implementation of a data class needs
#	these functions and should inherit this class.
#
#    Variables:
#	viewers   - list of all the viewer names (not object names).
#	type      - type of data; only cmlog is implemented
#	dataArray - name of array used to store data
#	dataCmd   - name of command used to query, disconnect, etc.
#	dataPts   - number of data "rows"
#
#    Methods:
#	connect      - makes a connection to data server,
#			returns 1 if error, 0 if success
#	disconnect   - disconnects from data server
#	getData      - gets the data from the server and places it in an
#			array (e.g. data(1) ... data(N)).
#	monitorData  - monitor for new, incoming data
#	numDataRows  - returns the number of data rows (e.g. N)
#	addViewer    - although not currently implemented, in the future
#			one may want to attach more than 1 viewer to a
#			single data object
#	removeViewer - remove viewer from "viewers" list
#	
#
#####################################################################

class DATA {
    constructor {} {}
    destructor  {}
        
    protected variable viewers ""
    protected variable type    cmlog

    protected variable dataArray  ""
    protected variable dataCmd    ""
    protected variable dataPts    ""
    
    public method connect
    public method disconnect
    public method getData
    public method monitorData
    public method numDataRows  {}       {return $dataPts}
    public method addViewer    {viewer} {lappend viewers $viewer}
    public method removeViewer {viewer} {}
}

body DATA::removeViewer {viewer} {
    set index [lsearch -exact $viewers $viewer]
    if {$index != -1} {
	set viewers [lreplace $viewers $index $index]
    }
}

#########################################################
#
#    Class CMLOGDATA
#	
#    Purpose:
#	This is the class which handles all the cmlog type of data.
#
#    Variables:
#	port   - port number of cmlogServer. If zero, the port number
#		 used is the one compiled into the tcl-cmlog interface
#		 software.
#	filter - filter condition evaluated by cmlog server (see setFilter)
#	oplist - list of allowed operators for filter condition
#
#    Methods:
#	cmlog_monitorIn - when the data monitor is turned on, this func
#			  is the callback
#	cmlog_queryIn   - when the server is queried, this func is the
#			  callback
#	setFilter       - set the variable "filter". It takes a list of filter 
#			  information as the first arg and whether this is to
#			  "filter out" or "view only" for the second arg.
#			  It then constructs the filter condition that's
#			  evaluated by the cmlog server.
#	
#    Function:
#	The way this works is the following. In creating this object
#	one specifies a global array name in which the queried data is
#	stored and also specifies a command name to be created upon the
#	execution of the "connect" procedure.
#
#	When the "getData" function is used, a callback func is executed
#	in the server sending the data. This callback puts the cmlog
#	data (cdev data actually) into the above-mentioned array. Each
#	element of the array (e.g. data(1) ... data(N)) contains a row
#	(tcl list) of tag-value pairs. In addition, the following array
#	elements give other vital info:
#	    data(length) - number of data rows
#	    data(qstate) - state of the query either: notfound, paused,
#			   success, or error. Notfound means no data in
#			   selected time period. Paused means user asked
#			   for a max of N data pts between time1 and time2.
#			   The query looked at N pts and found some that
#			   met the search criteria, but time2 was not reached
#			   before N pts were examined. Success means that
#			   the query was successful,and error means error.
#	    data(mstate) - state of the monitor either: cbkfinished, error,
#			   or success. Cbkfinished means that the monitor
#			   was turned off.
#	    data(append) - =1 if getData orginally called with the option to
#			   append data, 0 otherwise
#	    data(start)  - the row number for the new data to be appended at.
#			   If data(append) = 0, this is =1.
#	    data(timestart) - the starting time in seconds of the getData query.
#	    data(timefinal) - the ending time in seconds of the getData query.
#
#	The callback func also sets the variable data(mdataIn) or data(qdataIn)
#	to some value when all data has been received or some error has occurred.
#	A trace has been set up for each, meaning that when data(mdataIn) is set,
#	"cmlog_monitorIn" is executed, and when data(qdataIn) is set,
#	"cmlog_queryIn" is executed. These routines in turn, load data as needed.
#
#	Notice that all trace-related code must be run at the global level
#	or it doesn't work; hence all the uplevel's.
#
#
#########################################################

class CMLOGDATA {
    inherit DATA
    constructor {cmdName arrayName {portNum 0}} {}
    destructor  {catch {unset $dataArray; disconnect}}  
      
    private variable port   0
    private variable filter ""
    private common   oplist ""
   
    public  method get         {option}
    public  method connect     {}
    public  method disconnect  {}
    public  method getData     {{append 1} {startTime 0.0} {endTime ""} {numItems ""}}
    public  method monitorData {mode}
    public  method setFilter   {f fview} {}
   
    private method cmlog_monitorIn {name el op} {}
    private method cmlog_queryIn   {name el op} {}
}

body CMLOGDATA::cmlog_monitorIn {name el op} {
global $dataArray
    if {"$op" == "u"} {
	if {"$el" != ""} {
#puts "Min: Reset trace on ${name}($el); op = unset\n"
	    uplevel #0 [list trace variable ${name}($el) w [subst {[code $this cmlog_monitorIn]}]]
	    uplevel #0 [list trace variable ${name}($el) u [subst {[code $this cmlog_monitorIn]}]]
	}
    } elseif {"$op" == "w"} {
#set dataPts [set ${dataArray}(length)]
set mstate  [set ${dataArray}(mstate)]
#puts "Min: Reset trace on ${name}($el); op = write; mstate = $mstate\n"
	foreach i $viewers {
	    if {"$mstate" == "success"} {
		$i load 1 monitor
	    } elseif {"$mstate" == "error"} {
		tk_dialog .err WARNING "cmlog_monitorIn: [set ${dataArray}(error)]" {error} 0 DISMISS
	    } elseif {"$mstate" == "cbkfinished"} {
		# monitor turned off
	    }
	}
    }
}


body CMLOGDATA::cmlog_queryIn {name el op} {
global $dataArray
#puts "cmlog_queryIn"
    if {"$op" == "u"} {
	if {"$el" != ""} {
#puts "Qin: Reset trace on ${name}($el)\n"
	    uplevel #0 [list trace variable ${name}($el) w [subst {[code $this cmlog_queryIn]}]]
	    uplevel #0 [list trace variable ${name}($el) u [subst {[code $this cmlog_queryIn]}]]
	}
    } elseif {"$op" == "w"} {
#puts "array names = [array names $dataArray]"
	set dataPts [set ${dataArray}(length)]
	set qstate  [set ${dataArray}(qstate)]
	set append  [set ${dataArray}(append)]
#puts "qstate  = $qstate"
#puts "dataPts = $dataPts"
#puts "append  = $append"
	foreach i $viewers {
	    if {"$qstate" == "error"} {
		tk_dialog .err WARNING "cmlog_queryIn: [set ${dataArray}(error)]" {error} 0 DISMISS
		$i busy off
	    } elseif {"$qstate" == "notfound"} {
		$i clear
		$i busy off
	    } elseif {"$qstate" == "paused"} {
		$i load $append query
		foreach j [set ${dataArray}([set ${dataArray}(length)])] {
		    if {"[lindex $j 0]" == "cmlogTime"} {
			set endtime [lindex $j 1]
		    }
		}
		$i loadNextWin [set ${dataArray}(timefinal)] [expr .001+$endtime]
	    } elseif {"$qstate" == "success"} {
		$i load $append query
	    }
	}
    }
}

body CMLOGDATA::destructor {} {
    global $dataArray
    uplevel #0 [list trace vdelete ${dataArray}(mdataIn) w [subst {[code $this cmlog_monitorIn]}]]
    uplevel #0 [list trace vdelete ${dataArray}(mdataIn) u [subst {[code $this cmlog_monitorIn]}]]
    uplevel #0 [list trace vdelete ${dataArray}(qdataIn) w [subst {[code $this cmlog_queryIn]}]]
    uplevel #0 [list trace vdelete ${dataArray}(qdataIn) u [subst {[code $this cmlog_queryIn]}]]
    catch {disconnect}
}

body CMLOGDATA::constructor {cmdName arrayName {portNum 0}} {
    if {"[info commands $cmdName]" != ""} {
	tk_dialog .err WARNING "CMLOGDATA::connect - \"$cmdArray\" is a command already." {error} 0 DISMISS
	error "CMLOGDATA::connect - \"$cmdArray\" is a command already."
    }
    
    if {([array exists $arrayName]==1) || ([info exists $arrayName]==1)} {
	tk_dialog .err WARNING "CMLOGDATA::connect - \"$cmdArray\" already exists." {error} 0 DISMISS
	error "CMLOGDATA::connect - \"$cmdArray\" already exists."
    }
    if {[regexp {^[0-9]+$} $portNum] != 1} {
	tk_dialog .err WARNING "CMLOGDATA::connect - \"port\" needs to be integer > 0" {error} 0 DISMISS
	error "CMLOGDATA::connect - \"port\" needs to be integer > 0"
    }

    # list of allowed operators for filter condition
    set oplist    [list < <= > >= == != like]
    
    set port      $portNum
    set dataArray $arrayName
    set dataCmd   $cmdName
    
    # DON'T MESS WITH THE UPLEVELS !!!
    
    global $dataArray
    uplevel #0 set ${dataArray}(start)   0
    uplevel #0 set ${dataArray}(length)  0
    uplevel #0 set ${dataArray}(mdataIn) 0
    uplevel #0 [list trace variable ${dataArray}(mdataIn) w [subst {[code $this cmlog_monitorIn]}]]
    uplevel #0 [list trace variable ${dataArray}(mdataIn) u [subst {[code $this cmlog_monitorIn]}]]
    uplevel #0 set ${dataArray}(qdataIn) 0
    uplevel #0 [list trace variable ${dataArray}(qdataIn) w [subst {[code $this cmlog_queryIn]}]]
    uplevel #0 [list trace variable ${dataArray}(qdataIn) u [subst {[code $this cmlog_queryIn]}]]
}

body CMLOGDATA::connect {} {
    set cmd "cmlog connect $dataCmd $dataArray"
    if {$port > 0} {
	set cmd "$cmd $port"
    }
    
    if {[catch {eval $cmd} msg]} {
	set port 0
	error "Cannot connect to cmlogServer: $msg"
    }
}

body CMLOGDATA::disconnect {} {
    $dataCmd disconnect
}

body CMLOGDATA::getData {{append 1} {startTime 0.0} {endTime ""} {numItems ""}} {
    if {"$endTime" == ""} {set endTime [ns_systime]}
    
    if {[isInt $numItems] == 1} {
#puts "getData: $dataCmd query $append $startTime $endTime $numItems $filter"
	if {"$filter" != ""} {
	    $dataCmd query $append $startTime $endTime $numItems $filter
	} else {
	    $dataCmd query $append $startTime $endTime $numItems
	}
    } else {
#puts "getData: $dataCmd query $append $startTime $endTime 0 $filter"
	if {"$filter" != ""} {
	    $dataCmd query $append $startTime $endTime 0 $filter
	} else {
	    $dataCmd query $append $startTime $endTime
	}
    }
}

body CMLOGDATA::monitorData {mode} {
    if {("$mode" != "on") && ("$mode" != "off")} {
	puts "monitorData mode needs to be \"on\" or \"off\""
	return
    }
#puts "monitorData: $dataCmd monitor $mode data $filter"
    if {"$filter" != ""} {
	$dataCmd monitor $mode data $filter
    } else {
	$dataCmd monitor $mode data
    }
}

body CMLOGDATA::get {option} {
    # return values of all variables
    switch -- $option {
	-viewers   {return $viewers}
	-port      {return $port}
	-type      {return $type}
	-dataArray {return $dataArray}
	-dataCmd   {return $dataCmd}
	-dataPts   {return $dataPts}
	-source    {return [list $port]}
 	-filter    {return $filter}
 	-oplist    {return $oplist}
   }
    error "CMLOGDATA::get bad option \"$option\""
}

body CMLOGDATA::setFilter {filterlist filterview} {
    set finallist ""
    set count_lpar 0
    set count_rpar 0
   
    foreach i $filterlist {
	set arg [lindex $i 0]
	set typ [lindex $i 1]
	if {"$typ" == "lpar"} {
	    # count left parentheses
	    incr count_lpar 
	} elseif {"$typ" == "rpar"} {
	    # count right parentheses
	    incr count_rpar	
	} elseif {"$typ" == "val"} {
	    # put single quotes around val if string
	    if {[isNum $arg] == 0} {
		set arg \'$arg\'
	    }
	} elseif {"$typ" == "op"} {
	    if {("$arg" == "match")||("$arg" == "notmatch")} {
		error "\"match\" & \"notmatch\" operators are not supported by the cmlog server"
	    }
	} elseif {"$typ" == "conj"} {
	    # nothing
	}
	
	lappend finallist [list $arg $typ]
    }
    
    set filter ""
    foreach i $finallist {
	set filter "$filter[lindex $i 0] "
    }
    
    if {("$filterview" == "0")&&("$filter" != "")} {
 	set filter "!($filter)"
    }
     
#puts "setFilter, finallist  = $finallist"
#puts "setFilter, filter     = $filter"
}

#########################################################
#
#	This implementation of MSQLDATA is not complete
#	and does NOT work.
#
#########################################################

class MSQLDATA {
    inherit DATA
    constructor {cmdName arrayName _table _db {_host ""}} {}
    destructor  {catch {unset $dataArray; disconnect}}  
      
    private variable host  ""
    private variable db    ""
    private variable table ""
   
    public method get         {option}
    
    public method connect     {}
    public method disconnect  {}
    public method getData     {{startTime 0.0} {endTime ""}}
    public method monitorData {mode}
}

body MSQLDATA::constructor {cmdName arrayName _table _db {_host ""}} {
    if {"[info commands $cmdName]" != ""} {
	tk_dialog .err WARNING "MSQLDATA::connect - \"$cmdArray\" is a command already." {error} 0 DISMISS
	error
    }
    
    if {([array exists $arrayName]==1) || ([info exists $arrayName]==1)} {
	tk_dialog .err WARNING "MSQLDATA::connect - \"$cmdArray\" already exists." {error} 0 DISMISS
	error
    }
    set dataArray $arrayName
    set dataCmd   $cmdName
    set table     $_table
    set db        $_db
    set host      $_host
    set type	  msql
    
    global $dataArray
}

body MSQLDATA::connect {} {    
    set cmd "msql connect $dataCmd"
    if {"$host" != ""} {
	set cmd "$cmd $host"
    }
    
    if {[catch {eval $cmd} msg]} {
	tk_dialog .err WARNING "Cannot connect to msql Server: $msg" {error} 0 DISMISS
	set host ""
	return 1
    }
    return 0
}

body MSQLDATA::disconnect {} {
    $dataCmd disconnect
}

body MSQLDATA::monitorData {mode} {
}

body MSQLDATA::getData {{startTime 0.0} {endTime ""}} {
    global env
    $dataCmd set database $env(EXPID)
    if {"$endTime" == ""} {set endTime [ns_systime]}
    $dataCmd query $startTime $endTime
    global $dataArray
    upvar #0 $dataArray cmlogData
    set dataPts $cmlogData(length)
}

body MSQLDATA::get {option} {
    # return values of all variables
    switch -- $option {
	-viewers   {return $viewers}
	-host      {return $host}
	-db        {return $db}
	-table     {return $table}
	-type      {return $type}
	-dataArray {return $dataArray}
	-dataCmd   {return $dataCmd}
	-dataPts   {return $dataPts}
	-source    {return [list $table $db $host]}
    }
    error "MSQLDATA::get bad option \"$option\""
}

#####################################################################
#
#    Class VIEWER
#	
#    Purpose:
#	This class implements a window in which to view and manipulate
#	data.
#
#    Variables:
#	name    - viewer's name
#	screen  - screen on which main window is seen (eg. alcor:0.0)
#	printer - printer to send screen dump to
#	address - email address to send screen dump to
#	dialog  - dialog object name for some dialog boxes
#	melt    - =1 automatically see latest additions to viewer
#		  =0 view is stationary even with new data appearing
#	mapped  - =1 means viewer window is mapped
#	dataObject  - name of object which gets data
#	allTagsList - list of data tags to appear as a choice in some menus
#
#    Arrays:
#	widget    - names of various widgets
#	col       - info relating to columns & their data
#	vars      - some global variables needed for widgets
#	filterVal - info for viewer-side filtering of data
#
#    Methods:
#	get    - returns various private variable values
#	map    - maps/unmaps window
#	clear  - clears window and associated info
#	up     - scrolls window up   1 page
#	down   - scrolls window down 1 page
#	freeze - does/undoes automatic scrolling to view new data
#	print  - sends screen data to a printer
#	email  - mails screen data to an address
#	save   - puts up window to save screen data to a file
#	saveCmd- saves screen data to a file
#	
#	load  - takes data from dataObject and puts it in viewer.
#		It is a public method so that the dataObj can use it.
#	loadWin     - defines window used to load data
#	loadWinCmd  - command executed from load button on window.
#		      It actually does a "getData" cmd on dataObj.
#	loadNextWin - defines window to ask if more data wanted when
#		      getting limited no. of pts. It's public so that
#		      the dataObj can use it.
#
#	delColumn   - deletes a column
#	addColumn   - adds    a column
#	moveColumn  - moves   a column
#	widthColumn - changes a column's width
#
#	delColumnWin     - window  to delete column
#	addColumnWin     - window  to add    column
#	moveColumnWin    - window  to move   column
#	widthColumnWin   - window  to change column width
#	addColumnWinCmd  - window's cmd to change column width
#	moveColumnWinCmd - window's cmd to move column
#	columnListCmd    - updates list of columns in tixComboBox's for
#			   above column-related windows 
#
#	filter        - window for message filter definition
#	filterDefine  - updates a filter's definition each time something
#			new is added or it's changed
#	filterSub     - take list of a new filter's constituent parts
#			and make substitutions/changes for its final
#			use when loading data
#	filterApply   - take final filter statement and apply it
#	filterUpdate  - the filter window maintains a list of available
#			tags. Each time data is taken, any new tags are
#			added to this list. (This proc is a callback
#			when "allTagsList" is changed in the "load" method).
#	filterSwitchModes - switches information on the filter window between
#			    the server & browser modes
#
#	textWritable  - make text widgets in viewer window writable
#	textReadable  - make text widgets in viewer window readable only
#	monitorUpdate - turns data monitor & warning sign on/off
#	setUpdate     - define "Update Options" window
#
#	multiScrollX  - cmd for x scrolling of header & data widgets together
#	multiScrollY  - cmd for y scrolling of column widgets together
#	darrowCursor  - change cursor to double arrow <->
#	oldCursor     - change cursor back to default
#	newCursor     - change cursor
#	recordX       - record x position at the start of a column drag. Bound
#			to <Button-1>.
#	resize        - change width of header column header by release of
#			mouse when dragging data col (or vice versa).
#			Bound to <ButtonRelease-1>.
#	drag          - change width of column (either data or header) by
#			dragging. Bound to <B1-Motion>.
#    
#	writeData     - write viewed data to a file descriptor
#	deleteRows    - deletes  all data rows of a given title and value
#	restoreRows   - restores all data rows of a given title and value
#	delMenu       - defines the menu for deletions and restorations
#			(right mouse button over data).
#	regexpWin     - defines window to do a regular expression deletion/
#			restoration of data rows
#	regexpCmd     - cmd associated with regexpWin to delete/restore rows
#			using regular expressions
#
#
####################################################################
#
#    col array definitions:
#
#	col(tags) - list of columns' tags (1 tag/col)
#	col($tag) - list of titles associated with tag $tag
#	col(count) - number of columns
#	col(length) - number of rows being currently displayed
#	col(titles) - list of columns' titles
#	col($title)  - tag associated with title $title
#	col(xstart)   - starting x pos for dragging column edges
#	col(maxlines)  - maximum number of lines to view when monitoring (0=all)
#	col($tag,num)  - count of titles per tag
#	col($title,wid) - width of col $title
#	col(title,del)   - list of title & val for all deletions
#	col($title,dels)  - list of values deleted in col $title
#	col($title,text)   - text widget for col $title
#	col($title,tframe)  - frame widget holding text widget
#	col($title,tdivider) - canvas widget between previous col & $title col
#	col($title,header)   - text widget for col header
#	col($title,hframe)   - frame widget holding text widget for header
#	col($title,hdivider) - canvas widget between prev header col & this
#
#	col(rows)    -	list of integers representing all rows displayed
#			on the screen. For newly loaded data with N+1 pts,
#			it looks like: {0 1 2 3 ... N} . When a row is deleted,
#			its number is removed from list. One can think of it as
#			a list of original line numbers.
#			
#	col(delrows) -  list of deleted row info. Each row's item consists of
#			a list of 2 items: $title@$val of deletion, and an integer
#			representing the original line number of the deleted row -
#			(number taken from col(rows)).
#
####################################################################


class VIEWER {
    constructor {_name bfilterList bfilterview
		 sfilterList sfilterview
		 dataObj _mapped maxlines columnList
		 {geometry ""} {update 0} {scrn ""}} {}
    destructor  {}

    private variable name     ""
    private variable screen   ""
    private variable printer  ""
    private variable address  "cmlog@jlab.org"
    private variable dialog   ""
    private variable melt     1
    private variable mapped   1
    private variable dataObject  ""
    private variable allTagsList ""
   
    private variable widget
    private variable col
    
    private common   vars
    private common   filterVal

    public  method get     {option} {}
    public  method map     {m}      {}
    public  method clear   {} {}
    public  method clearOriginal   {} {}
    public  method up      {} {}
    public  method down    {} {}
    public  method freeze  {} {}
    public  method print   {{a ""}} {}
    public  method email   {{a ""}} {}
    public  method busy    {mode} {}
    public  method save    {{a ""}} {}
    public  method saveCmd {mode file} {}

    public  method load        {append {type query}} {}
    private method loadWin     {{a ""}} {}
    private method loadWinCmd  {time1 time2 append limitIt limit} {}
    public  method loadNextWin {finaltime endtime {a ""}} {}
    
    public  method delColumn   {title}  {}
    public  method addColumn   {title tag width} {}
    public  method moveColumn  {thisCol thatCol} {}
    public  method widthColumn {title width}     {}
    
    private method delColumnWin   {{screen_arg ""}} {}
    private method addColumnWin   {{screen_arg ""}} {}
    private method moveColumnWin  {{screen_arg ""}} {}
    private method widthColumnWin {{screen_arg ""}} {}
    private method addColumnWinCmd  {} {}
    private method moveColumnWinCmd {} {}
    private method columnListCmd    {w} {}

    public  method filter       {{screen_arg ""}} {}
    private method filterApply  {} {}
    private method filterSub    {} {}
    private method filterDefine {type arg} {}
    private method filterUpdate {n el op} {}
    private method filterSwitchModes {mode} {}
    
    public  method textWritable  {}  {}
    public  method textReadable  {}  {}
    public  method setUpdate     {{screen_arg ""}}  {}
    private method setUpdateCmd  {max_lines}  {}
    public  method monitorUpdate {mode} {}

    private method multiScrollX  {hscrCmd hhscrCmd args} {}
    private method multiScrollY  {args} {}
    private method darrowCursor  {w}    {}
    private method oldCursor     {w}    {}
    private method newCursor     {w c}  {}
    private method resize        {win tag w} {}
    private method drag          {win X tag} {}
    private method recordX       {X w} {}
    
    private method writeData    {fd}  {}
    private method deleteOldLines  {numLines} {}
    private method deleteRows   {title val} {}
    private method restoreRows  {title val} {}
    private method delMenu      {w x y X Y} {}
    private method regexpWin    {title mode geom {screen_arg ""}} {}
    private method regexpCmd    {title mode} {}
    
}    
####################################################################


body VIEWER::get {option} {
    # return values of private variables
    switch -- $option {
	-name       {return $name}
	-screen     {return $screen}
	-printer    {return $printer}
	-address    {return $address}
	-dialog     {return $dialog}
	-melt       {return $melt}
	-mapped     {return $mapped}
	-dataObject {return $dataObject}
	-taglist    {return $allTagsList}

	-topWidget  {return $widget(top)}
	-dataWidget {return $widget(data)}
	-vscrWidget {return $widget(vscr)}
	-headerWidget {return $widget(header)}
	
	-tags       {return $col(tags)}
	-rows       {return $col(rows)}
	-delrows    {return $col(delrows)}
	-count      {return $col(count)}
	-length     {return $col(length)}
	-titles     {return $col(titles)}
	-maxlines   {return $col(maxlines)}
	
	-widths     {set widths ""
		     foreach i $col(titles) {
			lappend widths [winfo width $col($i,tframe)]
		     }
		     return $widths
		    }
		    
	-geometry    {return [winfo geometry $widget(top)]}
	-update      {return $vars($name,update)}
	-bfilter     {return $filterVal(browser,$name,list)}
	-sfilter     {return $filterVal(server,$name,list)}
	-bfilterview {return $filterVal(browser,$name,view)}
	-sfilterview {return $filterVal(server,$name,view)}
	-dataType    {return [$dataObject get -type]}
	-dataSource  {return [$dataObject get -source]}
    }
    error "VIEWER::get bad option \"$option\""
}

body VIEWER::constructor {_name bfilterList bfilterview
			  sfilterList sfilterview
			  dataObj _mapped maxlines columnList
			  {geometry ""} {update 0} {scrn ""}} {
    global env
    set screen $scrn
    if {$screen == ""} {
	if {[catch {set screen $env(DISPLAY)} msg] == 1} {
	   if {[catch {set host [exec hostname]} msg] != 1} {
		set screen "$host:0.0"
	   } else {
	   	#tk_dialog .err WARNING "Set DISPLAY environment variable for viewer $_name" {error} 0 DISMISS
	   	error "Set DISPLAY environment variable for viewer $_name"
	   }
	}
    }
#puts "SETTING SCREEN TO $screen"

    # initialize variables
    set name $_name
    set wl  ._$name

    set col(xstart) 0
    set col(length) 0
    set col(count)  0
    set col(tags)   ""
    set col(rows)   ""
    set col(delrows)   ""
    set col(titles)    ""
    set col(title,del) ""
    set col(maxlines)  $maxlines

    set widget(top)   $wl
    set widget(data)   ""
    set widget(header) ""
    set widget(vscr)   ""
    set widget(hscr)   ""
    set widget(load)    .load_$name
    set widget(save)    .save_$name
    set widget(wait)    .wait_$name
    set widget(update)  .update_$name
    set widget(filter)  .filter_$name
    set widget(addCol)  .addCol_$name
    set widget(delCol)  .delCol_$name
    set widget(widCol)  .widCol_$name
    set widget(moveCol) .moveCol_$name
    set widget(popup)   $wl.menu
    set widget(regexp)  .regexp_$name
    set widget(loadnext) .loadnxt_$name
    
    set allTagsList [list code cmlogTime facility host message process severity status text]
    trace variable allTagsList w "[code $this filterUpdate]"

    DIALOG Dlg_$name
    set dialog Dlg_$name

    set dataObject $dataObj
    $dataObject addViewer $this
    set addToTitle ""
    if {"[$dataObject get -type]" == "cmlog"} {
	set port [$dataObject get -port]
	if {$port > 0} {
	    set addToTitle ";   Cmlog Port is $port"
	} else {
	    if {"[array names env CMLOG_PORT]" != ""} {
	    	set addToTitle ";   Cmlog Port is $env(CMLOG_PORT)"
	    }
	}
    }

    toplevel $wl -screen "$screen"
    wm protocol $wl WM_DELETE_WINDOW "$this map 0"
    wm title $wl "Data Viewer:  $name $addToTitle"   
    if {$geometry != "" } {
	catch {wm geometry $wl $geometry}
    }
    
    # popup menu's for text manipulation
    menu $widget(popup) -tearoff 0
    menu $widget(popup).res -tearoff 0

    global fontH fontM fontT
    set fontH "-*-helvetica-bold-r-normal--*-120-*"
    set fontM "-*-helvetica-medium-r-normal--*-120-*"
    set fontT "-*-courier-medium-r-normal--*-120-*"

    frame $wl.top        -bg gray
    frame $wl.top.button -bg gray -bd 2 -relief groove

    # up & down screen buttons
    set images_library $env(CODA)/common/images
    set image_up [image create bitmap -file $images_library/bitmap/up.xbm]
    set image_dn [image create bitmap -file $images_library/bitmap/down.xbm]
    button $wl.top.button.up   -bg grey -padx 0 -pady 0 -image $image_up \
	-command "$this up"
    button $wl.top.button.down -bg grey -padx 0 -pady 0 -image $image_dn \
	-command "$this down"

    set mnu $wl.top.button
    
    # "File" menu
    menubutton $mnu.file -menu $mnu.file.m -text "FILE" -bd 1 -width 6
    menu $mnu.file.m
    $mnu.file.m add command -label "Save"   -command "$this save"
    $mnu.file.m add command -label "Print"  -command "$this print"
    $mnu.file.m add command -label "Email"  -command "$this email"
    $mnu.file.m add command -label "Close"  -command "$this map 0"
    $mnu.file.m add command -label "Delete" -command "delete object $this"
   
    # "Edit" menu
    menubutton $mnu.edit -menu $mnu.edit.m -text "EDIT" -bd 1 -width 6
    menu $mnu.edit.m
    $mnu.edit.m add command -label "Add Column"    -command [code $this addColumnWin]
    $mnu.edit.m add command -label "Move Column"   -command [code $this moveColumnWin]
    $mnu.edit.m add command -label "Delete Column" -command [code $this delColumnWin]
    $mnu.edit.m add command -label "Column Width"  -command [code $this widthColumnWin]
 
    # "View" menu
    menubutton $mnu.view -menu $mnu.view.m -text "VIEW" -bd 1 -width 6
    menu $mnu.view.m
    $mnu.view.m add command -label "Clear"  -command "$this clear"
    $mnu.view.m add command -label "Freeze" -command "$this freeze"
    $mnu.view.m add command -label "Screen" -command "$this screen"
    $mnu.view.m add command -label "Up"     -command "$this up"
    $mnu.view.m add command -label "Down"   -command "$this down"
 
    # "Options" menu
    menubutton $mnu.opts -menu $mnu.opts.m -text "OPTIONS" -bd 1 -width 8
    menu $mnu.opts.m
    $mnu.opts.m add command -label "Filter"  -command "$this filter"
    $mnu.opts.m add command -label "Load"    -command [code $this loadWin]
    $mnu.opts.m add command -label "Updates" -command "$this setUpdate"
    
    # Screen Update Warning
    # label $wl.top.button.update -text "NOT UPDATING" -fg red -bg gray
   
    pack $wl.top.button.file   -side left
    pack $wl.top.button.edit   -side left
    pack $wl.top.button.view   -side left
    pack $wl.top.button.opts   -side left
   # pack $wl.top.button.update -padx 5 -side left
    pack $wl.top.button.up     -pady 1 -padx 1 -side right
    pack $wl.top.button.down   -pady 1 -padx 1 -side right

    # Scrolled window widget for all data
    tixScrolledWindow  $wl.fr -scrollbar both -height 300
    set twin          [$wl.fr subwidget window]
    set widget(data)   $twin
    set widget(hscr)  [$wl.fr subwidget hsb]
    set hscrCommand   [$widget(hscr) cget -command]
    set widget(vscr)  [$wl.fr subwidget vsb]
    $widget(vscr) config -command [code $this multiScrollY]
    set vscrWidth   [expr [$widget(vscr) cget -width]+(2*([$widget(vscr) cget -bd]+\
		    [$widget(vscr) cget -highlightthickness]))]

    # Column Headers
    tixScrolledWindow   $wl.header -height 30 -scrollbar y
    set widget(header) [$wl.header subwidget window]
    set hhscrWidget    [$wl.header subwidget hsb]
    set hhscrCommand   [$hhscrWidget cget -command]
    $widget(hscr) config -command [code "$this multiScrollX [list $hscrCommand] [list $hhscrCommand]"]
    
    # add columns
    foreach i $columnList {
	catch {addColumn [lindex $i 0] [lindex $i 1] [lindex $i 2]}
    }
    
    frame $wl.bot -bd 2 -relief groove
    label $wl.bot.stat1 -text "  ~ STATUS ~  " -anchor center
    label $wl.bot.stat2 -text "  ~ STATUS ~  " -anchor center
    frame $wl.bot.monitor -bd 2 -relief sunken
    label $wl.bot.monitor.text -text "Monitoring: "
    label $wl.bot.monitor.stat -text "OFF" -fg red
    frame $wl.bot.sfilter -bd 2 -relief sunken
    label $wl.bot.sfilter.text -text "Server Filter: "
    label $wl.bot.sfilter.stat -text "OFF" -fg red
    frame $wl.bot.bfilter -bd 2 -relief sunken
    label $wl.bot.bfilter.text -text "Browser Filter: "
    label $wl.bot.bfilter.stat -text "OFF" -fg red
    
    pack $wl.bot.monitor.text $wl.bot.monitor.stat -side left -fill x -expand 1
    pack $wl.bot.sfilter.text $wl.bot.sfilter.stat -side left -fill x -expand 1
    pack $wl.bot.bfilter.text $wl.bot.bfilter.stat -side left -fill x -expand 1

    pack $wl.bot.stat1   $wl.bot.monitor \
	 $wl.bot.sfilter $wl.bot.bfilter -side left -fill x -expand 1
    pack $wl.bot.stat2 -side right -fill x -expand 1

    pack $wl.top.button -fill x
    pack $wl.top    -fill x
    pack $wl.header -fill x
    pack $wl.bot    -fill x    -expand 1 -side bottom -ipady 5 -ipadx 10
    pack $wl.fr     -fill both -expand 1 -side top

    if {("$_mapped" == "0") || ("$_mapped" == "1")} {
	set mapped $_mapped
    } else {
	set mapped 1
    }
    map $mapped
    update idletasks
    
    # add server filter condition
    if {("$sfilterview" == "1") || ("$sfilterview" == "0")} {
	set filterVal($name,view) $sfilterview
    } else {
 	set filterVal($name,view) 1
    }
    set filterVal($name,tags)     ""
    set filterVal($name,error)    0
    set filterVal($name,type)     server
    set filterVal($name,list)     $sfilterList
    set filterVal($name,oplist)   [$dataObject get -oplist]
    set lasttype [lindex [lindex  $filterVal($name,list) end] 1]
    set filterVal($name,lasttype) $lasttype
    foreach i $filterVal($name,list) {
	if {"[lindex $i 1]" == "tag"} {
	    lappend filterVal($name,tags) [lindex $i 0]
	}
    }
    # test server filter condition, if error remove condition
    if {[catch filterSub msg] == 1} {
	set filterVal($name,list) ""
	set filterVal($name,tags) ""
	set lasttype ""
	filterSub
	filterApply
	puts "VIEWER constructor: error in filter condition"
    } else {
	filterApply
    }
 
    # add browser filter condition
    if {("$bfilterview" == "1") || ("$bfilterview" == "0")} {
	set filterVal($name,view) $bfilterview
    } else {
 	set filterVal($name,view) 1
    }
    set filterVal($name,tags)     ""
    set filterVal($name,error)    0
    set filterVal($name,type)     browser
    set filterVal($name,list)     $bfilterList
    set filterVal($name,oplist)   [list < <= > >= == != match notmatch] 
    set filterVal(browser,$name,oplist) [list < <= > >= == != match notmatch] 
    set lasttype [lindex [lindex  $filterVal($name,list) end] 1]
    set filterVal($name,lasttype) $lasttype
    foreach i $filterVal($name,list) {
	if {"[lindex $i 1]" == "tag"} {
	    lappend filterVal($name,tags) [lindex $i 0]
	}
    }
    # test browser filter condition, if error remove condition
    if {[catch filterSub msg] == 1} {
	set filterVal($name,list) ""
	set filterVal($name,tags) ""
	set lasttype ""
	filterSub
	filterApply
	puts "VIEWER constructor: error in filter condition"
    } else {
	filterApply
    }
    
    # turn monitor on/off
    set vars($name,update) $update
    if {$update == 1} {
	monitorUpdate on
    } else {
	monitorUpdate off
    }
}

body VIEWER::destructor {} {
    monitorUpdate off
    catch "destroy $widget(top)"
    trace vdelete allTagsList w "[code $this filterUpdate]"
    delete object $dialog
    delete object $dataObject
    mwin removeViewer $name
}

body VIEWER::multiScrollX {hscrCmd hhscrCmd args} {
    # orig cmd to scroll data widget
    uplevel #0 eval [join $hscrCmd]  $args
    uplevel #0 eval [join $hhscrCmd] $args
}
     
body VIEWER::multiScrollY {args} {
    # cmd to scroll all columns
    foreach i $col(titles) {
	eval $col($i,text) yview $args
    }
}
     
body VIEWER::darrowCursor {w} {
    $w config -cursor sb_h_double_arrow
}

body VIEWER::oldCursor {w} {
    $w config -cursor ""
}

body VIEWER::newCursor {w c} {
    $w config -cursor $c
}

body VIEWER::busy {mode} {
    set mode [string tolower $mode]
    set w $widget(wait)
    lappend windows $widget(top) $w
    foreach i $col(titles) {
	lappend windows $col($i,text)
	lappend windows $col($i,tdivider)
	lappend windows $col($i,hdivider)
    }
    if {"$mode" == "on"} {
	set x [expr 100+[winfo rootx $widget(top)]]
	set y [expr 100+[winfo rooty $widget(top)]]
	if {[winfo exists $w]} {
	    wm withdraw $w
	    wm geometry $w +${x}+${y}
	    wm deiconify $w
	} else {
	    toplevel $w -screen $screen
	    wm title $w "WAIT"
	    wm geometry $w +${x}+${y}
	    label $w.msg -justify center \
		-cursor watch \
		-text "Please Wait While Data \nIs Being Loaded" \
		-font -*-times-medium-r-normal--*-240-*
	    pack  $w.msg -fill both -expand 1 -padx 40 -pady 40
	}
	foreach i $windows {
	    catch {$i config -cursor watch}
	}
    } elseif {"$mode" == "off"} {
	catch {wm withdraw $w}
	foreach i $windows {
	    catch {$i config -cursor ""}
	}
    }
}

body VIEWER::resize {win title w} {
    if {$col($title,wid) < 1} {
	set col($title,wid) 1
    }
    $win config -width $col($title,wid)
#puts "resize: set col($title,wid) to $col($title,wid)"

    oldCursor $widget(header)
    oldCursor $widget(data)
    update idletasks
}

body VIEWER::drag {win X title} {
    set width [expr $col($title,wid)-($col(xstart)-$X)]
#puts "xs = $xstart, X= $X, width = $width"
    if {$width < 1} {
	$win config -width 1
    }
    $win config -width $width
    set col($title,wid)  $width
    set col(xstart) $X
    update idletasks
}

body VIEWER::recordX {X w} {
    set col(xstart) $X
    bind $w <Leave> ""
}

body VIEWER::addColumn {title tag width} {
    global fontH status_tags
#puts "add $title, $tag, $width"
    # check for duplicate titles and make sure there are no spaces in names
    if {[lsearch -exact $col(titles) $title] != -1} {
	return -1
    } elseif {"$title" == ""} {
	tk_dialog2 .err WARNING "The title cannot be blank" {error} 0 $screen DISMISS
	return -1
    }
    regsub -all { } $title {_} title

    # keep track of "number of cols" & "next col number"
    set num [incr col(count)]
    set color gray95
    
    # width of col.
    set col($title,wid) $width
    
    # col. deletions
    set col($title,dels) ""
    
    # save column name & tag info
    if {[array names col $tag] != ""} {
	# keep list of titles associated with a single tag
	lappend col($tag) $title
	# keep count of titles per tag
	incr col($tag,num)
    } else {
	set col($tag) [list $title]
	set col($tag,num) 1
    }
    set col($title)     $tag
    lappend col(titles) $title
    lappend col(tags)   $tag
    
    # save text/frame/header/divider widget names for column manipulation
    set col($title,header)   $widget(header).f$title.l
    set col($title,hframe)   $widget(header).f$title
    set col($title,hdivider) $widget(header).d$title
    
    set col($title,text)     $widget(data).f$title.t
    set col($title,tframe)   $widget(data).f$title
    set col($title,tdivider) $widget(data).d$title

    # create title
    frame $col($title,hframe) -highlightthickness 0 -bd 0 -width $width
    label $col($title,header) -text "$title" -height 2 -bg "$color" -highlightthickness 0 -bd 0
    pack  $col($title,hframe) -fill both -side left
    pack  $col($title,header) -fill x
    pack  propagate $col($title,hframe) 0
    
    # create divider between previous title & this one
    set can $col($title,hdivider)
    canvas $can -highlightthickness 0 -bd 0 -width 4
    $can create line 0 0 0 30 -fill gray80
    $can create line 1 0 1 30 -fill black
    $can create line 2 0 2 30 -fill black
    $can create line 3 0 3 30 -fill white
    
    pack $can -fill y -side left
    pack configure $col($title,hframe) -fill y -expand 0
    
    # add proper behavior to divider/resizing
    bind $can <ButtonRelease-1> "+ [code "$this resize $col($title,tframe) $title %W"]"
    bind $can <B1-Motion> "+ [code "$this drag $col($title,hframe) %X $title"]"
    bind $can <Button-1>  "+ [code "$this recordX %X %W"]"
    bind $can <Leave>     "+ [code "$this oldCursor $widget(header)"]"
    bind $can <Enter>     "+ [code "$this darrowCursor $widget(header)"]"
	
    # create text column under title
    frame $col($title,tframe) -width $width -highlightthickness 0 -bd 0
    text  $col($title,text)   -wrap none\
		 -yscrollcommand "$widget(vscr) set"\
		 -highlightthickness 0\
		 -bd 0\
		 -bg gray80
		
    pack $col($title,text)   -fill y -expand 1
    pack $col($title,tframe) -fill y -expand 0 -side left
    pack propagate $col($title,tframe) 0
    
    # add blank lines to text widget to equal number of existing lines
    textWritable
    for {set i 0} {$i < $col(length)} {incr i} {
	$col($title,text) insert end "\n"
    }
    textReadable

    # create divider between previous col & this one
    set can $col($title,tdivider).c
    frame $col($title,tdivider) -highlightthickness 0 -bd 0 -width 4
    canvas $can -highlightthickness 0 -bd 0 -width 4
    $can create line 0 0 0 1000 -fill gray75
    $can create line 1 0 1 1000 -fill black
    $can create line 2 0 2 1000 -fill black
    $can create line 3 0 3 1000 -fill white
    
    pack $can -fill both -expand 1
    pack $col($title,tdivider) -fill y -expand 0 -side left
    pack propagate $col($title,tdivider) 0

    # add proper behavior to divider/resizing
    bind $can <ButtonRelease-1> "+ [code "$this resize $col($title,hframe) $title %W"]"
    bind $can <B1-Motion> "+ [code "$this drag $col($title,tframe) %X $title"]"
    bind $can <Button-1>  "+ [code "$this recordX %X %W"]"
    bind $can <Leave>     "+ [code "$this oldCursor $widget(data)"]"
    bind $can <Enter>     "+ [code "$this darrowCursor $widget(data)"]"

    # configure text widget
    if {"$tag" == "status"} {
        # look to see which tags are defined in colour array
	set tags [array names status_tags]
	foreach t $tags {
	    $col($title,text) tag configure "$t" \
		-borderwidth 2 \
		-relief     [lindex $status_tags($t) 1] \
		-background [lindex $status_tags($t) 0]
        }
	#$col($title,text) tag bind ERROR <Button-1> "$this ERROR_handler $widget(top)"
	#$col($title,text) tag bind ERROR <Button-3> "$this flash_handler $widget(top)"
    }
    bind $col($title,text) <Button-3> [code $this delMenu $title %x %y %X %Y]
    
    $col($title,text) config -state disabled
    return 0
}

body VIEWER::addColumnWin {{screen_arg ""}} {
    # definition of the "Delete Column" window
    set w $widget(addCol)
    
    # screen_arg tells window where to go
    if {"$screen_arg" == ""} {
	toplevel $w -screen $screen
    } else {
	toplevel $w -screen $screen_arg
    }
    
    wm title $w "viewer $name"

    frame $w.top -height 40
    label $w.top.l -text "\nAdd Column\n" -bg white -anchor center
    pack  $w.top.l -fill both -expand 1
    
    frame $w.mid
    frame $w.mid.left  -bd 2 -relief groove
    frame $w.mid.right -bd 2 -relief groove
    
    label $w.mid.left.l1 -bd 2 -relief ridge -text " Title "
    label $w.mid.left.l2 -bd 2 -relief ridge -text " Tag "
    label $w.mid.left.l3 -bd 2 -relief ridge -text " Width "
    label $w.mid.left.l4 -bd 2 -relief ridge -text " Position "
    tixComboBox $w.mid.right.title -editable 1 -history 1 -historylimit 10
    tixComboBox $w.mid.right.tag   -editable 1 -history 1
    foreach i $allTagsList {
	$w.mid.right.tag appendhistory $i
    }
    tixControl  $w.mid.right.wid -integer 1 -min 1 -max 500
    tixControl  $w.mid.right.pos -integer 1 -min 1
    $w.mid.right.wid config -value 60
    $w.mid.right.pos config 
    pack $w.mid.left.l1 \
	 $w.mid.left.l2 \
	 $w.mid.left.l3 \
	 $w.mid.left.l4 \
	 -fill both -expand 1
    pack $w.mid.right.title \
	 $w.mid.right.tag \
	 -fill both -expand 1
    pack $w.mid.right.wid \
	 $w.mid.right.pos \
	 -fill y -expand 1 -anchor w
    pack $w.mid.left $w.mid.right -side left -fill both -expand 1
    
    frame  $w.bottom
    button $w.bottom.add -text "  Add  " \
			 -command "[code $this addColumnWinCmd]"
    button $w.bottom.dismiss -text "Dismiss" \
			     -command "destroy $w"
    pack $w.bottom.add $w.bottom.dismiss -side left -fill both -expand 1
    pack $w.top $w.mid $w.bottom -fill both -expand 1
}

body VIEWER::addColumnWinCmd {} {
    set w $widget(addCol).mid.right
    set title  [$w.title cget -selection]
    regsub -all { } $title {_} title    
    set tag    [$w.tag   cget -selection]
    $w.wid update
    $w.pos update
    set width  [$w.wid cget -value]
    set pos    [$w.pos cget -value]
    if {$pos > $col(count)} {
	set pos [expr 1 + $col(count)]
	$w.pos config -value $pos
    }
    if {[addColumn $title $tag $width] != 0} {
	return
    }
    set destTitle [lindex $col(titles) [expr $pos-1]]
    # if only column, don't need to move it
    if {"$destTitle" != ""} {
	moveColumn $title $destTitle
    }
}

body VIEWER::delColumnWin {{screen_arg ""}} {
    # definition of the "Delete Column" window
    set w $widget(delCol)
    
    # screen_arg tells window where to go
    if {"$screen_arg" == ""} {
	toplevel $w -screen $screen
    } else {
	toplevel $w -screen $screen_arg
    }
    
    wm title $w "viewer $name"

    label $w.l -text "Delete Column" -bg white -width 2 -anchor center
    tixComboBox $w.box -listcmd [code $this columnListCmd $w.box]

    frame  $w.bottom
    button $w.bottom.delete -text "Delete "\
	-command [code "[subst -nocommands {$this delColumn [$w.box cget -selection]}]"]
    button $w.bottom.dismiss -text "Dismiss" -command "destroy $w"
    pack $w.bottom.delete $w.bottom.dismiss -side left -fill x -expand 1
    pack $w.l -ipady 10 -fill both -expand 1
    pack $w.box $w.bottom -fill both -expand 1
}

body VIEWER::columnListCmd {w} {
    foreach i $col(titles) {
	$w appendhistory $i
    }
}

body VIEWER::delColumn {title} {
    set indx [lsearch -exact $col(titles) $title]
    if {$indx == -1} {
	puts "delColumn: No column by the name \"$title\""
	return
    }

    set tag $col($title)
    set col(titles) [lreplace $col(titles) $indx $indx]
    set col(tags)   [lreplace $col(tags)   $indx $indx]

    # delete column/header/divider
    destroy $col($title,tframe)
    destroy $col($title,hframe)
    destroy $col($title,tdivider)
    destroy $col($title,hdivider)

    unset col($title)
    
    incr col($tag,num) -1
    set indx [lsearch -exact $col($tag) $title]
    if {$indx != -1} {
	set col($tag) [lreplace $col($tag) $indx $indx]
    }

    # keep count of cols
    incr col(count) -1
}

body VIEWER::moveColumnWin {{screen_arg ""}} {
    # definition of the "Move Column" window
    set w $widget(moveCol)
    
    # screen_arg tells window where to go
    if {"$screen_arg" == ""} {
	toplevel $w -screen $screen
    } else {
	toplevel $w -screen $screen_arg
    }
    
    wm title $w "viewer $name"

    frame $w.top -height 40
    label $w.top.l -text "\nMove Column\n" -bg white -anchor center
    pack  $w.top.l -fill both -expand 1
    
    frame $w.mid
    frame $w.mid.left  -bd 2 -relief groove
    frame $w.mid.right -bd 2 -relief groove
    
    label $w.mid.left.l1 -bd 2 -relief ridge -text " Title "
    label $w.mid.left.l2 -bd 2 -relief ridge -text " Position "
    tixComboBox $w.mid.right.title -editable 1 -history 1 \
	-listcmd [code $this columnListCmd $w.mid.right.title]
    tixControl $w.mid.right.pos -integer 1 -min 1 -value 1
    
    pack $w.mid.left.l1 $w.mid.left.l2 -fill both -expand 1
    pack $w.mid.right.title -expand 1 -anchor w -fill both
    pack $w.mid.right.pos   -expand 1 -anchor w -fill y
    pack $w.mid.left $w.mid.right -side left -fill both -expand 1
    
    frame  $w.bottom
    button $w.bottom.delete -text "  Move "\
	-command [code $this moveColumnWinCmd]
    button $w.bottom.dismiss -text "Dismiss" -command "destroy $w"
    pack   $w.bottom.delete $w.bottom.dismiss -side left -fill x -expand 1
    pack   $w.top $w.mid $w.bottom -fill both -expand 1
}

body VIEWER::moveColumnWinCmd {} {
    set w $widget(moveCol)
    $w.mid.right.pos update
    set pos   [$w.mid.right.pos cget -value]
    if {$pos > $col(count)} {
	set pos [expr 1 + $col(count)]
	$w.mid.right.pos config -value $pos
    }
    set title [$w.mid.right.title cget -selection]
    set destTitle [lindex $col(titles) [expr $pos-1]]
    $this moveColumn $title $destTitle
}

body VIEWER::moveColumn {thisCol thatCol} {
    # note that input = titles

    if {"$thisCol" == "$thatCol"} {
	return
    }

    set thisTag $col($thisCol)
    set thatTag $col($thatCol)
    
    set thisIndex [lsearch -exact $col(titles) $thisCol]
    set thatIndex [lsearch -exact $col(titles) $thatCol]
    set thisPlus1 [expr 1+$thisIndex]
    set thatPlus1 [expr 1+$thatIndex]

    # repack widgets & rearrange lists to preserve proper order
    if {$thisIndex > $thatIndex} {
	pack configure $col($thisCol,tframe)   -before $col($thatCol,tframe)
	pack configure $col($thisCol,tdivider) -before $col($thatCol,tframe)
	pack configure $col($thisCol,hframe)   -before $col($thatCol,hframe)
	pack configure $col($thisCol,hdivider) -before $col($thatCol,hframe)
	set col(tags)   [linsert  $col(tags) $thatIndex "$thisTag"]
	set col(tags)   [lreplace $col(tags) $thisPlus1 $thisPlus1]
	set col(titles) [linsert  $col(titles) $thatIndex "$thisCol"]
	set col(titles) [lreplace $col(titles) $thisPlus1 $thisPlus1]
    } else {
	pack configure $col($thisCol,tdivider) -after $col($thatCol,tdivider)
	pack configure $col($thisCol,tframe)   -after $col($thatCol,tdivider)
	pack configure $col($thisCol,hdivider) -after $col($thatCol,hdivider)
	pack configure $col($thisCol,hframe)   -after $col($thatCol,hdivider)
	set col(tags)   [linsert  $col(tags) $thatPlus1 "$thisTag"]
	set col(tags)   [lreplace $col(tags) $thisIndex $thisIndex]
	set col(titles) [linsert  $col(titles) $thatPlus1 "$thisCol"]
	set col(titles) [lreplace $col(titles) $thisIndex $thisIndex]
    }
}

body VIEWER::widthColumnWin {{screen_arg ""}} {
    # definition of the "Column Width" window
    set w $widget(widCol)
    
    # screen_arg tells window where to go
    if {"$screen_arg" == ""} {
	toplevel $w -screen $screen
    } else {
	toplevel $w -screen $screen_arg
    }
    
    wm title $w "viewer $name"
    frame $w.top -height 40
    label $w.top.l -text "\nColumn Width\n" -bg white -anchor center
    pack  $w.top.l -fill both -expand 1
    
    frame $w.mid
    frame $w.mid.left  -bd 2 -relief groove
    frame $w.mid.right -bd 2 -relief groove
    
    label $w.mid.left.l1 -bd 2 -relief ridge -text " Title "
    label $w.mid.left.l2 -bd 2 -relief ridge -text " Width "
    tixComboBox $w.mid.right.title -editable 1 -history 1 \
	-listcmd [code $this columnListCmd $w.mid.right.title]
    tixControl  $w.mid.right.wid -integer 1 -min 1 -max 500
    $w.mid.right.wid config -value 60
    
    pack $w.mid.left.l1 $w.mid.left.l2 -fill both -expand 1
    pack $w.mid.right.title -expand 1 -anchor w -fill both
    pack $w.mid.right.wid   -expand 1 -anchor w -fill y
    pack $w.mid.left $w.mid.right -side left -fill both -expand 1
    
    frame  $w.bottom
    button $w.bottom.delete -text "Apply  "\
	-command "[code "[subst -nocommands \
	     {$this widthColumn [$w.mid.right.title cget -selection]\
				[$w.mid.right.wid   cget -value]}]"]"
    button $w.bottom.dismiss -text "Dismiss" -command "destroy $w"
    pack $w.bottom.delete $w.bottom.dismiss -side left -fill x -expand 1
    pack $w.top $w.mid $w.bottom -fill both -expand 1

}

body VIEWER::widthColumn {title width} {
    # width of col.
    set tag $col($title)
    set col($title,wid) $width
    
    $col($title,tframe) config -width $width
    $col($title,hframe) config -width $width

    update idletasks  
}

# Go row-by-row.
# Each row deletion will change line num of next deletion.
# Store all deleted information in a number of arrays.
    
body VIEWER::deleteRows {title val} {
    # get locations of all items to be deleted in col $title
    if {("$title" == "") || ("$val" == "")} {return}
    textWritable
    
    set tags [$col($title,text) tag ranges "$val"]
    set leng [llength $tags]
#puts "deleteRows: tags -> $tags"
#puts " length = $leng"
    set deletions 0
    set id "$title@$val"
    
    # add to list of title,val deletions if not already there
    set found 0
    foreach i $col(title,del) {
	set _title [lindex $i 0]
	set _val   [lindex $i 1]
	if {("$_title" == "$title") && ("$_val" == "$val")} {
	    set found 1
	    break
	}
    }
    if {$found != 1} {
	lappend col(title,del) [list $title $val]
    }
#puts " rows -> $col(rows)"
    
    for {set i 0} {$i < $leng} {incr i 2} {
	set tag      [lindex $tags $i]
	set index1   [expr round($tag) - 1]
	set beginTag [expr $index1 + 1 - $deletions].0
	set index2   [expr $index1 - $deletions]
	lappend col(delrows) [list $id [lindex $col(rows) $index2]]
#puts "  delrow append: [lindex $col(rows) $index2]"
#puts "  tag = $tag, index1 = $index1, index2 = $index2, btag = $beginTag"
	set col(rows) [lreplace $col(rows) $index2 $index2]
#puts "  rows = $col(rows)"
#puts "  delrows = $col(delrows)\n"
	foreach j $col(titles) {
	    set fr $col($j,text)
	    set endTag "$beginTag lineend"
	    set value [$fr get "$beginTag" "$endTag"]
	    $fr tag remove menu "$beginTag" "$endTag"
	    $fr delete "$beginTag" "$endTag +1c"
	    lappend col($j,dels) $value 
	}
	incr deletions
	incr col(length) -1
   }
    update idletasks  
    textReadable
}

body VIEWER::restoreRows {title val} {
    if {("$title" == "") || ("$val" == "")} {return}
    textWritable

#puts "Restore: title = $title; val = $val"

    # Are there any lines deleted for title & val?
    set index 0
    set found 0
    foreach i $col(title,del) {
	set _title [lindex $i 0]
	set _val   [lindex $i 1]
	if {("$_title" == "$title") && ("$_val" == "$val")} {
	    set found 1
	    break
	}
	incr index
    }
    if {$found != 1} {return}
#puts " Index = $index; list -> $col(title,del)"
    set col(title,del) [lreplace $col(title,del) $index $index]

    # Look at each deleted line to see if it is associated with this
    # "$title,$val" pair deletion. If so undelete it, if not go on.
    # delRowNum keeps track of the num of the Nth deleted row
    set delRowNum -1
    foreach i $col(delrows) {
	incr delRowNum
	set id [lindex $i 0]
	if {"$id" != "$title@$val"} {
	    continue
	}
	set origLineNum [lindex $i 1]
#puts "  delRowNum = $delRowNum; origLineNum = $origLineNum"
	    
	# scan original line nums left to see where this line fits
	set indx 0
	set leng [llength $col(rows)]
	foreach j $col(rows) {
#puts "  origLineNum = $origLineNum, j = $j"
	    if {$origLineNum < $j} {
		set beginTag  [expr 1+$indx].0
		set col(rows) [linsert $col(rows) $indx $origLineNum]
		break
	    } elseif {$origLineNum == $j} {
		error "Error in algorithm to keep track of row deletions"
		return
	    } elseif {$indx == [expr $leng - 1]} {
		set beginTag  [expr 2+$indx].0
		set col(rows) [linsert $col(rows) end $origLineNum]
		break
	    }
	    incr indx
	}
	if {$leng == 0} {
	    set beginTag 1.0
	    lappend col(rows) $origLineNum
	}
#puts "   beginTag = $beginTag, indx = $indx"

	# put deleted values into each col for single row
	foreach j $col(titles) {
	    set fr $col($j,text)
	    set value [lindex $col($j,dels) $delRowNum]
#puts "  insert $value in $j"
	    $fr insert $beginTag $value [list $value menu] \n ""
	}
	incr col(length)
    }

    update idletasks  
    textReadable
    
    # clean up arrays
    set copy $col(delrows)
    set index 0
    foreach i $copy {
	set id [lindex $i 0]
	if {"$id" != "$title@$val"} {
	    incr index
	} else {
	    set col(delrows) [lreplace $col(delrows) $index $index]
	    foreach j $col(titles) {
		set col($j,dels) [lreplace $col($j,dels) $index $index]
	    }
	}
    }
}

# Delete $numLines of visible data from viewer.

body VIEWER::deleteOldLines {numLines} { 
    if {($numLines < 1) || ($numLines > [llength $col(rows)])} {
	return
    }  
    set first_row [lindex $col(rows) $numLines]
    
    # remove lines from widgets
    foreach j $col(titles) {
	set fr $col($j,text)
	$fr delete 1.0 "1.0 +${numLines}l"
    }
    
    # change list of visible rows
    if {[llength $col(rows)] > 0} {
	set col(rows) [lreplace $col(rows) 0 [expr $numLines - 1]]
    }
    set col(length) $col(maxlines)
          
    # change list of deleted rows
    if {[llength $col(delrows)] > 0} {
      set index -1
      set copy $col(delrows)
      foreach i $copy {
	incr index
	set id  [lindex $i 0]
	set row [lindex $i 1]
	if {$row < $first_row} {
	    # delete all references to this "deleted" line
	    # so it's now unrecoverable
	    set col(delrows) [lreplace $col(delrows) $index $index]
	    foreach j $col(titles) {
		set col($j,dels) [lreplace $col($j,dels) $index $index]
	    }
	}
      }
    }
}

body VIEWER::delMenu {title x y X Y} {
    set w $col($title,text)
    set geom "+${X}+${Y}"
    $w tag raise menu
    set indx [$w index @$x,$y]
#puts "delMenu: index = $indx, total visible rows = $col(length)"
    set val [lindex [$w tag names $indx] 0]
    if {"$val" == "sel"} {
	set val [lindex [$w tag names $indx] 1]
    }
#puts "delMenu: val = $val"

    set m $widget(popup)
    $m     delete 0 last
    $m.res delete 0 last
    
    if {"$val" != ""} {
	$m add com \
	    -label "Delete $val" \
	    -command [code $this deleteRows $title $val]
    }
    
    if {$col(length) > 0} {
	$m add com \
	    -label "Delete <regexp>" \
	    -command [code $this regexpWin $title delete $geom]
	$m add sep
    }
    
    if {"$col($title,dels)" != ""} {
	$m add com \
	    -label "Restore <regexp>" \
	    -command [code $this regexpWin $title restore $geom]
    }
    
    set deletionsExist 0
    foreach i $col(title,del) {
    	set _title [lindex $i 0]
     	set _val   [lindex $i 1]
   
	if {"$_title" == "$title"} {
	    $m.res add com -label $_val -command [code $this restoreRows $_title $_val]
	    set deletionsExist 1
	}
    }
    if {$deletionsExist == 1} {
	$m add cas -label "Restore" -menu $m.res
    }
    
    tk_popup $m $X $Y
}

body VIEWER::regexpWin {title mode geom {screen_arg ""}} {
    if {"[string tolower $mode]" == "restore"} {
	set mode restore
	set Mode Restore
    } elseif {"[string tolower $mode]" == "delete"} {
	set mode delete
	set Mode Delete
    } else {
	tk_dialog2 .err WARNING "usage: regexpWin delete\n       regexpWin restore" {error} 0 $screen DISMISS
	return
    }
    
    set w $widget(regexp)
    if {[winfo exists $w]} {
	wm withdraw $w
	wm geometry $w $geom
	wm title    $w "$Mode Regexp"
	$w.top.l    config -text "$Mode items\nthat match:"
	$w.top.r.r1 config -text "$mode only"
	$w.top.r.r2 config -text "$mode all except"
	$w.bot.mode config\
	    -command [code "$this regexpCmd $title $mode; wm withdraw $w"] \
	    -text " $Mode"
	wm deiconify $w
	return
    }
    
    # screen_arg tells window where to go
    if {"$screen_arg" == ""} {
	toplevel $w -screen $screen
    } else {
	toplevel $w -screen $screen_arg
    }
    wm title $w "$Mode Regexp"
    wm geometry $w $geom
    wm protocol $w WM_DELETE_WINDOW "wm withdraw $w"

    frame $w.top
    label $w.top.l \
	-text "$Mode items\nthat match:" \
	-bg white\
	-anchor center \
	-bd 2 \
	-relief groove
    pack  $w.top.l -fill both -expand 1 -ipady 10 -anchor center
    frame $w.top.r -bd 2 -relief groove
    radiobutton $w.top.r.r1 -variable [scope vars($name,regexp)] -value 1 -text "$mode only"
    radiobutton $w.top.r.r2 -variable [scope vars($name,regexp)] -value 0 -text "$mode all except"
    $w.top.r.r1 invoke
    pack $w.top.r.r1 $w.top.r.r2 -pady 2 -padx 2 -anchor w
    pack $w.top.l -side left -fill both -expand 1
    pack $w.top.r -side left
    pack $w.top -fill both -expand 1

    tixComboBox  $w.regexp -editable 1 -history 1
    pack $w.regexp -fill x -expand 1 -ipady 2
    
    frame  $w.bot
    button $w.bot.mode   -text " $Mode" -command [code "$this regexpCmd $title $mode; wm withdraw $w"]
    button $w.bot.cancel -text "Cancel" -command "wm withdraw $w"
    
    pack $w.bot.mode $w.bot.cancel -side left -fill x -expand 1
    pack $w.bot -fill both
}

body VIEWER::regexpCmd {title mode} {
    set pattern [$widget(regexp).regexp cget -selection]
#puts "The pattern is: $pattern"
    
    set items ""
    # widget name
    set w $col($title,text) 
    set in_ex $vars($name,regexp)
    
    if {"$mode" == "delete"} {
	# num of columns to scan
#puts " Num rows = $col(length)"
	# Scan text widget to get column entries.
	# If there's a match, record it.
	for {set i 1} {$i <= $col(length)} {incr i} {
	    set value [$w get "$i.0" "$i.0 lineend"]
#puts " Found value = $value"
	    if {([regexp [subst {$pattern}] "$value"] == $in_ex)} {
#puts " matches pattern"
		if {([lsearch -exact $items $value] == -1)} {
		    lappend items "$value"
#puts "   Append Item = $value"
		}
	    }
	}
    } elseif {"$mode" == "restore"} {
	foreach i $col(title,del) {
	    set _title [lindex $i 0]
	    set _val   [lindex $i 1]
#puts " _title, _val = $_title, $_val"
	    if {("$title" == "$_title") &&\
		([regexp [subst {$pattern}] $_val] == $in_ex)} {
#puts " found $_val"
		lappend items "$_val"
	    }
	}
    }
    
    foreach i $items {
#puts " Invoke  ${mode}Rows"
	${mode}Rows $title $i
    }    
}

body VIEWER::textWritable {} {
    foreach i $col(titles) {
	$col($i,text) config -state normal
    }
}

body VIEWER::textReadable {} {
    foreach i $col(titles) {
	$col($i,text) config -state disabled
    }
}

body VIEWER::clearOriginal { } {
    # delete visual text
    foreach i $col(titles) {
	set w $col($i,text)
	$w config -state normal
	$w delete 0.0 end
	$w config -state disabled
	set col($i,dels) {}
    }
    # delete info
    set col(title,del) {}
    set col(delrows)   {}
    set col(rows)      {}
    set col(length)    0
}

body VIEWER::clear { } {
  # delete widget (faster than deleting all text)
  global status_tags
  
  foreach i $col(titles) {
	destroy $col($i,tdivider)
	destroy $col($i,tframe)
  }
  # delete info
  set col(title,del) {}
  set col(delrows)   {}
  set col(rows)      {}
  set col(length)    0

  # recreate widgets
  foreach i $col(titles) {
    # create text column under title
    frame $col($i,tframe) -width $col($i,wid) -highlightthickness 0 -bd 0
    text  $col($i,text)   -wrap none\
		 -yscrollcommand "$widget(vscr) set"\
		 -highlightthickness 0\
		 -bd 0\
		 -bg gray80
		
    pack $col($i,text)   -fill y -expand 1
    pack $col($i,tframe) -fill y -expand 0 -side left
    pack propagate $col($i,tframe) 0

    # create divider between previous col & this one
    set can $col($i,tdivider).c
    frame $col($i,tdivider) -highlightthickness 0 -bd 0 -width 4
    canvas $can -highlightthickness 0 -bd 0 -width 4
    $can create line 0 0 0 1000 -fill gray75
    $can create line 1 0 1 1000 -fill black
    $can create line 2 0 2 1000 -fill black
    $can create line 3 0 3 1000 -fill white
    
    pack $can -fill both -expand 1
    pack $col($i,tdivider) -fill y -expand 0 -side left
    pack propagate $col($i,tdivider) 0

    # add proper behavior to divider/resizing
    bind $can <ButtonRelease-1> "+ [code "$this resize $col($i,hframe) $i %W"]"
    bind $can <B1-Motion> "+ [code "$this drag $col($i,tframe) %X $i"]"
    bind $can <Button-1>  "+ [code "$this recordX %X %W"]"
    bind $can <Leave>     "+ [code "$this oldCursor $widget(data)"]"
    bind $can <Enter>     "+ [code "$this darrowCursor $widget(data)"]"

    # configure text widget
    if {"$col($i)" == "status"} {
        # look to see which tags are defined in status_tags array
	set tags [array names status_tags]
	foreach t $tags {
	    $col($i,text) tag configure "$t" \
		-borderwidth 2 \
		-relief     [lindex $status_tags($t) 1] \
		-background [lindex $status_tags($t) 0]
        }
    }
    bind $col($i,text) <Button-3> [code $this delMenu $i %x %y %X %Y]
    
    $col($i,text) config -state disabled
  } 
  update idletasks
  return 0
}

body VIEWER::map {{m ""}} {
    if {"$m" == "0"} {
	wm withdraw $widget(top)
	set mapped 0
	return $mapped
    } elseif {"$m" == "1"} {
	wm deiconify $widget(top)
	set mapped 1
	return $mapped
    } elseif {"$m" == ""} {
	return $mapped
    }
}

body VIEWER::up {} {
    foreach i $col(titles) {
	$col($i,text) yview scroll -1 page
    }
}

body VIEWER::down {} {
    foreach i $col(titles) {
	$col($i,text) yview scroll 1 page
    }
}

body VIEWER::freeze { } {
    if {"[$widget(top).top.button.view.m entrycget 2 -label]" == "Melt"} {
	set melt 1
	$widget(top).top.button.view.m entryconfig 2 -label Freeze
    } else {
	set melt 0
	$widget(top).top.button.view.m entryconfig 2 -label Melt
    }
}

body VIEWER::writeData {fd} {
    # write a line at a time to file descriptor
    for {set i 1} {$i <= $col(length)} {incr i} {
	set a ""
	foreach j $col(titles) {
	    set b \{[$col($j,text) get $i.0 "$i.0 lineend"]\}
	    set a "$a$b  "
	}
	puts $fd $a
    }
}


body VIEWER::save {{screen_arg ""}} {
    global env
    set w $widget(save)
    
    if {[winfo exists $w]} {
	wm withdraw  $w
	wm deiconify $w
	return
    }
    
    if {"$screen_arg" == ""} {
	toplevel $w -screen $screen
    } else {
	toplevel $w -screen $screen_arg
    }

    $widget(top).top.button.file.m entryconfig 1 -state disabled
    
    wm protocol $w WM_DELETE_WINDOW "wm withdraw $w"
    wm title $w "Save Data To File"
    
    tixFileSelectBox $w.file
    pack $w.file -fill both -expand 1
    
    frame  $w.bot
    button $w.bot.repl -text "Replace" \
	-command [code "$w.file config -command [subst -nocommands {[code $this saveCmd w]}]
			$w.file invoke
			wm withdraw $w
			$widget(top).top.button.file.m entryconfig 1 -state normal"]
	    
    button $w.bot.app -text "Append" \
	-command [code "$w.file config -command [subst -nocommands {[code $this saveCmd a]}]
			$w.file invoke
			wm withdraw $w
			$widget(top).top.button.file.m entryconfig 1 -state normal"]

    button $w.bot.cancel -text "Cancel" \
	-command "wm withdraw $w
 		  $widget(top).top.button.file.m entryconfig 1 -state normal"
   
    pack $w.bot.repl $w.bot.app $w.bot.cancel -side left -fill x -expand 1
    pack $w.bot -fill both

}    

body VIEWER::saveCmd {mode file} {
    if {[catch {open $file $mode} fd]} {
	tk_dialog2 .err WARNING "Error saving data" {} 0 $screen DISMISS
    } else {
	writeData $fd
	close     $fd
    }
}

body VIEWER::print {{screen_arg ""}} {
    # screen_arg tells the dialog box where to go
    if {"$screen_arg" == ""} {
	$dialog setscreen $screen
    } else {
	$dialog setscreen $screen_arg
    }

    $widget(top).top.button.file.m entryconfig 2 -state disabled
    
    set fname "/tmp/tkined[pid].log"
    catch {exec /bin/rm -f $fname}
    if {[file exists $fname] && ![file writable $fname]} {
	$dialog acknowledge $widget(top) "Can not write temporary file $fname."
	$widget(top).top.button.file.m entryconfig 2 -state normal
	return
    }

    if {[catch {open $fname w} file]} {
	$dialog acknowledge $widget(top) "Can not open $fname: $file"
	$widget(top).top.button.file.m entryconfig 2 -state normal
	return
    }

    if {[catch {puts $file "PRINTED OUTPUT OF ERROR MESSAGES:\n"} err]} {
	$dialog acknowledge $widget(top) "Failed to write $fname: $err"
	$widget(top).top.button.file.m entryconfig 2 -state normal
	catch {exec /bin/rm -f $fname}
	return
    }

    writeData $file
    catch {close $file}
    
    $dialog request $widget(top) \
	"Please enter a printer name:" \
	[list [list Printer: $printer]] \
	[list send cancel]
    set printlist [$dialog result request]
    set button    [lindex $printlist 0]
    
    if {"$button" == "cancel"} {
	$widget(top).top.button.file.m entryconfig 2 -state normal
	catch {exec /bin/rm -f $fname}
	return
    }
    
    set printer [lindex $printlist 1]
    catch "exec lpr -P$printer $fname" res

    catch {exec /bin/rm -f $fname}
    $widget(top).top.button.file.m entryconfig 2 -state normal
}

body VIEWER::email {{screen_arg ""}} {
    # screen_arg tells the dialog box where to go
    if {"$screen_arg" == ""} {
	$dialog setscreen $screen
    } else {
	$dialog setscreen $screen_arg
    }

    global env
    $dialog request $widget(top) \
	"Please enter the email address:" \
	[list [list To: $address] \
	     [list Subject: "log report $name"] ] \
	[list send cancel] 

    set result [$dialog result request]
    if {[lindex $result 0] == "cancel"} return

    set to [lindex $result 1]
    if { $to == "" } {
        $dialog acknowledge $widget(top) "Sender required"
        dp_after 100 $this email
        return
    }
    set address $to
    
    set subject [lindex $result 2]
    if { $subject == "" } {
        $dialog acknowledge $widget(top) "Subject required"
        dp_after 100 $this email
        return
    }

    if {[catch {split $env(PATH) :} path]} {
	set path "/usr/bin /bin /usr/ucb /usr/local/bin"
    }

    if {[catch {open "|mail $to" w} file]} {
        $dialog acknowledge $widget(top) "Unable to write to \"$to\""
        return
    }
    puts $file "Subject: $subject"
    writeData $file
    puts $file "."
    update
    close $file
}

body VIEWER::loadWin {{screen_arg ""}} {
    # screen_arg tells the dialog box where to go

    set w $widget(load)
    if {[winfo exists $w]} {
	wm withdraw  $w
	wm deiconify $w
	return
    }
    
    # screen_arg tells window where to go
    if {"$screen_arg" == ""} {
	toplevel $w -screen $screen
    } else {
	toplevel $w -screen $screen_arg
    }
    wm title $w "for viewer $name"
    wm protocol $w WM_DELETE_WINDOW "wm withdraw $w"
    
    # use homebrewed itk time widget (class Time)
    label $w.top -text "Load Data" -bg white -anchor center
    frame $w.f1
    frame $w.f1.from -bd 3 -relief groove
    frame $w.f1.to   -bd 3 -relief groove
    
    label $w.f1.from.l -text From: -bg white -anchor center -bd 2 -relief groove
    Time $w.f1.from.from -value [expr [ns_systime]-86400]
    pack $w.f1.from.l -fill both -expand 1 -ipady 5
    pack $w.f1.from.from
    
    label $w.f1.to.l -text To: -bg white -anchor center -bd 2 -relief groove
    Time $w.f1.to.to -value [ns_systime]
    pack $w.f1.to.l -fill both -expand 1 -ipady 5
    pack $w.f1.to.to
    pack $w.f1.from $w.f1.to -side left -fill both
    
    label  $w.l -text "Maximum Number of Items to Retrieve:" -anchor center
    frame  $w.f2
    frame  $w.f2.left
    frame  $w.f2.right -bd 2 -relief groove
    radiobutton $w.f2.left.rb1 -variable [scope vars($name,numItems)] -value 1 -text "Set Limit" \
	-command [code pack $w.f2.right.items]
    radiobutton $w.f2.left.rb2 -variable [scope vars($name,numItems)] -value 0 -text " Get All " \
	-command [code pack forget $w.f2.right.items]
    pack $w.f2.left.rb2 $w.f2.left.rb1 -fill both -side left
    set vars($name,numItems) 0
    
    tixControl $w.f2.right.items -min 1 -max 10000 -value 200 -integer 1 \
	-options {entry.width 9}

    pack $w.f2.left $w.f2.right -side left -padx 10 -anchor center
    frame  $w.but
    
    button $w.but.apply  -text "Load  " \
	-command [code "$w.f2.right.items update
			$this busy on
			$this loadWinCmd \
			    [subst -nocommands {[$w.f1.from.from get -timeSec]}] \
			    [subst -nocommands {[$w.f1.to.to get -timeSec]}] \
			    0 \
			    \$vars($name,numItems) \
			    [subst -nocommands {[$w.f2.right.items cget -value]}]
			wm withdraw $w"]
    button $w.but.cancel -text "Cancel"\
	-command "wm withdraw $w
		  $widget(top).top.button.opts.m entryconfig 2 -state normal"
    pack   $w.but.apply $w.but.cancel -side left -fill x -expand 1
    
    pack $w.top -fill both -expand 1 -ipady 10 -anchor center
    pack $w.f1  -fill both
    pack $w.l   -fill x -expand 1 -pady 5 -anchor center
    pack $w.f2  -fill both -expand 1
    pack $w.but -fill both -expand 1 -pady 3 -padx 3
}


body VIEWER::loadNextWin {finaltime endtime {screen_arg ""}} {
    # disable load menu item
    $widget(top).top.button.opts.m entryconfig 2 -state disabled
    
    set w $widget(loadnext)
#puts "loadNextWin: ftime = $finaltime, etime = $endtime"
    if {[winfo exists $w]} {
	wm withdraw  $w
	wm deiconify $w
	$w.but.apply config \
		-command [code "wm withdraw $w
			  $w.f2.right.items update
			  $this busy on
			  $widget(top).top.button.opts.m entryconfig 2 -state normal
			  $this loadWinCmd $endtime $finaltime 1 \
			  \$vars($name,numItems) \
			  [subst -nocommands {[$w.f2.right.items cget -value]}]"]
	return
    }
  
    # screen_arg tells window where to go
    if {"$screen_arg" == ""} {
	toplevel $w -screen $screen
    } else {
	toplevel $w -screen $screen_arg
    }
    wm title $w "for viewer $name"
    wm protocol $w WM_DELETE_WINDOW "wm withdraw $w"
        
    label $w.top -text "Load More Data" -bg white -anchor center -bd 2 -relief groove
    label  $w.l -text "Maximum Number of Items to Retrieve:" -anchor center
    frame  $w.f2
    frame  $w.f2.left
    frame  $w.f2.right -bd 2 -relief groove
    radiobutton $w.f2.left.rb1 -variable [scope vars($name,numItems)] -value 1 -text "Set Limit" \
	-command [code pack $w.f2.right.items]
    radiobutton $w.f2.left.rb2 -variable [scope vars($name,numItems)] -value 0 -text " Get All " \
	-command [code pack forget $w.f2.right.items]
    pack $w.f2.left.rb2 $w.f2.left.rb1 -fill both -side left
    
    tixControl $w.f2.right.items -min 1 -max 10000 \
	-value [$widget(load).f2.right.items cget -value] \
	-integer 1 \
	-options {entry.width 9}
    pack $w.f2.right.items
    pack $w.f2.left $w.f2.right -side left -padx 10 -anchor center
    frame  $w.but
    button $w.but.apply  -text "Load  " \
	-command [code "wm withdraw $w
			$w.f2.right.items update
			$this busy on
			$widget(top).top.button.opts.m entryconfig 2 -state normal
			$this loadWinCmd $endtime $finaltime 1 \
			\$vars($name,numItems) \
			[subst -nocommands {[$w.f2.right.items cget -value]}]"]
    button $w.but.cancel -text "Cancel"\
	-command "wm withdraw $w
		  $widget(top).top.button.opts.m entryconfig 2 -state normal"
    pack   $w.but.apply $w.but.cancel -side left -fill x -expand 1
    
    pack $w.top -fill both -expand 1 -ipady 10 -anchor center
    pack $w.l   -fill x -expand 1 -pady 5 -anchor center
    pack $w.f2  -fill both -expand 1
    pack $w.but -fill both -expand 1 -pady 3 -padx 3
}

body VIEWER::loadWinCmd {time1 time2 append limitIt limit} {
    set w $widget(load)
#puts "loadWinCmd: $time1, $time2, $append, $limitIt, $limit"
    if {$time2 < $time1} {
	set temp  $time1
	set time1 $time2
	set time2 $temp
    } elseif {$time2 == $time1} {
	tk_dialog2 .err WARNING "Start and stop times must be different for loading data" {} 0 $screen DISMISS
	return
    }
    
    # turn off any existing monitor
    monitorUpdate off
    
    if {$limitIt == 1} {
	$dataObject getData $append $time1 $time2 $limit
    } else {
	$dataObject getData $append $time1 $time2
    }
}

body VIEWER::load {append {type query}} {
#set t1 [ns_systime]
    set w $widget(top)

    if {"$append" == "0"} {
	clear
    }
    
    # to access global data array declare it global
    # to make notation simpler assign to local "data" array
    global   [$dataObject get -dataArray]
    upvar #0 [$dataObject get -dataArray] data
    
    textWritable
    set rowCount 0
    
    # "filter" is a logical expr. used to filter data from display.
    # The data is in array of lines with each line made of (tag,value) pairs.
    # First, for each pair, a tag is compared to an existing list of tags
    # which are being used in the "filter" expression. If there's a match,
    # all occurrances of tag in "filter" are substituted with the value.
    # After all tags have been examined, it's ready for later evaluation.
    # Second, the tag is compared to tags selected for display. If no
    # match, on to the next tag.
    # If an item is to be displayed, the cmd to do it is kept in "cmds".
    # Finally, after all items from a line are processed, the filter
    # expression is evaluated. If it's equal to $filterVal(browser,$name,view),
    # then all the cmds are evaled.
#puts "load: start looking at data"	    
    
    for {set i 1} {$i <= $data(length)} {incr i} {		
	set rowadded 0
	set cmds ""
	set filter $filterVal(browser,$name,filter)
	foreach j $data($i) {
	    set tag [lindex $j 0]
	    set val [lindex $j 1]
#puts "load: tag,val = $tag, $val"	    

	    if {[lsearch -exact $filterVal(browser,$name,tags) $tag] != -1} {
		regsub -all $tag $filter $val filter
	    }
	    if {[lsearch -exact $col(tags) $tag] == -1} {
		continue
	    }

	    set rowadded 1
	    if {"$tag" == "cmlogTime"} {
		foreach k $col($tag) {
		    if {"[string tolower $k]" == "date"} {
			set date [format "%-8s" "[ns_ctime $val %D]"]
			lappend cmds [list $col($k,text) insert end $date [list $date menu]]
		    } elseif {"[string tolower $k]" == "time"} {
			set time [format "%-8s" "[ns_ctime $val %T]"]
			lappend cmds [list $col($k,text) insert end $time [list $time menu]]
		    } else {
			lappend cmds [list $col($k,text) insert end $val [list $val menu]]
		    }
		}
	    } else {
		foreach k $col($tag) {
		    regsub -all [subst {\n}] $val {} val
		    lappend cmds [list $col($k,text) insert end $val [list $val menu]]
		}
	    }
	}
	
	if {$rowadded == 1} {
#puts "load: try adding a row"
	    set a [expr $filter]
	    if {$a != $filterVal(browser,$name,view)} {
#puts "load: doesn't get past filter"
		continue
	    }
	    incr col(length)
	    incr rowCount
	    foreach j $cmds {
#puts "load: evaluate\"$j\""
		eval $j
	    }
	    # put in line feeds for all cols
	    foreach j $col(titles) {
		$col($j,text) insert end \n
	    }
#puts "load: added a row"
	}
#puts ""
    }
    
    if {$melt == 1} {
	foreach i $col(titles) {
	    $col($i,text) see end
	}
    }

    # Modify col(rows) to keep track of deleted rows:
    # if clearing first ..., else if appending ...

    if {"$col(rows)" == ""} {
	for {set i 0} {$i < $rowCount} {incr i} {lappend col(rows) $i}
    } else {
	# find the last row number
	set biggest_rownum [lindex $col(rows) end]
	foreach j $col(delrows) {
	    set rownum [lindex $j 1]
	    if {$rownum > $biggest_rownum} {
		set biggest_rownum $rownum
	    }
	}
	set next [expr 1 + $biggest_rownum]
	for {set i $next} {$i < [expr $next + $rowCount]} {incr i} {
	    lappend col(rows) $i
	}
    }
    
    # delete old lines if adding update lines & too many already displayed
    if {($col(maxlines) > 0) && ("$type" == "monitor")} {
	if {$col(length) > $col(maxlines)} {
	    deleteOldLines [expr $col(length) - $col(maxlines)]
	}
    }
    
    update idletasks
    textReadable
       
    # update the list of tags for filtering to include those found here
    if {$data(length) != 0} {
	foreach i $data($data(length)) {
	    set tag [lindex $i 0]
	    if {[lsearch -exact $allTagsList $tag] == -1} {
		lappend allTagsList $tag
	    }
	}
    }
    set allTagsList [lsort $allTagsList]
    busy off
#set t2 [ns_systime]
#puts "Delta t = [expr $t2 - $t1]"
}

body VIEWER::monitorUpdate {mode} {
    $dataObject monitorData $mode
    if {"$mode" == "on"} {
	$widget(top).bot.monitor.stat config -text "ON"
	set vars($name,update) 1
    } elseif {"$mode" == "off"} {
	$widget(top).bot.monitor.stat config -text "OFF"
	set vars($name,update) 0
    }
}

body VIEWER::setUpdateCmd {max_lines} {
    set col(maxlines) $max_lines
}

body VIEWER::setUpdate {{screen_arg ""}} {
     
    set w $widget(update)
    if {[winfo exists $w]} {
	wm withdraw  $w
	wm deiconify $w
	return
    }
    
    # screen_arg tells window where to go
    if {"$screen_arg" == ""} {
	toplevel $w -screen $screen
    } else {
	toplevel $w -screen $screen_arg
    }
    
    wm title $w "Update $name"
    wm protocol $w WM_DELETE_WINDOW "wm withdraw $w"

    frame $w.top
    label $w.top.l -text "Automatic\nViewer Updating" -bd 2 -relief groove\
	-justify center -width 15 -anchor center -bg white
    
    frame $w.top.f -bd 2 -relief groove
    # the "code" command is needed since routines are private
    radiobutton $w.top.f.r1 -variable [scope vars($name,update)] -value 1 -text "ON "\
	-command [code $this monitorUpdate on]
    radiobutton $w.top.f.r2 -variable [scope vars($name,update)] -value 0 -text OFF\
	-command [code $this monitorUpdate off]
	
    frame $w.bot
    tixControl  $w.bot.ml -integer 1 -min 0 -command  [code $this setUpdateCmd] \
			  -label " Maximum Viewed Lines " \
			  -selectmode immediate -value $col(maxlines)
    pack $w.bot.ml -fill both -expand 1
    
    pack $w.top.f.r1 $w.top.f.r2 -pady 2 -padx 2
    pack $w.top.l -side left -fill both -expand 1
    pack $w.top.f -side left

    button $w.but -text "Dismiss" -command "destroy $w"
    
    pack $w.top -fill x
    pack $w.bot -fill x
    pack $w.but -fill both -expand 1
}

#############################################################################
#
# FILTER INFO
#
#    The filter window allows for both server-side and browser-side
#    filtering. When using the window, the variables filterVal($name, ...)
#    are used. These are copied to either filterVal(server,$name, ...) or
#    filterVal(browser,$name, ...) when the "Apply" button is hit.
#
#
# FILTER LINGO
#
# Variables used with filter window:
#
#    filterVal($name,list)	  is the list of lists which holds all
#				  elements of the filter condition:
#				  {{val type} {val type} ...}
#    filterVal($name,reallist)	  is filterVal($name,list) with substitutions
#				  made necessary for correct tcl parsing (i.e.
#				  quotes are added, etc)
#    filterVal($name,lasttype)	  is the type (del, op, val, tag, conj, rpar, or lpar)
#				  of the last element of filterVal($name,list)
#    filterVal($name,tags)	  is a list of cdev tags in filterVal($name,list).
#				  It's used for efficient data loading.
#    filterVal($name,clause)	  is a string made from the list elements of
#				  filterVal($name,reallist)
#    filterVal($name,filter)      is the string used to actually do the filtering
#				  in the "load" routine. It is made equal to 
#				  filterVal($name,clause) when the "Apply" button
#				  is hit. It = 1 when filterVal($name,clause) = ""
#    filterVal($name,gui)         is a string made from the list elements of
#				  filterVal($name,list) which is what is viewed
#				  in the "Filter" window. It doesn't contain all
#				  the potentially confusing substitutions required
#				  for real parsing.
#    filterVal($name,error)	  is = 1 if filter condition has an error or is
#				  incomplete
#    filterVal($name,errorType)	  specifies type of error
#    filterVal($name,view)	  =1 means "view only", =0 means "filter out"
#    filterVal($name,type)	  =browser or =server for selecting type of filter
#    filterVal($name,oplist)	  list of all operators defined
#    filterVal($name,ops)	  operator currently chosen for input into filter
#    filterVal($name,val)	  value    currently chosen for input into filter
#
# Variables used for browser:
#
#    filterVal(browser,$name,...) same as above, only for browser side
#
# Variables used for server:
#
#    filterVal(server,$name,...) same as above, only for server side
#
# Types of filter condition list element types:
#
#    del  - deletion
#    op   - operator (<, <=, >, >=, ==, !=, match, or notmatch for browser)
#    val  - value
#    tag  - cdev tag
#    conj - conjunction (&& ||)
#    lpar - left parenthesis  "("
#    rpar - right parenthesis ")"
#
# Match & Nomatch:
#
#    These operators are ==, != for use with glob-style matching rules:
#    (*,?,{},\)
#    
#############################################################################

body VIEWER::filter {{screen_arg ""}} {
    # definition of the message filter window
    set w $widget(filter)
    
    if {[winfo exists $w]} {
	wm withdraw  $w
	wm deiconify $w
	return
    }

    # screen_arg tells window where to go
    if {"$screen_arg" == ""} {
	toplevel $w -screen $screen
    } else {
	toplevel $w -screen $screen_arg
    }
    wm title $w "Message Filter for $name"
    wm protocol $w WM_DELETE_WINDOW "wm withdraw $w"

    frame $w.top
    label $w.top.l -text "Message\nFilter" -bd 2 -relief groove\
	-justify center -width 15 -anchor center -bg white
    
    frame $w.top.m -bd 2 -relief groove
    # the "code" command is needed since routines are private
    radiobutton $w.top.m.r1\
	-variable [scope filterVal($name,type)] \
	-value server -text "Server " \
	-command [code $this filterSwitchModes server]
    radiobutton $w.top.m.r2\
	-variable [scope filterVal($name,type)] \
	-value browser -text "Browser" \
	-command [code $this filterSwitchModes browser]
	
    frame $w.top.r -bd 2 -relief groove
    # the "code" command is needed since routines are private
    radiobutton $w.top.r.r1\
	-variable [scope filterVal($name,view)] \
	-value 1 -text "View Only" \
	-command "$w.but.apply config -highlightbackground red -highlightcolor red"
    radiobutton $w.top.r.r2\
	-variable [scope filterVal($name,view)] \
	-value 0 -text "Filter Out" \
	-command "$w.but.apply config -highlightbackground red -highlightcolor red"
	
    pack $w.top.m.r1 $w.top.m.r2 -pady 2 -padx 2 -anchor w
    pack $w.top.r.r1 $w.top.r.r2 -pady 2 -padx 2 -anchor w
    pack $w.top.l -side left -fill both -expand 1
    pack $w.top.m -side left
    pack $w.top.r -side left
   
    frame $w.mid
    frame $w.mid.f1
    frame $w.mid.f1.f1
    frame $w.mid.f1.f2
    frame $w.mid.f1.f3
    frame $w.mid.f1.f4
    
    # operators
    set filterVal($name,ops)  ""
    label $w.mid.f1.f1.l -text "Tag/Operator"
    tixComboBox   $w.mid.f1.f1.tag\
		  -command "[code $this filterDefine tag]"\
		  -editable 1\
		  -history 1\
		  -historylimit 50
    tixOptionMenu $w.mid.f1.f1.op\
		  -variable [scope filterVal($name,ops)]\
		  -command "[code $this filterDefine op]"	  
    $w.mid.f1.f1.tag config -disablecallback 1
    $w.mid.f1.f1.op  config -disablecallback 1
    foreach i $allTagsList {
	$w.mid.f1.f1.tag appendhistory $i
    }

    foreach i $filterVal($name,oplist) {
	$w.mid.f1.f1.op add command $i
    }
    $w.mid.f1.f1.tag config -disablecallback 0
    $w.mid.f1.f1.op  config -disablecallback 0
    pack $w.mid.f1.f1.l -anchor center -pady 5
    pack $w.mid.f1.f1.tag $w.mid.f1.f1.op -fill x
    
    # conjunctions
    label  $w.mid.f1.f2.l -text "And/Or"
    button $w.mid.f1.f2.and -text "&&" -command [code $this filterDefine conj &&]
    button $w.mid.f1.f2.or  -text "||" -command [code $this filterDefine conj ||]
    pack   $w.mid.f1.f2.l -anchor center -pady 5
    pack   $w.mid.f1.f2.and $w.mid.f1.f2.or -fill x
    
    # parentheses
    label  $w.mid.f1.f3.l -text "Paren"
    button $w.mid.f1.f3.lpar -text " (" -command [code $this filterDefine lpar (]
    button $w.mid.f1.f3.rpar -text " )" -command [code $this filterDefine rpar )]
    pack   $w.mid.f1.f3.l -anchor center -pady 5
    pack   $w.mid.f1.f3.lpar $w.mid.f1.f3.rpar -fill x
    
    # deletions
    label  $w.mid.f1.f4.l -text "Delete"
    button $w.mid.f1.f4.delL -text "Delete Last" -command [code $this filterDefine del 1]
    button $w.mid.f1.f4.delA -text "Delete  All" -command [code $this filterDefine del a]
    pack   $w.mid.f1.f4.l -anchor center -pady 5
    pack   $w.mid.f1.f4.delL $w.mid.f1.f4.delA -fill x
    
    pack   $w.mid.f1.f1 $w.mid.f1.f2 $w.mid.f1.f3 $w.mid.f1.f4\
	   -side left -fill x -expand 1

    frame $w.mid.f2
    set filterVal($name,val)  ""
    tixComboBox $w.mid.f2.tcb\
	-label " Value: "\
	-labelside left\
	-variable [scope filterVal($name,val)]\
	-editable 1 \
	-command "[code $this filterDefine val]" \
	-history 1 \
	-historylimit 20 \
	-options {entry.width 30}
    pack  $w.mid.f2.tcb -padx 5 -pady 5

    frame $w.mid.f3   -bd 3 -relief groove
    label $w.mid.f3.l -text "Filter Condition"
    entry $w.mid.f3.e -textvariable [scope filterVal($name,gui)]
    pack  $w.mid.f3.l -pady 5 -anchor center
    pack  $w.mid.f3.e -pady 5 -fill x -expand 1

    pack  $w.mid.f1 $w.mid.f2 $w.mid.f3 -fill x -expand 1
    
    frame  $w.but
    button $w.but.apply  -text "Apply " -command [code $this filterApply]
    button $w.but.cancel -text "Cancel" -command "wm withdraw $w"
    pack   $w.but.apply $w.but.cancel -side left -fill x -expand 1
   
    pack $w.top $w.mid $w.but -fill both
}

body VIEWER::filterSwitchModes {mode} {
    set w $widget(filter)
    
    if {"$mode" == "server"} {
#	set filterVal($name,reallist) $filterVal(server,$name,reallist)
	set filterVal($name,lasttype) $filterVal(server,$name,lasttype)
#	set filterVal($name,clause)   $filterVal(server,$name,clause)
	set filterVal($name,oplist)   [$dataObject get -oplist]
	set filterVal($name,list)     $filterVal(server,$name,list)
	set filterVal($name,view)     $filterVal(server,$name,view)
	set filterVal($name,tags)     $filterVal(server,$name,tags)
	set filterVal($name,gui)      $filterVal(server,$name,gui)
	set filterVal($name,val)      ""

	set items [$w.mid.f1.f1.op entries]
	foreach i $items {
	    $w.mid.f1.f1.op delete $i
	}
	foreach i $filterVal($name,oplist) {
	    $w.mid.f1.f1.op add command $i
	}
	$w.but.apply config -highlightbackground {gray80} -highlightcolor {gray80}
	
    } elseif {"$mode" == "browser"} {
	set filterVal($name,reallist) $filterVal(browser,$name,reallist)
	set filterVal($name,lasttype) $filterVal(browser,$name,lasttype)
	set filterVal($name,filter)   $filterVal(browser,$name,filter)
	set filterVal($name,clause)   $filterVal(browser,$name,clause)
	set filterVal($name,oplist)   $filterVal(browser,$name,oplist)
	set filterVal($name,list)     $filterVal(browser,$name,list)
	set filterVal($name,view)     $filterVal(browser,$name,view)
	set filterVal($name,tags)     $filterVal(browser,$name,tags)
	set filterVal($name,gui)      $filterVal(browser,$name,gui)
	set filterVal($name,val)      ""
	
	set items [$w.mid.f1.f1.op entries]
	foreach i $items {
	    $w.mid.f1.f1.op delete $i
	}
	foreach i $filterVal($name,oplist) {
	    $w.mid.f1.f1.op add command $i
	}
 	$w.but.apply config -highlightbackground {gray80} -highlightcolor {gray80}
   }
}

# The only decent way I could find of updating the tixComboBox every time
# "allTagsList" was changed (in "load") was to put on a trace & call the
# following func. It's ugly but works.

body VIEWER::filterUpdate {n el op} {
#puts "got filterupdate"
    set w $widget(filter)
    if {[winfo exists $w] != 1} {
	return
    }
    set listbx [$w.mid.f1.f1.tag subwidget listbox]
    $listbx delete 0 end
    $w.mid.f1.f1.tag config -disablecallback 1
    foreach i $allTagsList {
	$w.mid.f1.f1.tag appendhistory $i
    }
    $w.mid.f1.f1.tag config -disablecallback 0
}

body VIEWER::filterApply {} {
    set w $widget(filter)
    if {$filterVal($name,error) == 1} {
	tk_dialog2 .err WARNING "Error in filter condition: $filterVal($name,errorType)" {} 0 $screen DISMISS
	return
    }
    
    if {"$filterVal($name,type)" == "server"} {
#	set filterVal(server,$name,reallist) $filterVal($name,reallist)
	set filterVal(server,$name,lasttype) $filterVal($name,lasttype)
#	set filterVal(server,$name,clause)   $filterVal($name,clause)
	set filterVal(server,$name,list)     $filterVal($name,list)
	set filterVal(server,$name,view)     $filterVal($name,view)
	set filterVal(server,$name,tags)     $filterVal($name,tags)
	set filterVal(server,$name,gui)      $filterVal($name,gui)
	
	set type [$dataObject get -type]
	if {"$type" == "cmlog"} {
	    $dataObject setFilter $filterVal($name,list) $filterVal($name,view)
	}
	
	if {"$filterVal($name,list)" == ""} {
	    $widget(top).bot.sfilter.stat config -text "OFF"
	} else {
	    $widget(top).bot.sfilter.stat config -text "ON"
	}
    } else {
	set filterVal(browser,$name,reallist) $filterVal($name,reallist)
	set filterVal(browser,$name,lasttype) $filterVal($name,lasttype)
	set filterVal(browser,$name,clause)   $filterVal($name,clause)
	set filterVal(browser,$name,list)     $filterVal($name,list)
	set filterVal(browser,$name,view)     $filterVal($name,view)
	set filterVal(browser,$name,tags)     $filterVal($name,tags)
	set filterVal(browser,$name,gui)      $filterVal($name,gui)
	
	set filterVal($name,filter) $filterVal($name,clause)
	if {"$filterVal($name,filter)" == ""} {
	    set filterVal($name,filter) 1
	    $widget(top).bot.bfilter.stat config -text "OFF"
	} else {
	    $widget(top).bot.bfilter.stat config -text "ON"
	}
	set filterVal(browser,$name,filter)   $filterVal($name,filter)
    }
    if {[winfo exists $w.but.apply] == 1} {
	$w.but.apply config -highlightbackground {gray80} -highlightcolor {gray80}
    }
}

body VIEWER::filterDefine {type arg} {
    set w $widget(filter)
    set entryWidget $w.mid.f3.e
   
    set addit 0
    switch $type {
	del  {  set addit -1 }
	op   {  if {"$filterVal($name,lasttype)" == "tag"} {set addit 1}}
	val  {  if {"$filterVal($name,lasttype)" == "op" } {set addit 1}}
	tag  {  if {"$filterVal($name,lasttype)" == "" ||
		    "$filterVal($name,lasttype)" == "lpar" ||
		    "$filterVal($name,lasttype)" == "conj"} {
		    
		    set addit 1
		    lappend filterVal($name,tags) $arg
		}
	     }
	conj {  if {"$filterVal($name,lasttype)" == "val" ||
		    "$filterVal($name,lasttype)" == "rpar"} {set addit 1}
	     }
	lpar {  if {"$filterVal($name,lasttype)" == "" ||
		    "$filterVal($name,lasttype)" == "lpar" ||
		    "$filterVal($name,lasttype)" == "conj"} {set addit 1}
	     }
	rpar {  if {"$filterVal($name,lasttype)" == "val" ||
		    "$filterVal($name,lasttype)" == "rpar"} {set addit 1}
	     }
    }
    
    if {$addit == 0} {
	# syntax rules violated, ignore input
	return
    } elseif {$addit == 1} {
	# add another part to filter condition
	lappend filterVal($name,list) [list "$arg" $type]
	set filterVal($name,lasttype) $type
    } else {
	# subtract item(s)
	set length [llength $filterVal($name,list)]
	if {"$arg" == "a"} {
	    set num $length
	} else {
	    set num $arg
	    if {$num > $length} {set num $length}
	}
	for {set i 0} {$i < $num} {incr i} {
	    set lasttype [lindex [lindex $filterVal($name,list) end] 1]
	    set filterVal($name,list) [lreplace $filterVal($name,list) end end]

	    if {"$lasttype" == "tag"} {
		set filterVal($name,tags) [lreplace $filterVal($name,tags) end end]
	    }

	    set lasttype [lindex [lindex $filterVal($name,list) end] 1]
	    set filterVal($name,lasttype) $lasttype
	}    
    }
#puts "fDefine: fVal($name,list) = $filterVal($name,list)"
    
    # Make proper substitutions to list of filter conditions so that
    # it parses (evaluates) properly in tcl. Store in filter($name,reallist)
    filterSub
    
    $w.but.apply config -highlightbackground red -highlightcolor red
}

body VIEWER::filterSub {} {
    set filterVal($name,reallist) ""
    set count_lpar 0
    set count_rpar 0
   
    foreach i $filterVal($name,list) {
	set arg  [lindex $i 0]
	set type [lindex $i 1]
	if {"$type" == "lpar"} {
	    # count left parentheses
	    incr count_lpar
	    
	} elseif {"$type" == "rpar"} {
	    # count right parentheses
	    incr count_rpar	
	    
	} elseif {"$type" == "tag"} {
	    # make sure all tags have curly brackets (conversions
	    # to numbers are automatically taken care of by tcl)
	    set arg \{$arg\}
	    
	} elseif {"$type" == "val"} {
	    # go back a couple items (to operator and tag)
	    set opIndx  [expr  [llength $filterVal($name,reallist)] -1]
	    set tagIndx [expr  [llength $filterVal($name,reallist)] -2]
	    set opArg   [lindex [lindex $filterVal($name,reallist) $opIndx]  0]
	    set tagArg  [lindex [lindex $filterVal($name,reallist) $tagIndx] 0]

	    # put brackets around val as well
	    set arg \{$arg\}
	    
	    # find out if op = "match" or "notmatch"
	    if {("$opArg" == "match")||("$opArg" == "notmatch")} {
		set newArg "\[string match $arg $tagArg\]"
		set filterVal($name,reallist)\
			[lreplace $filterVal($name,reallist) $tagIndx $opIndx\
				[list $newArg tag] [list == op]]
		set arg \{1\}
		if {"$opArg" == "notmatch"} {set arg \{0\}}

	    }
	} elseif {("$type" == "op") || ("$type" == "conj")} {
	    # nothing
	}
	
	lappend filterVal($name,reallist) [list $arg $type]
    }
    
    set filterVal($name,clause) ""
    foreach i $filterVal($name,reallist) {
	set j [lindex $i 0]
	set filterVal($name,clause) "$filterVal($name,clause)$j "
    }
    
    set filterVal($name,gui) ""
    foreach i $filterVal($name,list) {
	set j [lindex $i 0]
	set filterVal($name,gui) "$filterVal($name,gui)$j "
    }
    
    set filterVal($name,error) 0
    
    if {$count_lpar != $count_rpar} {
	set filterVal($name,error) 1
	set filterVal($name,errorType) "Parentheses Mismatch"
    }
    
    if {("$filterVal($name,lasttype)" != "val")  &&\
	("$filterVal($name,lasttype)" != "rpar") &&\
	("$filterVal($name,lasttype)" != "")} {
	set filterVal($name,error) 1
	set filterVal($name,errorType) "Incomplete condition"
    }
#puts "fSub, reallist  = $filterVal($name,reallist)"
#puts "fSub, clause    = $filterVal($name,clause)"
#puts "fSub, guiclause = $filterVal($name,gui)"
}


#########################################################
#
#    Class Time
#	
#    Purpose:
#	This class defines an itcl megawidget used to specify a
#	certain time. It takes account of leap years/days and the
#	"Seconds:" widget can be set by hand to 60 or 61 seconds
#	in order to account for 1 or 2 leap seconds.
#
#    Methods:
#	The only public method is "get" which makes all data
#	available.
#	
#    Use:
#	% set   timewidget .topWindow.time
#	% Time $timewidget -value [ns_systime]
#	% pack $timewidget
#	...
#	% set timeInSeconds [$timewidget get -timeSec]
#
#
#########################################################

class Time {
    inherit itk::Widget
    
    constructor {args} {} 
    destructor  {} 

    public  method get        {s} {}
    
    private method set_year   {s} {}
    private method set_month  {s} {}
    private method set_day    {s} {}
    private method set_hour   {s} {}
    private method set_min    {s} {}
    private method set_sec    {s} {}
    private method set_time   {s} {}
    private method reset_time {}  {}
    
    private method incr_day   {s} {}
    private method decr_day   {s} {}
    private method incr_hour  {s} {}
    private method decr_hour  {s} {}
    private method incr_min   {s} {}
    private method decr_min   {s} {}
    private method incr_sec   {s} {}
    private method decr_sec   {s} {}

    private variable time
    private variable month January
    private variable year  1997
    private variable day   1
    private variable hour  0
    private variable min   0
    private variable sec   0
    private variable secMax 61
    private variable dayMax 31
}

body Time::get {option} {
    # return values of private variables
    reset_time
    switch -- $option {
	-year      {return $year}
	-month	   {return $month}
	-day	   {return $day}
	-hour	   {return $hour}
	-min	   {return $min}
	-sec	   {return $sec}
	-timeSec   {return $time(sec)}
	-timeDate  {return $time(date)}
	-timeClock {return $time(clock)}
	-timeTotal {return $time(total)}
    }
    error "Time::get bad option \"$option\""
}

body Time::constructor {args} {
  
    itk_component add year {
	tixControl $itk_interior.a -label "Year: " -integer true \
	    -bd 2 \
	    -command [code $this set_year] -min 1970 -max 2037 \
	    -value 1997 \
	    -options {
		entry.width 9
		label.padY 5
		label.width 11
		label.anchor e
	    }
    } {
	keep -background
	keep -cursor
    }
    
    itk_component add month {
	tixComboBox $itk_interior.b -label "Month: " -dropdown true \
	    -bd 2 \
	    -command [code $this set_month] -editable false \
	    -options {
		listbox.height 6
		entry.width 9
		label.padY 5
		label.width 11
		label.anchor e
	    }
    } {
	keep -background
	keep -cursor
    }

    itk_component add day {
	tixControl $itk_interior.c -label "Day: " -integer true \
	    -bd 2 \
	    -command [code $this set_day] -min 1 -max $dayMax\
	    -incrcmd [code $this incr_day] \
	    -decrcmd [code $this decr_day] \
	    -options {
		entry.width 9
		label.padY 5
		label.width 11
		label.anchor e
	    }
    } {
	keep -background
	keep -cursor
    }

    itk_component add hour {
	tixControl $itk_interior.d -label "Hours: " -integer true \
	    -bd 2 \
	    -command [code $this set_hour] -min 0 -max 23\
	    -incrcmd [code $this incr_hour] \
	    -decrcmd [code $this decr_hour] \
	    -options {
		entry.width 9
		label.padY 5
		label.width 11
		label.anchor e
	    }
    } {
	keep -background
	keep -cursor
    }

    itk_component add min {
	tixControl $itk_interior.e -label "Minutes: " -integer true \
	    -bd 2 \
	    -command [code $this set_min] -min 0 -max 59\
	    -incrcmd [code $this incr_min] \
	    -decrcmd [code $this decr_min] \
	    -options {
		entry.width 9
		label.padY 5
		label.width 11
		label.anchor e
	    }
    } {
	keep -background
	keep -cursor
    }

    itk_component add sec {
	tixControl $itk_interior.f -label "Seconds: " -integer true \
	    -bd 2 \
	    -command [code $this set_sec] -min 0 -max $secMax\
	    -incrcmd [code $this incr_sec] \
	    -decrcmd [code $this decr_sec] \
	    -options {
		entry.width 9
		label.padY 5
		label.width 11
		label.anchor e
	     }
    } {
	keep -background
	keep -cursor
    }

    itk_component add time {
	tixControl $itk_interior.time -label "  Time (sec): " -integer true \
	    -bd 2 \
	    -command [code $this set_time] -min 18000 \
	    -options {
		entry.width 9
		label.padY 5
		label.width 11
		label.anchor e
	     }
    } {
	keep -value
	keep -background
	keep -cursor
    }

    pack $itk_component(year)  -anchor w -fill both -expand 1
    pack $itk_component(month) -anchor w -fill both -expand 1
    pack $itk_component(day)   -anchor w -fill both -expand 1
    pack $itk_component(hour)  -anchor w -fill both -expand 1
    pack $itk_component(min)   -anchor w -fill both -expand 1
    pack $itk_component(sec)   -anchor w -fill both -expand 1
    pack $itk_component(time)  -anchor w -fill both -expand 1

    # Insert the choices into the month & year combo boxes
    $itk_component(month) insert end January
    $itk_component(month) insert end February
    $itk_component(month) insert end March
    $itk_component(month) insert end April
    $itk_component(month) insert end May
    $itk_component(month) insert end June
    $itk_component(month) insert end July
    $itk_component(month) insert end August
    $itk_component(month) insert end September
    $itk_component(month) insert end October
    $itk_component(month) insert end November
    $itk_component(month) insert end December
    
    bind $itk_component(year) <Leave> {+ %W update}
    bind $itk_component(day)  <Leave> {+ %W update}
    bind $itk_component(hour) <Leave> {+ %W update}
    bind $itk_component(min)  <Leave> {+ %W update}
    bind $itk_component(sec)  <Leave> {+ %W update}
    bind $itk_component(time) <Leave> {+ %W update}

    eval itk_initialize $args
}

body Time::set_year {s} {
    if {"$month" == "February"}  {
	if {[expr ($s/4)*4 ] == $s} {
	    if {([expr ($s/100)*100 ] == $s) && \
		([expr ($s/400)*400 ] != $s)} {
		set dayMax 28
	    } else {
		set dayMax 29
	    }
	} else { 
	    set dayMax 28
	}
    }
    $itk_component(day) config -max $dayMax
    if {$day > $dayMax} {
	set_day $dayMax
    }
    
    set year $s
    tixSetSilent $itk_component(year) $s
    reset_time
}

body Time::set_month {s} {
    switch $s {
	January   {set dayMax 31}
	February  {
	    if {[expr ($year/4)*4 ] == $year} {
		if {([expr ($year/100)*100 ] == $year) && \
		    ([expr ($year/400)*400 ] != $year)} {
		    set dayMax 28
		} else {
		    set dayMax 29
		}
	    } else { 
		set dayMax 28
	    }
	}
	March     {set dayMax 31}
	April     {set dayMax 30}
	May       {set dayMax 31}
	June      {set dayMax 30}
	July      {set dayMax 31}
	August    {set dayMax 31}
	September {set dayMax 30}
	October   {set dayMax 31}
	November  {set dayMax 30}
	December  {set dayMax 31}
    }
    $itk_component(day) config -max $dayMax
    if {$day > $dayMax} {
	set_day $dayMax
    }
    set month $s
    tixSetSilent $itk_component(month) $s
    reset_time
}

body Time::set_day {s} {
    set day $s
    tixSetSilent $itk_component(day) $s
    reset_time
}

body Time::incr_day {val} {
    incr val
    if {$val > $dayMax} {set val 1}
    return $val
}
body Time::decr_day {val} {
    incr val -1
    if {$val < 1} {set val $dayMax}
    return $val
}

body Time::set_hour {s} {
    set hour $s
    tixSetSilent $itk_component(hour) $s
    reset_time
}

body Time::incr_hour {val} {
    incr val
    if {$val > 23} {set val 0}
    return $val
}
body Time::decr_hour {val} {
    incr val -1
    if {$val < 0} {set val 23}
    return $val
}

body Time::set_min {s} {
    set min $s
    tixSetSilent $itk_component(min) $s
    reset_time
}

body Time::incr_min {val} {
    incr val
    if {$val > 59} {set val 0}
    return $val
}
body Time::decr_min {val} {
    incr val -1
    if {$val < 0} {set val 59}
    return $val
}

body Time::set_sec {s} {
    set sec $s
    tixSetSilent $itk_component(sec) $s
    reset_time
}

body Time::incr_sec {val} {
    incr val
    if {$val > 59} {set val 0}
    return $val
}
body Time::decr_sec {val} {
    incr val -1
    if {$val < 0} {set val 59}
    return $val
}

body Time::reset_time {} {
    set time(sec)   [ns_ptime "$year $month $day $hour $min $sec" "%Y %B %d %H %M %S"]
#puts "$time(sec)\n"
    set time(date)  [ns_ctime $time(sec) %x]
    set time(clock) [ns_ctime $time(sec) %X]
    set time(total) [ns_ctime $time(sec) %c]
    tixSetSilent $itk_component(time) $time(sec)
    update idletasks
}

body Time::set_time {t} {
    set month [ns_ctime $t %B]
    # it's necessary to strip off initial zeros so
    # there will be no confusion with octal numbers
    regsub {(0+)([1-2][0-9]+)} [ns_ctime $t %Y] {\2} year
    regsub {(0+)([1-9])} [ns_ctime $t %d] {\2} day
    regsub {(0+)([0-9])} [ns_ctime $t %H] {\2} hour
    regsub {(0+)([0-9])} [ns_ctime $t %M] {\2} min
    regsub {(0+)([0-9])} [ns_ctime $t %S] {\2} sec
#puts "Set Time: $year, $month, $day, $hour, $min, $sec"
    tixSetSilent $itk_component(year)  $year
    tixSetSilent $itk_component(month) $month
    tixSetSilent $itk_component(day)   $day
    tixSetSilent $itk_component(hour)  $hour
    tixSetSilent $itk_component(min)   $min
    tixSetSilent $itk_component(sec)   $sec
    set time(sec)    $t
    set time(date)  [ns_ctime $t %x]
    set time(clock) [ns_ctime $t %X]
    set time(total) [ns_ctime $t %c]
    update idletasks
}

#########################################
#
#	tk_dialog is expanded to allow
#	display on another screen.
#
#########################################

proc tk_dialog2 {w title text bitmap default screen args} {
    global tkPriv

    # 1. Create the top-level window and divide it into top
    # and bottom parts.

    catch {destroy $w}
    if {[catch {toplevel $w -class Dialog -screen $screen} msg] == 1} {
	toplevel $w -class Dialog
    }
    wm title $w $title
    wm iconname $w Dialog
    wm protocol $w WM_DELETE_WINDOW { }
    wm transient $w [winfo toplevel [winfo parent $w]]
    frame $w.top -relief raised -bd 1
    pack $w.top -side top -fill both
    frame $w.bot -relief raised -bd 1
    pack $w.bot -side bottom -fill both

    # 2. Fill the top part with bitmap and message (use the option
    # database for -wraplength so that it can be overridden by
    # the caller).

    option add *Dialog.msg.wrapLength 3i widgetDefault
    label $w.msg -justify left -text $text \
	    -font -Adobe-Times-Medium-R-Normal--*-180-*-*-*-*-*-*
    pack $w.msg -in $w.top -side right -expand 1 -fill both -padx 3m -pady 3m
    if {$bitmap != ""} {
	label $w.bitmap -bitmap $bitmap
	pack $w.bitmap -in $w.top -side left -padx 3m -pady 3m
    }

    # 3. Create a row of buttons at the bottom of the dialog.

    set i 0
    foreach but $args {
	button $w.button$i -text $but -command "set tkPriv(button) $i"
	if {$i == $default} {
	    frame $w.default -relief sunken -bd 1
	    raise $w.button$i $w.default
	    pack $w.default -in $w.bot -side left -expand 1 -padx 3m -pady 2m
	    pack $w.button$i -in $w.default -padx 2m -pady 2m
	} else {
	    pack $w.button$i -in $w.bot -side left -expand 1 \
		    -padx 3m -pady 2m
	}
	incr i
    }

    # 4. Withdraw the window, then update all the geometry information
    # so we know how big it wants to be, then center the window in the
    # display and de-iconify it.

    wm withdraw $w
    update idletasks
    set x [expr [winfo screenwidth $w]/2 - [winfo reqwidth $w]/2 \
	    - [winfo vrootx [winfo parent $w]]]
    set y [expr [winfo screenheight $w]/2 - [winfo reqheight $w]/2 \
	    - [winfo vrooty [winfo parent $w]]]
    wm geom $w +$x+$y
    wm deiconify $w

    # 5. Set a grab and claim the focus too.

    set oldFocus [focus]
    set oldGrab [grab current $w]
    if {$oldGrab != ""} {
	set grabStatus [grab status $oldGrab]
    }
    grab $w
    tkwait visibility $w
    if {$default >= 0} {
	focus $w.button$default
    } else {
	focus $w
    }

    # 6. Wait for the user to respond, then restore the focus and
    # return the index of the selected button.  Restore the focus
    # before deleting the window, since otherwise the window manager
    # may take the focus away so we can't redirect it.  Finally,
    # restore any grab that was in effect.

    tkwait variable tkPriv(button)
    catch {focus $oldFocus}
    destroy $w
    if {$oldGrab != ""} {
	if {$grabStatus == "global"} {
	    grab -global $oldGrab
	} else {
	    grab $oldGrab
	}
    }
    return $tkPriv(button)
}
 
 
#########################################################
#
#    Class DIALOG
#	
#    Purpose:
#	This class defines convenient dialog windows.
#
#    Methods:
#	
#    Use:
#
#
#########################################################
   class DIALOG {

    constructor {args} {} 
    destructor {} 

    public  method setscreen {s} {}
    public  method position  {w} {}
    private method create    {w} {}
    private method name      {w lines bm} {}
    private method buttons   {w blist} {}

    public  method request_callback {w tlist button} {}

    private variable widget          ""
    private variable screen          ""
    private variable confirm_result  ""
    private variable general_result  ""
    private variable request_result  ""

    public method general     {c args} {}
    public method acknowledge {c args} {}
    public method confirm     {c args} {}
    public method request     {c args} {}
    public method result      {name {value ""}} {}

}

body DIALOG::result {name {value ""}} {
    if {$value != ""} {
	set ${name}_result $value
    } else {
	return [set ${name}_result]
    }
}

body DIALOG::constructor {args} {
}

body DIALOG::destructor {} {
}

body DIALOG::setscreen {s} {
    set screen $s
}

body DIALOG::position {w} {
    set top [winfo toplevel $widget]
    wm withdraw $w
    update idletasks
    
    set x [expr {[winfo rootx $top]+[winfo vrootx $top]+100}]
    set y [expr {[winfo rooty $top]+[winfo vrooty $top]+100}]
	
    if {$x < 0} { set x 0 }
    
    if {$y < 0} { set y 0 }
    
    wm geometry $w +$x+$y
    wm deiconify $w    
}

## Make a dialog toplevel window with the given dialog name.

body DIALOG::create {w} {
    
    catch {destroy $w}
    if {$screen != ""} {
	toplevel $w -screen $screen
    } else {
	toplevel $w
    }
    set widget $w
}

## Make a title for a dialog. Create a message containing the lines
## given in the lines argument.

body DIALOG::name {w lines {bm info}} {
    set text ""
    frame $w
    foreach line $lines { append text "$line\n" }
    message $w.m -aspect 25000 -text $text
    label $w.l -bitmap $bm
    pack $w.l -padx 10 -side left -in $w 
    pack $w.m -side left -in $w
    
    pack $w -side top
}

## Most dialogs have a list of buttons in the bottom. This proc
## creates these buttons and makes the first one the default.

body DIALOG::buttons {w args} {
    tixButtonBox $w.box -orientation horizontal
    
    foreach button $args {
	foreach arg $button {
	    $w.box add [lindex $arg 0] -text [lindex $arg 0] \
		-command "[lindex $arg 1];destroy $w" \
		-width 5
	}
    }
    pack $w.box -side bottom -fill both
    
}


## An acknowledge dialog takes its arguments, puts them into a
## message and displays them until the user hits the dismiss 
## button.

body DIALOG::general {c args} {
    
    set general_result ""
    set w ".tkgeneral"
    
    create $w
    
    set idx [llength $args]; incr idx -1
    set buttons [lindex $args $idx]; incr idx -1
    
    name $w.title [lrange $args 0 $idx]
    
    if {$buttons == ""} { set buttons dismiss } 
    foreach button $buttons {
	lappend foobar [list $button "$this result general $button"]
    }
    buttons $w $foobar
    position $w
    
    grab set $w
    tkwait window $w
    return $general_result
}

## An acknowledge dialog takes its arguments, puts them into a
## message and displays them until the user hits the dismiss button.

body DIALOG::acknowledge {c args} {
    set w ".tkacknowledge"
    
    create $w
    name $w.title $args
    buttons $w "dismiss"
    
    position $w
    grab set $w
    tkwait window $w
}

## A confirm dialog takes its arguments, puts them into a
## message and displays them until the user hits the yes, no
## or cancel button.

body DIALOG::confirm {c args} {
    
    set confirm_result ""
    set w ".tkconfirm"
    
    create $w
    set idx [llength $args]; incr idx -1
    set buttons [lindex $args $idx]; incr idx -1
    name $w.title [lrange $args 0 $idx]
    
    if {$buttons == ""} { set buttons dismiss }    
    foreach button $buttons {
	lappend foobar [list $button "$this result confirm $button"]
    }
    buttons $w $foobar
    
    position $w
    grab set $w
    tkwait window $w
}

## A request dialog displays a message and asks the user to enter
## something in a request box. The last argument is a default for
## the entry widget.

body DIALOG::request {c args} {
    set w ".tkrequest"
    set request_result ""
    
    create $w
    set idx [llength $args]; incr idx -1
    set buttons [lindex $args $idx]; incr idx -1
    set lastarg [lindex $args $idx]; incr idx -1
    name $w.title [lrange $args 0 $idx] question
    
    set idx 1
    set tlist ""
    frame $w.ia
    frame $w.ia.msg
    frame $w.ia.entry
    foreach elem $lastarg {
	message $w.ia.msg.$idx -aspect 25000 -text [lindex $elem 0]
	pack $w.ia.msg.$idx -side top -pady 1
	switch [lindex $elem 2] {
	    scale {
		frame $w.ia.entry.$idx
		label $w.ia.entry.$idx.l -width 4 -font fixed
		scale $w.ia.entry.$idx.s -orient h -label "" -showvalue false \
		    -command "$w.ia.entry.$idx.l configure -text"
		set from [expr round([lindex $elem 3])]
		set to   [expr round([lindex $elem 4])]
		if {[lindex $elem 3] != ""} {
		    catch {$w.ia.entry.$idx.s configure -from $from}
		}
		if {[lindex $elem 4] != ""} {
		    catch {$w.ia.entry.$idx.s configure -to $to}
		}
		set val [expr round([lindex $elem 1])]
		catch {$w.ia.entry.$idx.s set $val} err
		pack $w.ia.entry.$idx.l -side left
		pack $w.ia.entry.$idx.s -side right -fill x -expand true
		pack $w.ia.entry.$idx -side top -fill both -expand true 
	    }
	    logscale {
		frame $w.ia.entry.$idx
		label $w.ia.entry.$idx.l -width 4 -font fixed
		scale $w.ia.entry.$idx.s -orient h -label "" -showvalue false \
		    -command "$w.ia.entry.$idx.l configure -text"
		set from [expr round(exp([lindex $elem 3]))]
		set to [expr round(exp([lindex $elem 4]))]
		if {[lindex $elem 3] != ""} {
		    catch {$w.ia.entry.$idx.s configure -from $from}
		}
		if {[lindex $elem 4] != ""} {
		    catch {$w.ia.entry.$idx.s configure -to $to}
		}
		set val [expr round(exp([lindex $elem 1]))]
		catch {$w.ia.entry.$idx.s set $val}
		pack $w.ia.entry.$idx.l -side left
		pack $w.ia.entry.$idx.s -side right -fill x -expand true
		pack $w.ia.entry.$idx -side top -fill both \
		    -expand true -padx 1 -pady 1
	    }
	    radio {
		frame $w.ia.entry.$idx
		set i 0
		foreach word [lrange $elem 3 end] {
		    radiobutton $w.ia.entry.$idx.$i -text $word -relief flat \
			-variable request_value_$idx -value $word
		    pack $w.ia.entry.$idx.$i -side left -fill x -expand true
		    if {$word == [lindex $elem 1]} {
			catch { $w.ia.entry.$idx.$i invoke }
		    }
		    incr i
		}
		pack $w.ia.entry.$idx -side top -fill both \
		    -expand true -padx 1 -pady 1
	    }
	    check {
		frame $w.ia.entry.$idx
		global request_value
		set i 0
		foreach word [lrange $elem 3 end] {
		    set request_value($idx,$i) ""
		    checkbutton $w.ia.entry.$idx.$i -text $word -relief flat \
			-variable request_value($idx,$i) \
			-offvalue "" -onvalue $word
		    pack $w.ia.entry.$idx.$i -side left -fill x -expand true
		    if {[lsearch [lindex $elem 1] $word] >= 0} {
			catch { $w.ia.entry.$idx.$i invoke }
		    }
		    incr i
		}
		pack $w.ia.entry.$idx -side top -fill both \
		    -expand true -padx 1 -pady 1
	    }
	    option {
		eval tk_optionMenu $w.ia.entry.$idx \
		    request_value_$idx [lrange $elem 3 end]
		$w.ia.entry.$idx configure \
		    -highlightthickness 0 -pady 0
		[winfo child $w.ia.entry.$idx] configure -tearoff false 
		pack $w.ia.entry.$idx -side top -fill both \
		    -expand true -padx 1 -pady 2
	    }
	    entry {
		entry $w.ia.entry.$idx -width [lindex $elem 3] -relief sunken
		$w.ia.entry.$idx insert 0 [lindex $elem 1]
		pack $w.ia.entry.$idx -side top -fill both \
		    -expand true -padx 1 -pady 2
	    }
	    default {
		entry $w.ia.entry.$idx -width 40 -relief sunken
		$w.ia.entry.$idx insert 0 [lindex $elem 1]
		pack $w.ia.entry.$idx -side top -fill both \
		    -expand true -padx 1 -pady 2
	    }
	}
	
	lappend tlist [lindex $elem 2]
	incr idx
    }
    pack $w.ia.msg -side left
    pack $w.ia.entry -side right -padx 10
    pack $w.ia -side top
    
    if {$buttons == ""} { set buttons dismiss }
    foreach button $buttons {
	lappend foobar [list $button "$this request_callback $w \{$tlist\} $button"]
    }
    
    buttons $w $foobar
    position $w
    grab set $w
    tkwait window $w

    return $request_result
}

body DIALOG::request_callback { w tlist button } {
    set idx 1
    set request_result [list $button]
    foreach t $tlist {
	switch $t {
	    scale {
		set val [$w.ia.entry.$idx.s get]
		lappend $this::request_result $val
	    }
	    logscale {
		set val [$w.ia.entry.$idx.s get]
		lappend $this::request_result [expr log($val)]
	    }
	    radio {
		global request_value_$idx
		lappend $this::request_result [set request_value_$idx]
	    }
	    check {
		global request_value
		set aa ""
		    foreach name [array names request_value] {
			if {[string match "$idx,*" $name]} {
			    if {$request_value($name) != ""} {
				lappend aa $request_value($name)
			    }
			}
		    }
		lappend $this::request_result $aa
	    }
	    option {
		global request_value_$idx
		lappend $this::request_result [set request_value_$idx]
	    }
	    default {
		lappend $this::request_result [$w.ia.entry.$idx get]
	    }
	}
	incr idx
    }
}


#########################
#
# start things off here
#
#########################
set i 0
set file ""
set screen ""

if {($argc == 2) || ($argc == 4)} {
puts "argument $i"
    foreach el $argv {
	incr i
	if {"[string tolower $el]" == "-config"} {
	    set file [lindex $argv $i]
	}
	if {"[string tolower $el]" == "-screen"} {
	    set screen [lindex $argv $i]
	}
    }
}
puts "file = $file, screen = $screen"

MAINWIN mwin con $file $screen











