#!/bin/ksh
# the next line restarts using the OS dependent executable \
if [ "$OSTYPE" = "cygwin" ]; then\
  PLATFORM="Windows_NT";\
  SCRIPT=$(cygpath -w $0);\
else \
  PLATFORM=$(uname -s);\
  SCRIPT=$0;\
fi;\
exec ${0%vsd*}vsdwish$PLATFORM "$SCRIPT" -- "$@"

# fix 50914 - make sure auto_path does not contain build directory cruft.
# must come before "package require struct::set"
#
set isSlow 0
if [catch {sl_isSlow} isSlow] {
  puts "sl_isSlow failed"
}

set mtype 11

# remove any path that contains installMTYPE. The files we ship are under vsdtree, not installMTYPE
set pattern install${mtype}

# puts "pattern is $pattern"
if {$isSlow} {
  puts "\[Debug\]: Starting auto_path is $auto_path"
}       
set idx [lsearch -glob $auto_path *${pattern}* ]
while {$idx != -1} {
  if {$isSlow} {
    set x [lindex $auto_path $idx]
    puts "\[Debug\]: Removing $x from auto_index"
  }
  set auto_path [lreplace $auto_path $idx $idx]
  set idx [lsearch -glob $auto_path *${pattern}* ]
}
if {$isSlow} {	  
  puts "\[Debug\]: Final auto_path is $auto_path"
}

package require struct::set

if {[string equal $tcl_platform(platform) "windows"]} {
    set isWindows 1
} else {
    set isWindows 0
}

global fileToSource

if {![info exists vsd(restarting)]} {
    namespace import rbc::vector
    namespace import rbc::spline
    namespace import rbc::graph
    namespace import rbc::stripchart
    namespace import rbc::barchart
    namespace import rbc::busy
}

set vsd(infoScript) [info script]

option add *Text.font normalTextFont
bind Entry <Control-a> +break


#set tcl_precision 10

# Prints a timestamp and a message for debugging.
# proc printMicroSecTimeStamp {msg} {
#   # get current time with microseconds precision:
#   set val [clock microseconds]
#   # extract time with seconds precision:
#   set seconds_precision [expr { $val / 1000000 }]
#   # find number of microseconds:
#   set microseconds [expr { $val - $seconds_precision * 1000000 }]

#   # output the epoc with microseconds precision:
#   puts "\[[format "%s.%06d" [clock format $seconds_precision -format "%H:%M:%S"] $microseconds]\]: $msg"
#   flush stdout
# }

# Called when fileUpdate reads new data to remember the time
# the was updated with new data. Time is in seconds
proc setLastUpdateTimeForDataFile {f} {
  global vsd
  if {![dataFileIdExists $f]} {
    showError "setLastUpdateTimeForDataFile: dataFileId $f does not exist"
  }
  set vsd($f:lastUpdateTime) [clock seconds]
}

# Changes colour of all samples in the Samples column from green to black
# if no updates for the current file have been seen in more than
# $vsd(fileInactiveTime) seconds (default: 90)
proc showAllInstancesInactiveIfNeededForFile {df} {
  global vsd
  if {![hasInactiveDataFile $df]} {
    set lastUpdateTime $vsd($df:lastUpdateTime)
    set now [clock seconds]
    if {($now - $lastUpdateTime) > $vsd(fileInactiveTime)} {
      set msg "No updates to file [file tail [getUniqueFileName $df]] in more than $vsd(fileInactiveTime) seconds"
      bell
      setStatus $msg
      showAllInstancesInactiveForFile $df
    }
  }
}

# Called from the C debugger to print out the tcl stack
proc stacktrace {} {
  set stack "Stack trace:\n"
  for {set i 1} {$i < [info level]} {incr i} {
    set lvl [info level -$i]
    set pname [lindex $lvl 0]
    append stack [string repeat " " $i]$pname
    foreach value [lrange $lvl 1 end] arg [info args $pname] {
      if {$value eq ""} {
	info default $pname $arg value
      }
      append stack " $arg='$value'"
    }
    append stack \n
  }
  puts $stack
  flush stdout
}

proc patchTix {} {
  global tixSep
  # hack fixes for bug in Tix4.0.5
  if {[info commands "tixHList::GoState-33"] != ""} {
    set tixSep "::"
  } else {
    set tixSep ":"
  }

  proc tixHList${tixSep}GoState-33 {w x y} {
    global tixSep
    set ent [tixHList${tixSep}GetNearest $w $y]
    if {$ent != {}} {
      $w anchor set $ent
      if {[$w selection includes $ent]} {
	$w selection clear $ent
      } else {
	$w selection set $ent
      }
      tixHList${tixSep}Browse $w $ent
    }
  }
}

proc myBusyOn {w} {
  busy hold $w
  update
}
proc myBusyOff {w} {
  if [winfo exists $w] {
    busy release $w
    busy forget $w
  }
}
####################

proc showAxis {graph axis} {
  global vsd
  $graph $axis configure -hide false -background $vsd(currentBgColor)
}
proc showMarker {graph id} {
  $graph marker configure $id -hide false
}
proc hideAxis {graph axis} {
  $graph $axis configure -hide true
}
proc hideMarker {graph id} {
  $graph marker configure $id -hide true
}
proc mapLegend {graph bool} {
  global vsd
  $graph legend configure -hide [expr {!$bool}] -background $vsd(currentBgColor)
}
proc isLegendMapped {graph} {
  expr {! [$graph legend cget -hide]}
}


####################

# The following Copyright applies to the bgerror implementation

# Copyright (c) 1993 Xerox Corporation.
# Use and copying of this software and preparation of derivative works based
# upon this software are permitted. Any distribution of this software or
# derivative works must comply with all applicable United States export
# control laws. This software is made available AS IS, and Xerox Corporation
# makes no warranty about the software, its performance or its conformity to
# any specification.

proc bgerror { msg } {
  global vsd isWindows

  set savedErrorInfo "Error: $msg\n"
  set callStack [buildCallStack]
  puts "callStack is\n$callStack"
  if {[catch {buildCallStack} callStack]} {
    set savedErrorInfo [string cat "\nError getting Tcl call stack:\n   $callStack\n"]
  } else {
    set savedErrorInfo [string cat $savedErrorInfo $callStack]
  }
  
  if {$isWindows} {
    set font "ansifixed"
  } else {
    set font "fixed"
  }

  set base ".errorInfo"
  set title "Error Info"
  # Create a toplevel to contain the error trace back
  if [catch {
    # Choose a unique name
    for {set x 1} {$x < 10} {set x [expr {$x + 1}]} {
      if {! [winfo exists $base-$x]} {
	break
      }
    }
    set title $title-$x
    set name $base-$x

    toplevel $name -bd 2
    wm title $name $title
    wm group $name .
    wm minsize $name 20 5
    
    frame $name.buttons
    pack $name.buttons -side top -fill x
    
    if {! [info exists vsd(maintainer)]} {
      set vsd(maintainer) "vsd@gemtalksystems.com"
    }

    button $name.buttons.quit -text "Ignore" -command [list destroy $name]
    pack append $name.buttons $name.buttons.quit {left}

    if {$isWindows} {
      button $name.buttons.copy -text "Copy to Clipboard" \
	-command [list CopyError $name]
      pack append $name.buttons $name.buttons.copy {right}
    } else {
      button $name.buttons.mailto -text "Mail to $vsd(maintainer)" \
	-command [list MailError $name]
      pack append $name.buttons $name.buttons.mailto {right}
    }

    global widgetText TextType


    set msg  "Please type a few words of explanation if you "
    set msg [string cat $msg "are going to report this error. If you are logged "]
    set msg [string cat $msg "in as another user include your actual email address."]
    message $name.ex -font $font -aspect 1000 -text $msg
    pack $name.ex -side top -fill x
    text $name.user -font $font -width 60 -bd 2 -relief raised
    $name.user configure -height 5
    $name.user insert end "What happened: "
    $name.user tag add sel 1.0 1.14
    focus $name.user
    pack $name.user -side top -fill both -expand true
    $name.user mark set hlimit 1.0
    set widgetText($name.user,extend) 0
    set widgetText($name.user,geo) {}
    set TextType($name.user) text

    frame $name.msg
    pack $name.msg -side top -fill both -expand true

    text $name.msg.t -font $font -width 60 -bd 2 -relief raised \
      -setgrid true -yscrollcommand [list $name.msg.sy set]
    scrollbar $name.msg.sy -orient vertical -command [list $name.msg.t yview]
    $name.msg.t configure -height 20
    $name.msg.t insert end [getSysInfo]
    $name.msg.t insert end $savedErrorInfo
    pack $name.msg.sy -side right -fill y
    pack $name.msg.t -side left -fill both -expand true
    set widgetText($name.msg.t,extend) 0
    set widgetText($name.msg.t,geo) {}
    set TextType($name.msg.t) text
    setMasterBgColorForWindow $name
    
    tkwait visibility $name

  } anErr] {
    puts stderr "$anErr"
    puts stderr "bgerror: $msg"
    puts stderr "*** TCL Trace ***"
    puts stderr $savedErrorInfo
  }
}

proc getSysInfo {} {
  global vsd argv argv0 env isWindows tcl_platform
  set res ""
  if {[info commands clock] != ""} {
    append res "Date: [clock format [clock seconds]]\n"
  } elseif {![catch {eval exec $vsd(exec:date)} date]} {
    append res "Date: $date\n"
  }
  if [info exists env(USER)] {
    append res "$env(USER) got an error\n"
  }
  append res "command line: $argv0 $argv\n"
  append res "vsd version: $vsd(version)\n"
  append res "git SHA: $vsd(git_sha)\n"
  append res "statistics version: $vsd(stats_version)\n"
  append res "TK version: [info patchlevel]\n"
  append res "TCL version: [info patchlevel]\n"
  if [info exists tcl_platform] {
    append res "host=[info hostname], type=$tcl_platform(machine), "
    append res "os=$tcl_platform(os) $tcl_platform(osVersion)\n"
  } elseif {! $isWindows} {
    catch {eval exec $vsd(exec:uname)} uname
    append res "$uname\n\n"
  }
  catch {pwd} pwdResult
  append res "Working directory is: $pwdResult"
  if {$vsd(statsrc) != {}} {
    append res "Info on current data file:\n"
    catch {$vsd(statsrc) -info} fileinfo
    foreach i $fileinfo {
      append res "  $i\n"
    }
  }
  append res "\n"
  return $res
}

proc CopyError { w } {
  global vsd argv argv0
  set errText "To: $vsd(maintainer)\n"
  append errText "Subject: $argv0 error\n\n"
  append errText [$w.user get 1.0 end]
  append errText "\n\n"
  append errText [$w.msg.t get 1.0 end]
  append errText "\n"
  clipboard clear
  clipboard append $errText
  destroy $w
  return
}

proc MailError { w } {
  global vsd argv argv0
  if [catch {open /tmp/error.[pid] w} out] {
    puts stderr "Cannot open /tmp/error.[pid]"
    return
  }
  if [catch {
    puts $out "To: $vsd(maintainer)"
    puts $out "Subject: $argv0 error"
    puts $out ""
    puts $out [$w.user get 1.0 end]
    puts $out ""
    puts $out [$w.msg.t get 1.0 end]
    close $out
  } msg] {
    puts stderr "/tmp/error.[pid] $msg"
    return
  }
  if [catch {
    eval exec $vsd(exec:mail) $vsd(maintainer) < /tmp/error.[pid]
  } msg] {
    puts stderr "$vsd(exec:mail) error: $msg"
  } else {
    puts stderr "Mailed report to $vsd(maintainer)"
    destroy $w
  }
  catch {file delete -force /tmp/error.[pid]}
}

#################### some BLT graphing features ######################

proc Blt_CrosshairsOn { graph } {
  bind bltCrosshairs <Any-Motion>   {
    %W crosshairs configure -position @%x,%y
  }
  $graph crosshairs configure -color black
  bind $graph <Enter> [format {
    BltAddBindTag %s bltCrosshairs  
    %s crosshairs on
  } $graph $graph]
  bind $graph <Leave> [format {
    BltRemoveBindTag %s bltCrosshairs  
    %s crosshairs off
  } $graph $graph]
}

proc Blt_CrosshairsOff { graph } {
  bind bltCrosshairs <Any-Motion>   {}
  bind $graph <Enter> {}
  bind $graph <Leave> {}
}

proc Blt_ZoomStack { graph } {
  global bltZoom
  global bltActiveEntry
  global bltSelectedLines

  set bltActiveEntry($graph) -1
  set bltSelectedLines($graph) {}

  set bltZoom($graph,A,x) {}
  set bltZoom($graph,A,y) {}
  set bltZoom($graph,A,y2) {}
  set bltZoom($graph,B,x) {}
  set bltZoom($graph,B,y) {}
  set bltZoom($graph,B,y2) {}
  set bltZoom($graph,stack) {}
  set bltZoom($graph,corner) A

  bind $graph <1> {
    ChartHandleLeftClick %W %x %y 0
  }
  bind $graph <Control-1> {
    ChartHandleLeftClick %W %x %y 1
  }
  bind $graph <ButtonRelease-2> {
    ChartHandleMiddleClick %W %x %y 
  }
  bind $graph <ButtonPress-3> {
    ChartHandleRightClick %W
  }
}

proc removeSelectedLine {graph e} {
  global bltSelectedLines
  set l $bltSelectedLines($graph)
  set idx [lsearch -exact $l $e]
  if {$idx != -1} {
    $graph legend deactivate $e
    set bltSelectedLines($graph) [lreplace $l $idx $idx]
    #puts "DEBUG: removing $e from $l"
  }
}

proc addSelectedLine {graph e} {
  global bltSelectedLines
  set l $bltSelectedLines($graph)
  set idx [lsearch -exact $l $e]
  if {$idx != -1} {
    #puts "DEBUG: removing $e from $l"
    # it was already select so deselect it
    $graph legend deactivate $e
    set bltSelectedLines($graph) [lreplace $l $idx $idx]
  } else {
    #puts "DEBUG: adding $e to $l"
    lappend bltSelectedLines($graph) $e
    #puts "DEBUG: activating \"$bltSelectedLines($graph)\""
    eval $graph legend activate $bltSelectedLines($graph)
    # $graph legend activate $e
  }
}

proc getCommonFilter {graph filter} {
  global vsd bltSelectedLines
  foreach e $bltSelectedLines($graph) {
    set edata [$vsd(ed:$e:$graph) -info]
    if {![string equal $filter [lindex $edata 5]]} {
      return {}
    }
  }
  return $filter
}

proc getSelectedLines {graph} {
  global bltSelectedLines
  return $bltSelectedLines($graph)
}

proc clearSelectedLines {graph} {
  global bltSelectedLines
  set l $bltSelectedLines($graph)
  if {$l != {}} {
    eval $graph legend deactivate $l
    set bltSelectedLines($graph) {}
    #puts "DEBUG: clear all selected lines"
  }
  return $l
}

proc resetSelectedLines {graph lines} {
  global bltSelectedLines
  set bltSelectedLines($graph) $lines
  if {$lines != {}} {
    eval $graph legend activate $lines
    #puts "DEBUG: reset selected lines to $lines"
  }
}

proc deleteElement {graph element {doFree 1}} {
  global vsd
  global bltActiveGraph
  global bltActiveEntry

  set cleanup 0

  removeSelectedLine $graph $element

  if {$vsd($graph:chartOp) != {}} {
    chartCancelOp$vsd($graph:chartOp) $graph
  }
  if {$bltActiveEntry($graph) == $element} {
    set bltActiveEntry($graph) -1
    set cleanup 1
  }
  if {$bltActiveGraph($graph) == $element} {
    set bltActiveGraph($graph) 0
    set bltActiveGraph($graph,closest) {}
    set cleanup 1
  }
  if {$cleanup && $bltActiveEntry($graph) == -1} {
    deactivateAxis $graph
    showLineInfo $graph ""
  }
  if {$doFree} {
    $vsd(ed:$element:$graph) -free
  }
  unset vsd(ed:$element:$graph)
  set axisName [$graph element cget $element -mapy]
  $graph element delete $element
  foreach e [$graph element names] {
    if {[string equal $axisName [$graph element cget $e -mapy]]} {
      return;
    }
  }
  # No more lines using this axis so unmap it
  hideAxis $graph ${axisName}axis
  if {[string equal $axisName "y"]} {
    $graph configure -leftmargin 2
  } else {
    $graph configure -rightmargin 2
  }
}

proc getLineUnitDescription {isAxis lineinfo} {
  set fileId [lindex $lineinfo 7]
  set instName [lindex $lineinfo 0]
  set linectr [lindex $lineinfo 1]
  set linescale [lindex $lineinfo 4]
  set linefilter [lindex $lineinfo 5]
  set lineoperator [lindex $lineinfo 9]
  set lineoffset [lindex $lineinfo 12]
  set linedivider [lindex $lineinfo 13]
  if [catch {getStatType $linectr} ctrtype] {
    # This happens if its a derived line with more than one ctr
    # NYI: just go with defaults
    set ctrtype ""
    # NYI in many derived line cases the units could be figured out
    #     this should be done in statlib when the line is created
    set ctrunits "None"
    set linectr "([getCtrAlias $linectr $instName $fileId])"
  } else {
    set saveLineCtr $linectr
    set linectr [getCtrAlias $linectr $instName $fileId]
    set ctrunits [string toupper [getStatUnits $linectr] 0 0]
  }
  set axisLabel ""
  if {[string equal $linefilter "none"]} {
    if $isAxis {
      if {[string equal $ctrtype "counter"]} {
	append axisLabel "total "
      }
    }
    if {[string equal $ctrunits "None"]} {
      append axisLabel $linectr
    } else {
      append axisLabel $ctrunits
    }
  } elseif {[string equal $linefilter "aggregate"]} {
    if {[string equal $ctrunits "None"]} {
      append axisLabel $linectr
    } else {
      append axisLabel $ctrunits
    }
    append axisLabel " +"
  } elseif {[string equal $linefilter "smooth"]} {
    if {[string equal $ctrunits "None"]} {
      append axisLabel $linectr
    } else {
      append axisLabel $ctrunits
    }
    append axisLabel " ~"
  } else { # per filter 
    if {[string equal $ctrunits "None"]} {
      append axisLabel $linectr
    } else {
      append axisLabel $ctrunits
    }
    if {[string equal $linefilter "persecond"]} {
      append axisLabel " / second"
    } elseif {[string equal $linefilter "persample"]} {
      append axisLabel " / sample"
    }
  }
  if $isAxis {
    if {$linedivider != 1.0 && $linedivider != 1} {
      if {[expr {abs($linedivider)}] >= 1.0} {
	append axisLabel " / $linedivider"
      } else {
	append axisLabel " * [expr {1.0 / $linedivider}]"
      }
    }
    if {$lineoffset != 0.0 && $lineoffset != 0} {
      if {$lineoffset < 0} {
	append axisLabel " + [expr {- $lineoffset}]"
      } else {
	append axisLabel " - $lineoffset"
      }
    }
    if {$linescale != 1.0 && $linescale != 1} {
      append axisLabel " * $linescale"
    }
  }
  return $axisLabel
}

proc getElementYAxisTitle {g e} {
  global vsd
  return [getLineUnitDescription 1 [$vsd(ed:$e:$g) -info]]
}

proc stripext {n} {
  set result [file rootname $n]
  while {![string equal $result $n]} {
    set n $result
    set result [file rootname $n]
  }
  return $result
}

# Redesign for 45852.
# statlib.c now calls HostExpandFileName, so we should never
# have to deal with a duplicate file name here.
#
# Summary of file maps in VSD:
#
#uniqueNameDict
#    slFile2 -> /export/bunk2/users/normg/stats/appended/statmon24483-3.out.gz
#
#fileListMap
#  "1. /export/bunk2/users/normg/stats/appended/statmon24483-1.out.gz bunk Linux 3.3.1" -> slFile1
#
#appendedFileMap
#  "1. /export/bunk2/users/normg/stats/appended/statmon24483-1.out.gz bunk Linux 3.3.1" -> 0
#
#vsd(filelist)
#   "1. /export/bunk2/users/normg/stats/appended/statmon24483-1.out.gz bunk Linux 3.3.1"
#   "2. /export/bunk2/users/normg/stats/appended/statmon24483-3.out.gz bunk Linux 3.3.1"
#
#vsd(w:filelist) subwidget listbox
#   "1. /export/bunk2/users/normg/stats/appended/statmon24483-1.out.gz bunk Linux 3.3.1"
#   "2. /export/bunk2/users/normg/stats/appended/statmon24483-3.out.gz bunk Linux 3.3.1"
#
#$vsd(w:filelist) configure -value
#   "1. /export/bunk2/users/normg/stats/appended/statmon24483-1.out.gz bunk Linux 3.3.1"

proc calcUniqueFileName {dfid fileInfo} {
  global vsd
  dict set vsd(uniqueNameDict) $dfid [lindex $fileInfo 0]
}

# groupFilesByPattern deleted
proc allAppendedFileNames {} {
  global vsd
  set result [dict keys $vsd(appendedFileNamesDict)]
  return $result
}

proc addAppendedFileName {aFileName} {
  global vsd
  dict set vsd(appendedFileNamesDict) $aFileName 1
}

proc fileNameWasAppended {aFileName} {
  global vsd
  return [dict exists $vsd(appendedFileNamesDict) $aFileName]
}

proc dataFileIdExists {dfid} {
  global vsd
  return [dict exists $vsd(uniqueNameDict) $dfid]
}

proc addUniqueFileName {dfid fileInfo} {
  global vsd
  if {[dataFileIdExists $dfid]} {
    # already added
    return
  }
  calcUniqueFileName $dfid $fileInfo
}

proc getUniqueFileName {dfid} {
  global vsd
  return [dict get $vsd(uniqueNameDict) $dfid]
}

proc getUniqueFileDirName {dfid} {
  global vsd
  return [file dirname [dict get $vsd(uniqueNameDict) $dfid]]
}

# does not include files that were loaded and appended
proc allLoadedFileNames {} {
  global vsd
  set result [dict values $vsd(uniqueNameDict)]
  return $result
}

proc allDataFileIds {} {
  global vsd
  set result [dict keys $vsd(uniqueNameDict)]
  return $result
}

proc numDataFiles {} {
  global vsd
  return [dict size $vsd(uniqueNameDict)]
}

proc normalizedStatmonFilesInDir {aDir} {
  set filelist [ glob -nocomplain -directory $aDir -type f  *.gz *.out *.lz4 ]
  set result [lmap each $filelist {expr {[file normalize $each]}}]
  return $result
}

proc saveContentsOfDir {aDir} {
  global vsd
  if {![dict exists $vsd(dirContentsDict) $aDir]} {
    set contents [normalizedStatmonFilesInDir $aDir]
    dict set vsd(dirContentsDict) $aDir $contents
  }
}

proc newFilesInDir {aDir} {
  global vsd
  set filesInDir [normalizedStatmonFilesInDir $aDir]
  set oldFilesInDir [dict get $vsd(dirContentsDict) $aDir]
  lappend oldFilesInDir {*}[allLoadedFileNames]
  lappend oldFilesInDir {*}[allAppendedFileNames]
  set oldFilesInDir [lsort -unique $oldFilesInDir]
  set newFiles [lsort -dictionary [::struct::set difference $filesInDir $oldFilesInDir]]
  return $newFiles
}

proc addInactiveDataFile {df} {
  global vsd
  dict set vsd(inactiveDataFiles) $df $df
}

proc hasInactiveDataFile {df} {
  global vsd
  return [dict exists $vsd(inactiveDataFiles) $df]
}

proc removeInactiveDataFile {df} {
  global vsd
  if {[hasInactiveDataFile $df]} {
    dict unset vsd(inactiveDataFiles) $df
  }
}

proc numInactiveDataFiles {} {
  global vsd
  return [dict size $vsd(inactiveDataFiles)]
}
  
proc getShortOS {os} {
  if {[string first "Solaris" $os] != -1} {
    return "Solaris"
  } elseif {[string first "SunOS" $os] != -1} {
    return "Solaris"
  } elseif {[string first "NT" $os] != -1} {
    return "Windows"
  } elseif {[string first "Windows" $os] != -1 ||
	    [string first "win32" $os] != -1} {
    return "Windows"
  } elseif {[string first "AIX" $os] != -1} {
    return "AIX"
  } elseif {[string first "HPUX" $os] != -1} {
    return "HPUX"
  } elseif {[string first "Linux" $os] != -1 ||
	    [string first "linux" $os] != -1} {
    return "Linux"
  } else {
    return $os
  }
}

proc getShortGsVer {ver} {
  # GBS format is "GemBuilder for Smalltalk 7.6a2154 of March 13, 2013"
  if {[string first "GemBuilder" $ver] != -1} {
    set slist [split $ver " "]
    return [lindex $slist 3]
  }
  # Statmon format is "3.1.0.5, Mon Oct 28 16:37:15 2013"
  set idx [string first "," $ver]
  if {$idx != -1} {
    incr idx -1
    return [string range $ver 0 $idx]
  }
  return $ver
}

proc getShortMachine {m} {
  set idx [string first " " $m]
  if {$idx == -1} {
    return $m
  }
  incr idx -1
  set res [string range $m 0 $idx]
  return [string trimright $res ":"]
}

proc getFileId {dfid} {
  # dfid is "slFile" followed by a bunch of digits
  return [string range $dfid 6 end]
}

# Change formatting for 45852
# Add fileInfoNameMap for as part of fix for 45854
proc getFileInfoName {dfid} {
  global vsd fileInfoNameMap

  if {[info exists fileInfoNameMap($dfid)]} {
    return $fileInfoNameMap($dfid)
  }
  set id [getFileId $dfid]
  set finfo [$dfid -info]
  set fname [getUniqueFileName $dfid]
  set platform [getShortOS [lindex $finfo 2]]

  set gsVer [getShortGsVer [lindex $finfo 3]]
  set machine [getShortMachine [lindex $finfo 4]]
  set result "$id. $fname $machine $platform $gsVer"
  set fileInfoNameMap($dfid) $result
  return $result
}

proc getTitleInfoName {dfid} {
  global vsd
  set id [getFileId $dfid]
  set finfo [$dfid -info]
  set fname [getUniqueFileName $dfid]
  if {[string match "*statArchive" $fname]} {
    set idx [string last "statArchive" $fname]
    incr idx -1
    set fname [string range $fname 0 $idx]
  }
  set gsVer [getShortGsVer [lindex $finfo 3]]
  set idx [string first " as of" $gsVer]
  if {$idx != -1} {
    incr idx -1
    set gsVer [string range $gsVer 0 $idx]
  }
  set archiveFormat [lindex $finfo 1]
  if {$archiveFormat != 1} {
    set machine [lindex [lindex $finfo 4] 1]
  } else {
    set machine [getShortMachine [lindex $finfo 4]]
  }
  set result "$id. $fname $machine $gsVer"
  return $result
}

proc getElementXAxisTitle {g e} {
  global vsd
  set linfo [$vsd(ed:$e:$g) -info]
  set dfids [lindex $linfo 7]
  if {[llength $dfids] == 1} {
    set dfid [lindex $dfids 0]
    return [getTitleInfoName $dfid]
  } else {
    foreach dfid $dfids {
      append result [getUniqueFileName $dfid]
      append result " "
    }
    return [string trimright $result]
  }
}

proc activateAxis {g e} {
  global vsd

  set axisName [$g element cget $e -mapy]
  append axisName "axis"
  $g $axisName configure -color $vsd(activecolor)
  if {$vsd($g:$axisName:showtitle)} {
    set axisLabel [getElementYAxisTitle $g $e]
    if {$axisLabel != ""} {
      $g $axisName configure -title $axisLabel
    }
  }
  if {$vsd($g:xaxis:showtitle)} {
    set xaxisLabel [getElementXAxisTitle $g $e]
    if {$xaxisLabel != ""} {
      $g xaxis configure -title $xaxisLabel
    }
  }
}

proc deactivateAxis {g} {
  global vsd

  $g yaxis configure -color "black"
  if {$vsd($g:yaxis:showtitle)} {
    $g yaxis configure -title $vsd(yaxis:defaultTitle)
  }
  $g y2axis configure -color "black"
  if {$vsd($g:y2axis:showtitle)} {
    $g y2axis configure -title $vsd(y2axis:defaultTitle)
  }
  if {$vsd($g:xaxis:showtitle)} {
    $g xaxis configure -title $vsd(xaxis:defaultTitle)
  }
}

proc getLineLabel {g e} {
  global vsd
  if [isLegendMapped $g] {
    set result ""
  } else {
    set lineinfo [$vsd(ed:$e:$g) -info]
    set fullname [lindex $lineinfo 0]
    # fullname = {name pid uniqueid}
    if {[lindex $fullname 1] == 0} {
      # fix for bug 22816
      set result [lindex $fullname 0]
    } else {
      set result [join $fullname ", "]
    }
    append result ": "
  }
  set w [winfo toplevel $g]
  if {$vsd($w:showlinestats)} {
    set xStart [$g xaxis cget -min]
    set xEnd [$g xaxis cget -max]
    set lstats [$vsd(ed:$e:$g) -stats $xStart $xEnd]
    append result "points: [lindex $lstats 0]"
    append result " min: [formatFloat [lindex $lstats 1]]"
    append result " max: [formatFloat [lindex $lstats 2]]"
    append result " mean: [formatFloat [lindex $lstats 3]]"
    append result " sd: [formatFloat [lindex $lstats 4]]"
    # 46240 - include Y value of last sample
    set jsLast [lindex $lstats 6]
    set jslstats [$vsd(ed:$e:$g) -stats $jsLast $jsLast]
    append result " last: [formatFloat [lindex $jslstats 2]]"

    $g marker configure triStart -coords [list [lindex $lstats 5] -Inf]
    showMarker $g triStart
    $g marker configure triEnd -coords [list [lindex $lstats 6] -Inf]
    showMarker $g triEnd
  }
  return $result
}

proc bltDoActivateGraph {graph x y new} {
  global bltActiveGraph
  global bltActiveEntry
  unset bltActiveGraph($graph,alarmid)
  unset bltActiveGraph($graph,x)
  unset bltActiveGraph($graph,y)
  set old $bltActiveGraph($graph)
  if { $old != $new } {
    if { $old != 0 } {
      $graph legend deactivate $old
      $graph element deactivate $old
      $graph marker configure activeLine -text ""
      deactivateAxis $graph
      showLineInfo $graph ""
      set bltActiveGraph($graph,closest) {}
    } else {
      if {[info exists bltActiveGraph($graph,oldSelectedLines)]} {
	set bltActiveGraph($graph,oldSelectedLines) [clearSelectedLines $graph]
      }
      set legendEntry $bltActiveEntry($graph)
      if {$legendEntry != -1} {
	if {$new != $legendEntry} {
	  $graph legend deactivate $legendEntry
	  $graph element deactivate $legendEntry
	  $graph marker configure activeLine -text ""
	  deactivateAxis $graph
	  showLineInfo $graph ""
	}
      }
    }
    #puts "DEBUG: bltDoActivateGraph legend activate $new"
    $graph legend activate $new
    $graph element activate $new
    $graph marker configure activeLine -text [getLineLabel $graph $new]
    activateAxis $graph $new
    set bltActiveGraph($graph) $new
    showLineInfo $graph $new
    set bltActiveGraph($graph,closest) {}
  }
  bltActivatePoint $graph $x $y $new
  return
}

proc bltActivatePoint {graph x y new} {
  global bltActiveGraph
  global vsd
  if [$graph element closest $x $y info -halo 5 -interpolate 0 $new] {
    set coordinates "$info(x) $info(y)"
    if {[string equal $vsd($graph:chartOp) "Delta"]} {
      if {[string equal $new $vsd($graph:deltaLine)]} {
	$graph config -cursor target
      } else {
	$graph config -cursor crosshair
      }
    } elseif {[string equal $vsd($graph:chartOp) "Compare"]} {
      $graph config -cursor target
    } elseif {[string equal $vsd($graph:chartOp) "Trim"]} {
      if {[string equal $new $vsd($graph:trimLine)]} {
	$graph config -cursor target
      } else {
	$graph config -cursor crosshair
      }
    }
    if {![string equal $coordinates $bltActiveGraph($graph,closest)]} {
      setCurXY $graph $new $info(x) $info(y)
      set bltActiveGraph($graph,closest) $coordinates
    }
  }
}

proc bltActivateGraph {g x y new} {
  global bltActiveGraph

  if {[info exists bltActiveGraph($g,alarmid)]} {
    set oldx $bltActiveGraph($g,x)
    set oldy $bltActiveGraph($g,y)
    if {abs($x - $oldx) < 3 && abs($y - $oldy) < 3} {
      # still close to place that alarm already set for
      return
    }
    # forget the old guy and setup a new one
    after cancel $bltActiveGraph($g,alarmid)
    unset bltActiveGraph($g,alarmid)
  }
  set old $bltActiveGraph($g)
  if { $old == $new } {
    # Don't bother delaying with an alarm since this is on
    # the same line
    bltActivatePoint $g $x $y $new
    return
  }

  # Set up an alarm so we can delay doing this in case the user
  # is just moving the mouse over the graph.
  set bltActiveGraph($g,x) $x
  set bltActiveGraph($g,y) $y
  set bltActiveGraph($g,alarmid) [after 500 \
				    [list bltDoActivateGraph $g $x $y $new]]
  return
}

proc bltDeactivateGraph {g} {
  global bltActiveGraph
  global bltActiveEntry
  global vsd

  if {[string equal $vsd($g:chartOp) "Delta"] ||
      [string equal $vsd($g:chartOp) "Trim"] ||
      [string equal $vsd($g:chartOp) "Compare"]} {
    $g config -cursor crosshair
  }
  if {[info exists bltActiveGraph($g,alarmid)]} {
    after cancel $bltActiveGraph($g,alarmid)
    unset bltActiveGraph($g,alarmid)
    unset bltActiveGraph($g,x)
    unset bltActiveGraph($g,y)
  }
  if {![info exists bltActiveGraph($g)]} {
    return
  }
  if {$bltActiveGraph($g) == 0} {
    return
  }
  $g legend deactivate $bltActiveGraph($g)
  $g element deactivate  $bltActiveGraph($g)
  $g marker configure activeLine -text ""
  deactivateAxis $g
  set bltActiveGraph($g) 0
  if {[info exists bltActiveGraph($g,oldSelectedLines)]} {
    resetSelectedLines $g $bltActiveGraph($g,oldSelectedLines)
    unset bltActiveGraph($g,oldSelectedLines)
  }
  showLineInfo $g ""
  set bltActiveGraph($g,closest) {}
  set le $bltActiveEntry($g)
  if {$le != -1} {
    # If an active legend is in use then restore it
    $g legend activate $le
    $g element activate $le
    $g marker configure activeLine -text  [getLineLabel $g $le]
    activateAxis $g $le
    showLineInfo $g $le
  }
}

# For some reason <1> and <2> end up calling the <Leave> binding.
# So we make sure we are out of the graph before doing the <Leave> work.
proc chartHandleLeaveEvent {g x y} {
  set y [$g yaxis invtransform $y]
  set ylimits [$g yaxis limits]
  if {$y >= [lindex $ylimits 0] && $y <= [lindex $ylimits 1]} {
    set x [$g xaxis invtransform $x]
    set xlimits [$g xaxis limits]
    if {$x >= [lindex $xlimits 0] && $x <= [lindex $xlimits 1]} {
      return
    }
  }
  bltDeactivateGraph $g
}

proc Blt_BA_ActiveGraph {W x y} {
  if [$W element closest $x $y info -interpolate true -halo 5] {
    bltActivateGraph $W $x $y $info(name)
  } else {
    bltDeactivateGraph $W
  }
}

proc Blt_ActiveGraph { graph } {
  global bltActiveGraph
  set bltActiveGraph($graph) 0
  set bltActiveGraph($graph,closest) {}
  set bltActiveGraph($graph,oldSelectedLines) {}
  bind bltActiveGraph <Any-Motion>   {Blt_BA_ActiveGraph %W %x %y}
  bind bltActiveGraph <Leave>   {
    chartHandleLeaveEvent %W %x %y
  }
  BltAddBindTag $graph bltActiveGraph
}

proc activateElement {graph new control} {
  global bltActiveEntry
  global bltSelectedLines
  set old $bltActiveEntry($graph)
  if { $old != $new } {
    if {!$control} {
      clearSelectedLines $graph
    }
    addSelectedLine $graph $new
    if { $old != -1 } {
      #$graph legend deactivate $old
      $graph element deactivate $old
      $graph marker configure activeLine -text ""
      deactivateAxis $graph
      showLineInfo $graph ""
    }
    #$graph legend activate $new
    $graph element activate $new
    $graph marker configure activeLine -text [getLineLabel $graph $new]
    activateAxis $graph $new
    set bltActiveEntry($graph) $new
    showLineInfo $graph $new
  } else {
    if {!$control} {
      clearSelectedLines $graph
    } else {
      removeSelectedLine $graph $new
    }
    $graph legend deactivate $new
    $graph element deactivate $new
    $graph marker configure activeLine -text ""
    deactivateAxis $graph
    showLineInfo $graph ""
    set bltActiveEntry($graph) -1
  }
}

proc BltAddBindTag { graph name } {
  set oldtags [bindtags $graph]
  if { [lsearch $oldtags $name] < 0 } {
    bindtags $graph [concat $name $oldtags]
  }
}

proc BltRemoveBindTag { graph name } {
  set tagList {}
  foreach tag [bindtags $graph] {
    if { $tag != $name } {
      lappend tagList $tag
    }
  }
  bindtags $graph $tagList
}

proc BltGetCoords { graph x y index } {

  set y2 [$graph y2axis invtransform $y]
  set coords [$graph invtransform $x $y]
  set x [lindex $coords 0]
  set y [lindex $coords 1]

  scan [$graph xaxis limits] "%s %s" xmin xmax
  scan [$graph yaxis limits] "%s %s" ymin ymax
  scan [$graph y2axis limits] "%s %s" y2min y2max

  if { $x > $xmax } { 
    set x $xmax 
  } elseif { $x < $xmin } { 
    set x $xmin 
  }

  if { $y > $ymax } { 
    set y $ymax 
  } elseif { $y < $ymin } { 
    set y $ymin 
  }

  if { $y2 > $y2max } { 
    set y2 $y2max 
  } elseif { $y2 < $y2min } { 
    set y2 $y2min 
  }

  global bltZoom
  set bltZoom($graph,$index,x) $x
  set bltZoom($graph,$index,y) $y
  set bltZoom($graph,$index,y2) $y2
}

proc updateActiveLine {g} {
  global bltActiveEntry

  if {[info exists bltActiveEntry($g)] && $bltActiveEntry($g) != -1} {
    set e $bltActiveEntry($g)
    $g marker configure activeLine -text [getLineLabel $g $e]
  }
  showMarker $g activeLine
}

proc updateAllActiveLines {} {
  global bltActiveEntry

  foreach g [array names bltActiveEntry] {
    if {$bltActiveEntry($g) != -1} {
      updateActiveLine $g
    }
  }
}

proc updateAllTimeAxis {} {
  global bltActiveEntry

  foreach g [array names bltActiveEntry] {
    if {$bltActiveEntry($g) != -1} {
      chartMenuTime $g
    }
  }
}

proc BltPopZoom { graph } {
  global bltZoom

  set zoomStack $bltZoom($graph,stack)
  if { [llength $zoomStack] > 0 } {
    set cmd [lindex $zoomStack 0]
    set bltZoom($graph,stack) [lrange $zoomStack 1 end]
    eval $cmd
    BltZoomTitleLast $graph
    myBusyOn $graph
    set cmd [format {
      if {[winfo exists %s]} {
	if { $bltZoom(%s,corner) == "A" } {
	  %s marker delete "bltZoom_title"
	  updateActiveLine %s
	}
      }
    } $graph $graph $graph $graph]
    after 2000 $cmd
    myBusyOff $graph
  } else {
    $graph marker delete "bltZoom_title"
    updateActiveLine $graph
  }
}

proc ZoomHorizontalPage {g i} {
  global vsd

  set limits [$g xaxis limits]
  set screenMin [$g xaxis transform [lindex $limits 0]]
  set screenMax [$g xaxis transform [lindex $limits 1]]
  set width [expr {($screenMax - $screenMin) - 1}]
  ZoomHorizontal $g [expr {$width * $i}]
}

proc ZoomHorizontal {g i} {
  global vsd

  set axisMin [$g xaxis cget -min]
  if {$axisMin != ""} {
    set screenMin [$g xaxis transform $axisMin]
    set axisMin [$g xaxis invtransform [expr {$screenMin + $i}]]
  }

  set axisMax [$g xaxis cget -max]
  if {$axisMax != ""} {
    set screenMax [$g xaxis transform $axisMax]
    set axisMax [$g xaxis invtransform [expr {$screenMax + $i}]]
  }

  if {$axisMin != "" || $axisMax != ""} {
    $g xaxis configure -min $axisMin -max $axisMax
    updateActiveLine $g
  }
}

proc ZoomVertical {g i} {
  global vsd
  # NYI
}

# Push the old axis limits on the stack and set the new ones

proc BltPushZoom { graph } {
  deleteAllZoomMarkers $graph

  global bltZoom
  set xA $bltZoom($graph,A,x)
  set yA $bltZoom($graph,A,y)
  set y2A $bltZoom($graph,A,y2)
  set xB $bltZoom($graph,B,x)
  set yB $bltZoom($graph,B,y)
  set y2B $bltZoom($graph,B,y2)

  if { ($xA == $xB) && ($yA == $yB) && ($y2A == $y2B) } { 
    # No delta, revert to start
    updateActiveLine $graph
    return
  }

  set cmd [format {
    %s xaxis configure -min "%s" -max "%s"
    %s yaxis configure -min "%s" -max "%s"
    %s y2axis configure -min "%s" -max "%s"
  } $graph [$graph xaxis cget -min] [$graph xaxis cget -max] \
	     $graph [$graph yaxis cget -min] [$graph yaxis cget -max] \
	     $graph [$graph y2axis cget -min] [$graph y2axis cget -max] ]

  if { $xA > $xB } { 
    $graph xaxis configure -min $xB -max $xA 
  } elseif { $xA < $xB } {
    $graph xaxis configure -min $xA -max $xB
  } 
  if { $yA > $yB } { 
    $graph yaxis configure -min $yB -max $yA
  } elseif { $yA < $yB } {
    $graph yaxis configure -min $yA -max $yB
  } 
  if { $y2A > $y2B } { 
    $graph y2axis configure -min $y2B -max $y2A
  } elseif { $y2A < $y2B } {
    $graph y2axis configure -min $y2A -max $y2B
  } 
  set bltZoom($graph,stack) [linsert $bltZoom($graph,stack) 0 $cmd]

  update
  updateActiveLine $graph
}

proc chartFinishOp {g} {
  global vsd
  $g config -cursor $vsd(graphcursor)
  set vsd($g:chartOp) {}
}

proc ChartHandleRightClick { graph } {
  global vsd

  chartCancelOp$vsd($graph:chartOp) $graph
  chartFinishOp $graph
}

proc deleteAllZoomMarkers {graph} {
  foreach e [$graph marker names "bltZoom_*"] {
    $graph marker delete $e
  }
}
proc chartCancelOpZoom { graph } {
  global bltZoom

  deleteAllZoomMarkers $graph
  showMarker $graph activeLine

  if { $bltZoom($graph,corner) == "A" } {
    # Reset the whole axis
    BltPopZoom $graph
  } else {
    set bltZoom($graph,corner) A
    bind $graph <Motion> { }
  }
}

proc chartCancelOpDelta { g } {
  global vsd
  unset vsd($g:deltaLine)
  if [info exists vsd($g:deltapoint1)] {
    unset vsd($g:deltapoint1)
  }
  bind $g <Any-Motion> { }
  $g marker delete "chartop_title"
  if [$g marker exists "delta_line"] {
    $g marker delete "delta_line"
  }
  showMarker $g activeLine
  chartFinishOp $g
}

proc chartCancelOpCompare { g } {
  global vsd
  if [info exists vsd($g:compareLine1)] {
    unset vsd($g:compareLine1)
  }
  if [info exists vsd($g:comparepoint1)] {
    unset vsd($g:comparepoint1)
  }
  bind $g <Any-Motion> { }
  $g marker delete "chartop_title"
  if [$g marker exists "compare_line"] {
    $g marker delete "compare_line"
  }
  showMarker $g activeLine
  chartFinishOp $g
}

proc chartCancelOpCombine { g } {
  global vsd
  bind $g <Any-Motion> { }
  $g marker delete "chartop_title"
  showMarker $g activeLine
  chartFinishOp $g
}

proc chartCancelOpMinTime { g } {
  $g marker delete "chartop_title"
  showMarker $g activeLine
  chartFinishOp $g
}

proc chartCancelOpMaxTime { g } {
  chartCancelOpMinTime $g
}

proc BltZoomTitleNext {graph} {
  global bltZoom vsd

  set level [expr {[llength $bltZoom($graph,stack)] + 1}]
  set title "Zoom #$level (right click to cancel)"
  $graph config -cursor bottom_right_corner
  hideMarker $graph activeLine
  $graph marker create text -name "bltZoom_title" -text $title \
    -coords {-Inf Inf} -anchor nw -bg {} -font vsdsf
}

proc BltZoomTitleLast {graph} {
  global bltZoom vsd

  set level [llength $bltZoom($graph,stack)]
  if { $level > 0 } {
    hideMarker $graph activeLine
    set title "Zoom #$level"
    $graph marker create text -name "bltZoom_title" -text $title \
      -coords {-Inf Inf} -anchor nw -bg {} -font vsdsf
  }
}

proc getSelectedLine {g} {
  global bltActiveEntry
  global bltActiveGraph

  set e $bltActiveGraph($g)
  if {$e != 0} {
    return $e
  }
  set e $bltActiveEntry($g)    
  if {$e != -1} {
    return $e
  }
  return ""
}

proc chartOp {graph x y control} {
  global vsd
  global bltActiveGraph

  set e $bltActiveGraph($graph)
  if {$e != 0} {
    activateElement $graph $e $control
  }
}

proc chartOpZoom {graph x y control} {
  global vsd
  global bltZoom

  BltGetCoords $graph $x $y $bltZoom($graph,corner)
  if { $bltZoom($graph,corner) == "A" } {
    # First corner selected, start watching motion events

    BltZoomTitleNext $graph 
    bind $graph <Any-Motion> { 
      BltGetCoords %W %x %y B
      BltBox %W
    }
    set bltZoom($graph,corner) B
  } else {
    bind $graph <Any-Motion> { }
    BltPushZoom $graph
    chartFinishOp $graph
    set bltZoom($graph,corner) A
  }
}

proc DeltaLine {g x y} {
  global vsd
  set yaxis [$g element cget $vsd($g:deltaLine) -mapy]
  set x1 [lindex $vsd($g:deltapoint1) 0]
  set y1 [lindex $vsd($g:deltapoint1) 1]
  set x2 [$g xaxis invtransform $x]
  set y2 [$g ${yaxis}axis invtransform $y]
  set coords {$x1 $y1 $x2 $y2}
  if [$g marker exists "delta_line"] {
    $g marker configure "delta_line" -coords $coords
  } else {
    $g marker create line -coords $coords -name "delta_line" -mapy $yaxis
  }
  if [$g element closest $x $y info -halo 5 -interpolate 0 $vsd($g:deltaLine)] {
    $g marker configure "delta_line" -outline $vsd(activecolor)
  } else {
    $g marker configure "delta_line" -outline black
  }
}

proc CompareLine {g x y} {
  global vsd
  set yaxis [$g element cget $vsd($g:compareLine1) -mapy]
  set x1 [lindex $vsd($g:comparepoint1) 0]
  set y1 [lindex $vsd($g:comparepoint1) 1]
  set x2 [$g xaxis invtransform $x]
  set y2 [$g ${yaxis}axis invtransform $y]
  set coords {$x1 $y1 $x2 $y2}
  if [$g marker exists "compare_line"] {
    $g marker configure "compare_line" -coords $coords
  } else {
    $g marker create line -coords $coords -name "compare_line" -mapy $yaxis
  }
  if [$g element closest $x $y info -halo 5 -interpolate 0] {
    $g marker configure "compare_line" -outline $vsd(activecolor)
  } else {
    $g marker configure "compare_line" -outline black
  }
}

proc showData {str {wrap 0}} {
  global vsd widgetHelp
  set w .datawin
  set exists [winfo exists $w]
  if $exists {
    raise $w
    wm deiconify $w
  } else {
    set widgetHelp($w) {Log Window}
    toplevel $w
    wm title $w "Vsd Data Log"
    if {$vsd(geometry:$w) != {}} {
      wm geometry $w $vsd(geometry:$w)
    }
    frame $w.top -relief raised -bd 1
    tixScrolledText $w.top.msg -width 500 -height 220
    [$w.top.msg subwidget text] configure -wrap none -font dataLogFont
    pack $w.top.msg -expand yes -fill both
    pack $w.top -side top -fill both -expand yes
  }

  if {$wrap} {
    [$w.top.msg subwidget text] configure -wrap word
  } else {
    [$w.top.msg subwidget text] configure -wrap none
  }
  [$w.top.msg subwidget text] insert end $str
  [$w.top.msg subwidget text] insert end "\n"
  [$w.top.msg subwidget text] see end
  setMasterBgColorForWindow $w
}

proc elapsedTime {mseconds} {
  set result ""
  set seconds [expr {wide($mseconds / 1000)}]
  set ms [ expr {wide($mseconds) % 1000}]
  if {$seconds >= 86400} {
    set days [expr {wide($seconds / 86400)}]
    set seconds [expr {$seconds % 86400}]
    append result " $days day"
    if {$days != 1} {
      append result "s"
    }
    if {$seconds != 0} {
      append result ","
    }
  }
  if {$seconds >= 3600} {
    set hours [expr {wide($seconds / 3600)}]
    set seconds [expr {$seconds % 3600}]
    append result " $hours hour"
    if {$hours != 1} {
      append result "s"
    }
    if {$seconds != 0} {
      append result ","
    }
  }
  if {$seconds >= 60} {
    set minutes [expr {wide($seconds / 60)}]
    set seconds [expr {$seconds % 60}]
    append result " $minutes minute"
    if {$minutes != 1} {
      append result "s"
    }
    if {$seconds != 0} {
      append result ","
    }
  }
  if {$seconds != 0 || $result == {}} {
    append result " $seconds"
    if {$ms != 0} {
      append result ".$ms"	      
    }
    append result " second"
    if {$seconds != 1} {
      append result "s"
    }
  }
  return $result
}

proc xToStr {g x} {
  global vsd
  if {[string equal $vsd($g:xformat) "elapsed"]} {
    return [showElapsedOnTicks $g $x]
  } elseif {[string equal $vsd($g:xformat) "time"]} {
    return [showTimeOnTicks $g $x]
  } else {
    return [showDateOnTicks $g $x]
  }
}

# 46253, add commas to numbers
proc commify { num {sep ,} } {
  global vsd
  if {$vsd(showCommas)} {    
    while {[regsub {^([-+]?\d+)(\d\d\d)} $num "\\1$sep\\2" num]} {}
  }
  return $num
}

# 46244 - format to 4 decimal places and avoid exponential notation
proc formatFloat {aFloat} {
  if {$aFloat == ""} {
    return $aFloat
  }
  if {$aFloat < 10000 && $aFloat > -10000} {
    set result [stripFloat [format "%.4f" $aFloat]]
  } elseif {$aFloat < 100000 && $aFloat > -100000} {
    set result [format "%.0f" $aFloat]
  } else {
    set result [commify [format "%.0f" $aFloat]]
  }
  return $result
}

# 46253, add commas to numbers
proc formatTick {widget x} {
  return [formatFloat $x]
}

# 45619 - correct deltax by factor of 1000 to get per second rate
proc chartOpDelta {g x y control} {
  global vsd

  set e $vsd($g:deltaLine)
  if [$g element closest $x $y info -halo 5 -interpolate 0 $e] {
    set scale [lindex [$vsd(ed:$e:$g) -info] 4]
    set coordinates "$info(x) $info(y) $scale"
  } else {
    # Was not over a valid point when left click happened so ignore
    return
  }
  if [info exists vsd($g:deltapoint1)] {
    # Spit the results out
    # Warning: x values are in milliseconds, not seconds.
    set linfo [$vsd(ed:$e:$g) -info]
    set x1 [lindex $vsd($g:deltapoint1) 0]
    set y1 [expr {[lindex $vsd($g:deltapoint1) 1] / [lindex $vsd($g:deltapoint1) 2]}]
    set x2 [lindex $coordinates 0]
    set y2 [expr {[lindex $coordinates 1] / [lindex $coordinates 2]}]
    set deltax [expr {$x2 - $x1}]
    set deltax [expr {abs($deltax)}]
    set deltay [expr {$y2 - $y1}]
    set units [getLineUnitDescription 0 $linfo]
    showData "\nDelta on [lrange $linfo 0 1]
  point1: [xToStr $g $x1], [formatFloat $y1]
  point2: [xToStr $g $x2], [formatFloat $y2]
  time delta =      [elapsedTime $deltax].
  value delta =      [formatFloat $deltay] $units."
    if {$deltax != 0} {
      # 46241 - include per hour rate as well.
      set persec [formatFloat [expr {1000 * ($deltay / $deltax)}]]
      set perhr [formatFloat [expr {3600000  * ($deltay / $deltax)}]]
      showData "  value/time delta = $persec $units per second"
      showData " = $perhr $units per hour."
      set lstats [$vsd(ed:$e:$g) -stats $x1 $x2]
      set min [formatFloat [lindex $lstats 1]]
      set max [formatFloat [lindex $lstats 2]]
      set avg [formatFloat [lindex $lstats 3]]
      set stddev [formatFloat [lindex $lstats 4]]
      
      showData \
	"  Of the [lindex $lstats 0] data points in this range:
    the min is:     $min
    the max is:     $max
    the average is: $avg
    the stddev is:  $stddev"
    }
    chartCancelOpDelta $g
  } else {
    # Save coordinates into vsd($g:deltapoint1)
    set vsd($g:deltapoint1) $coordinates
    # start watching motion events
    $g config -cursor crosshair
    set title "Click on second data point, right click to cancel"
    $g marker create text -name "chartop_title" -text $title \
      -coords {-Inf Inf} -anchor nw -bg {} -font vsdsf
    bind $g <Any-Motion> { 
      DeltaLine %W %x %y
    }
  }
}

proc chartCancelOpTrim { g } {
  global vsd
  unset vsd($g:trimLine)
  unset vsd($g:trimLeft)
  bind $g <Any-Motion> { }
  $g marker delete "chartop_title"
  showMarker $g activeLine
  chartFinishOp $g
}


proc chartOpTrim {g x y control} {
  global vsd

  set e $vsd($g:trimLine)
  if [$g element closest $x $y info -halo 5 -interpolate 0 $e] {
    set ts [expr {wide($info(x))}]
    if {$vsd($g:trimLeft)} {
      if {$ts == 0} {
	# can't trim left the very first point as it would be treated
	# as an untrim. Besides it would be a noop since "ts" is the
	# first timestamp to keep.
	return
      }
      $vsd(ed:$e:$g) -trimleft $ts
    } else {
      $vsd(ed:$e:$g) -trimright $ts
    }
    chartCancelOpTrim $g
    trimUpdate $g $e
  } else {
    # Was not over a valid point when left click happened so ignore
    return
  }
}

# 45619 - correct deltax by factor of 1000 to get per second rate
proc chartOpCompare {g x y control} {
  global vsd

  if [$g element closest $x $y info -halo 5 -interpolate 0] {
    set e $info(name)
    set scale [lindex [$vsd(ed:$e:$g) -info] 4]
    set coordinates "$info(x) $info(y) $scale"
  } else {
    # Was not over a valid point when left click happened so ignore
    return
  }
  if [info exists vsd($g:comparepoint1)] {
    # Spit the results out
    # Warning: x values are in milliseconds, not seconds.
    set e1 $vsd($g:compareLine1)
    set e2 $e
    set sameline [expr {$e1 == $e2}]
    set linfo [$vsd(ed:$e1:$g) -info]
    set units [getLineUnitDescription 0 $linfo]
    set lname "\"[lrange $linfo 0 1]\""
    set header "Comparing data from $lname"
    if {!$sameline} {
      set linfo2 [$vsd(ed:$e2:$g) -info]
      set l2name "\"[lrange $linfo2 0 1]\""
      set units2 [getLineUnitDescription 0 $linfo2]
      if {![string equal $units $units2]} {
	append units "&$units2"
      }
      append header " and $l2name"
    }
    set x1 [lindex $vsd($g:comparepoint1) 0]
    set y1 [expr {[lindex $vsd($g:comparepoint1) 1] / [lindex $vsd($g:comparepoint1) 2]}]
    set x2 [lindex $coordinates 0]
    set y2 [expr {[lindex $coordinates 1] / [lindex $coordinates 2]}]
    set deltax [expr {$x2 - $x1}]
    set deltax [expr {abs($deltax)}]
    set deltay [expr {$y2 - $y1}]
    showData "\n$header
  point1: [xToStr $g $x1], [formatFloat $y1]
  point2: [xToStr $g $x2], [formatFloat $y2]
  time delta =      [elapsedTime $deltax].
  value delta =      [formatFloat $deltay] $units."
    if {$deltax != 0} {
      set persec [formatFloat [expr {1000 * ($deltay / $deltax)}]]
      set perhr [formatFloat [expr {3600000 * ($deltay / $deltax)}]]
      showData "  value/time delta = $persec $units per second."
      showData " = $perhr $units per hour."
      set lstats [$vsd(ed:$e1:$g) -stats $x1 $x2]
      showData \
	"  Of the [lindex $lstats 0] $lname points in this range:
    the min is:     [formatFloat [lindex $lstats 1]]
    the max is:     [formatFloat [lindex $lstats 2]]
    the average is: [formatFloat [lindex $lstats 3]]
    the stddev is:  [formatFloat [lindex $lstats 4]]"
      if {!$sameline} {
	set lstats [$vsd(ed:$e2:$g) -stats $x1 $x2]
	showData \
	  "  Of the [lindex $lstats 0] $l2name points in this range:
    the min is:     [formatFloat [lindex $lstats 1]]
    the max is:     [formatFloat [lindex $lstats 2]]
    the average is: [formatFloat [lindex $lstats 3]]
    the stddev is:  [formatFloat [lindex $lstats 4]]"
      }
    }
    chartCancelOpCompare $g
  } else {
    # Save coordinates into vsd($g:comparepoint1)
    set vsd($g:compareLine1) $e
    set vsd($g:comparepoint1) $coordinates
    $g config -cursor crosshair
    set title "Click on second data point, right click to cancel"
    $g marker create text -name "chartop_title" -text $title \
      -coords {-Inf Inf} -anchor nw -bg {} -font vsdsf
    # start watching motion events
    bind $g <Any-Motion> { 
      CompareLine %W %x %y
    }
  }
}

proc chartOpMinTime {g x y control} {
  global vsd

  set x [$g xaxis invtransform $x]
  set xmin [lindex [$g xaxis limits] 0]
  if {$x >= $xmin} {
    $g xaxis configure -min $x
  } else {
    # reset as if no min was ever set
    $g xaxis configure -min ""
  }
  chartCancelOpMinTime $g
}

proc chartOpMaxTime {g x y control} {
  global vsd

  set x [$g xaxis invtransform $x]
  set xmax [lindex [$g xaxis limits] 1]
  if {$x <= $xmax} {
    $g xaxis configure -max $x
  } else {
    # reset as if no max was ever set
    $g xaxis configure -max ""
  }
  chartCancelOpMaxTime $g
}

proc chartOpCombine {g x y control} {
  global vsd

  set e2 [getSelectedLine $g]
  if {$e2 == {}} {
    return
  }
  set e1 [lindex $vsd($g:combineData) 0]
  if {$e2 == $e1} {
    return
  }
  set op [lindex $vsd($g:combineData) 1] 
  set l1 $vsd(ed:$e1:$g)
  set l2 $vsd(ed:$e2:$g)

  addDerivedLine [list $l1 $l2] $op

  chartCancelOpCombine $g
}

proc ChartHandleLeftClick {graph x y control} {
  global vsd

  focus $graph

  set legEntry [$graph legend get @$x,$y]
  if {$legEntry != {}} {
    activateElement $graph $legEntry $control
    if {![string equal $vsd($graph:chartOp) "Combine"]} {
      return
    }
  }

  chartOp$vsd($graph:chartOp) $graph $x $y $control
}

proc quickZoom {g x y} {
  global vsd

  set gx [$g xaxis invtransform $x]
  set xlimits [$g xaxis limits]
  set xMinLimit [lindex $xlimits 0]
  set xMaxLimit [lindex $xlimits 1]
  if {$gx < $xMinLimit || $gx > $xMaxLimit} {
    return
  }
  set gy [$g yaxis invtransform $y]
  set ylimits [$g yaxis limits]
  if {$gy < [lindex $ylimits 0] || $gy > [lindex $ylimits 1]} {
    return
  }

  set cmd [format {
    %s xaxis configure -min "%s" -max "%s"
  } $g [$g xaxis cget -min] [$g xaxis cget -max] ]

  #We want to center 'gx' in the middle of the graph and reduce
  #the amount of X data displayed by half.
  set xrange [expr {wide(($xMaxLimit - $xMinLimit) / 4)}]
  set xmin [expr {($gx - $xrange)}]
  if {$xmin <= $xMinLimit} {
    set xmin [$g xaxis cget -min]
  }
  set xmax [expr {$gx + $xrange}]
  if {$xmax >= $xMaxLimit} {
    set xmax [$g xaxis cget -max]
  }

  set xminVal $xmin
  if {$xminVal == ""} {
    set xminVal $xMinLimit
  }
  set xmaxVal $xmax
  if {$xmaxVal == ""} {
    set xmaxVal $xMaxLimit
  }
  if {$xminVal < $xmaxVal} {
    $g xaxis configure -min $xmin -max $xmax

    global bltZoom
    set bltZoom($g,stack) [linsert $bltZoom($g,stack) 0 $cmd]
    updateActiveLine $g
  }
}

proc ChartHandleMiddleClick {graph x y} {
  global vsd

  focus $graph

  set legEntry [$graph legend get @$x,$y]
  if {$legEntry != {}} {
    # 48628 - don't allow the last line to be deleted
    if {[llength [$graph element names]] == 1} {
      bell
      return
    }    
    deleteElement $graph $legEntry
  } else {
    quickZoom $graph $x $y
  }
}

proc BltBox {graph} {
  global bltZoom

  if { $bltZoom($graph,A,x) > $bltZoom($graph,B,x) } { 
    set x1 $bltZoom($graph,B,x)
    set x2 $bltZoom($graph,A,x) 
    set y1 $bltZoom($graph,B,y)
    set y2 $bltZoom($graph,A,y) 
  } else {
    set x1 $bltZoom($graph,A,x)
    set x2 $bltZoom($graph,B,x) 
    set y1 $bltZoom($graph,A,y)
    set y2 $bltZoom($graph,B,y) 
  }
  set coords {
    $x1 $y1 $x1 $y2 $x1 $y1 $x2 $y1 $x2 $y1 $x2 $y2 $x1 $y2 $x2 $y2 
  }
  if [$graph marker exists "bltZoom_outline"] {
    $graph marker configure "bltZoom_outline" -coords $coords
  } else {
    $graph marker create line -coords $coords -name "bltZoom_outline" \
      -dashes { 4 2 }
  }
}

######################## start of vsd code #####################


proc searchHelp {w str {next 0} {forwards 1}} {
  global vsd
  if {$str == ""} {
    return
  }
  if {$forwards} {
    set direction "-forwards"
  } else {
    set direction "-backwards"
  }
  if [info exists vsd($w:searchidx)] {
    set idx $vsd($w:searchidx)
    if {$next} {
      if {$forwards} {
	set idx [$w index "$idx +1chars"]
      } else {
	set idx [$w index "$idx -1chars"]
      }
    }
  } else {
    set idx "end"
  }
  set idx [$w search $direction -exact -nocase -- $str $idx]
  if {$idx == {}} {
    $w tag remove sel 1.0 end
    setStatus "Could not find \"$str\"."
    if {[string first $vsd(search:unfound) $str] != 0} {
      set vsd(search:unfound) $str
      bell
    }
  } else {
    $w see $idx
    $w tag remove sel 1.0 end
    $w tag add sel $idx "$idx +[string length $str]chars"
    # Note - setting background color for the "sel" tag is broken.
    #        This should work but does not:
    #
    #     $w configure -selectbackground yellow
    #
    # Next 2 lines are needed to fix 45176
    $w tag configure sel -foreground yellow
    $w tag raise sel
    set vsd($w:searchidx) $idx
  }
}

proc followHelpLink {w key} {
  doHelp $key $w
}

proc getHelpText {msgKey} {
  global vsdhelp
  set text $vsdhelp($msgKey)
  if [string match {TCLEVAL *} $text] {
    set cmd [lindex $text 1]
    set arglist [lindex $text 2]
    set text [$cmd $arglist]
  }
  return [string trimleft $text "\n"]
}

proc helpRefresh {w} {
  global vsdLinkVisited
  catch {unset vsdLinkVisited}
  restartVsd
  doHelp refresh $w
}

proc isHiddenTopic {topic} {
  expr {[string first "Topics that contain" $topic] == 0 ||
	[lsearch -exact {"All Topics" "All Help Text" "All Statistics"} $topic] != -1}
}

proc searchAllTopics {} {
  global vsd vsdhelp
  set p $vsd(search:parent)
  set searchString [string tolower $vsd($p:st)]
  if {$searchString == {}} {
    return
  }
  
  myBusyOn .vsdhelpsearch

  set searchTopic "Topics that contain \"$searchString\":"
  if {![info exists vsdhelp($searchTopic)]} {
    set searchText ""
    foreach topic [lsort [array names vsdhelp]] {
      if [isHiddenTopic $topic] {
	continue
      }
      if {[string first $searchString [string tolower $topic]] == -1} {
	set text [string tolower [getHelpText $topic]]
	if {[string first $searchString $text] == -1} {
	  continue
	}
      }
      append searchText "*\[" $topic "]\n"
    }
    if {$searchText == {}} {
      set searchText "\n<em>no topics found</em>"
    }
    set vsdhelp($searchTopic) $searchText
  }
  doHelp $searchTopic [winfo toplevel $p]
  myBusyOff .vsdhelpsearch
}

proc allHelpWindows {} {
  set result {}
  foreach w [winfo children .] {
    if {[string first ".help" $w] == 0} {
      lappend result $w
    }
  }
  return $result
}

proc rememberPosition {w} {
  global vsd
  set idx $vsd($w:helpHistoryIdx)
  set textw [$w.top.msg subwidget text]
  set vsd($w:historyPositions) [lreplace $vsd($w:historyPositions) $idx $idx \
				  [lindex [$textw yview] 0]]
}

proc closeHelpWindow {w} {
  global vsd

  #Do a focus so that the search window will go away
  focus $w
  update
  # remember geometry for 49836
  set vsd(helpGeo) [wm geometry $w]
  catch {unset vsd($w:helpHistoryIdx)}
  catch {unset vsd($w:helpHistory)}
  catch {unset vsd($w:historyPositions)}
  catch {unset vsd($w:normalCursor)}
  catch {unset vsd($w:hlinkCount)}
  destroy $w
}

proc doHelp {msgKey {w {}} {doFocus 1}} {
  global vsd vsdhelp vsdLinkVisited
  set doHistory 0
  if {[string equal $msgKey "back"]} {
    if {$vsd($w:helpHistoryIdx) == 0} {
      setStatus "nothing to go back to."
      bell
      return
    }
    rememberPosition $w
    incr vsd($w:helpHistoryIdx) -1
    set doHistory 1
  } elseif {[string equal $msgKey "forward"]} {
    if {($vsd($w:helpHistoryIdx) + 1) == [llength $vsd($w:helpHistory)]} {
      setStatus "nothing to go foward to."
      bell
      return
    }
    rememberPosition $w
    incr vsd($w:helpHistoryIdx)
    set doHistory 1
  } elseif {[string equal $msgKey "refresh"]} {
    set doHistory 1
  }
  if {$doHistory} {
    set msgKey [lindex $vsd($w:helpHistory) $vsd($w:helpHistoryIdx)]
  }
  if {![info exists vsdhelp($msgKey)]} {
    setStatus "no help for '$msgKey'."
    bell
    return
  }

  set vsdLinkVisited($msgKey) 1

  if {$w == {}} {
    # Look for a window that is already displaying this topic
    foreach w [allHelpWindows] {
      set topic [lindex $vsd($w:helpHistory) $vsd($w:helpHistoryIdx)]
      if {[string equal $topic $msgKey]} {
	# found an existing help window
	wm deiconify $w
	if {$doFocus} {
	  focus $w
	}
	raise $w
	return
      }
    }
    if {$doFocus} {
      # if doFocus is FALSE we end up using the last help window for
      # this topic instead of always creating a new window. This the
      # case for context sensitive help. if doFocus is TRUE then we
      # need to clear $w so we will create a new window. This is the
      # case of when a link was middle clicked on.
      set w {}
    }
  }
  
  if {$w != {} && [winfo exists $w]} {
    wm deiconify $w
    if {$doFocus} {
      focus $w
    }
    raise $w
    set textw [$w.top.msg subwidget text]
    if {!$doHistory} {
      set currentTopic [lindex $vsd($w:helpHistory) $vsd($w:helpHistoryIdx)]
      if {[string equal $currentTopic $msgKey]} {
	# Window already contains requested topic
	return
      }
      set vsd($w:historyPositions) \
	[lreplace $vsd($w:historyPositions) $vsd($w:helpHistoryIdx) \
	   $vsd($w:helpHistoryIdx) [lindex [$textw yview] 0]]
      incr vsd($w:helpHistoryIdx)
      if {$vsd($w:helpHistoryIdx) == [llength $vsd($w:helpHistory)]} {
	lappend vsd($w:helpHistory) $msgKey
	lappend vsd($w:historyPositions) 0
      } else {
	set vsd($w:helpHistory) \
	  [lreplace $vsd($w:helpHistory) $vsd($w:helpHistoryIdx) end $msgKey]
	set vsd($w:historyPositions) \
	  [lreplace $vsd($w:historyPositions) $vsd($w:helpHistoryIdx) end 0]
      }
    }
    $textw configure -state normal
    $textw configure -cursor $vsd($w:normalCursor)
    $textw delete "1.0" end
    $textw tag delete bulletItem bold fixed italic underline
    while {$vsd($w:hlinkCount) > 0} {
      $textw tag delete "hlink$vsd($w:hlinkCount)"
      incr vsd($w:hlinkCount) -1
    }
    foreach wn [$textw window names] {
      destroy $wn
    }
  } else {
    incr vsd(helpIdx)
    set w ".help$vsd(helpIdx)"
    toplevel $w

    global widgetHelp isWindows
    set widgetHelp($w) {Help Window Features}

    # 49836
    if { $vsd(helpGeo) != {} } {
      wm geometry $w $vsd(helpGeo)
    }
	
    frame $w.top -relief raised -bd 1

    tixScrolledText $w.top.msg -scrollbar y -width 400
    pack $w.top.msg -expand yes -fill both

    set textw [$w.top.msg subwidget text]

    # fix 45174
    $textw configure -font normalTextFont

    set tmp [list startSearch $textw searchHelp 0 .vsdhelpsearch]
    bind $w <Control-s> $tmp
    bind $w >Control-f> $tmp

    bind $w <Control-b> "doHelp back $w; break"
    bind $w <Control-f> "doHelp forward $w; break"

    bind $w <Down>  "$textw yview scroll +1 units; break"
    bind $w <Up>    "$textw yview scroll -1 units; break"
    bind $w <Next>  "$textw yview scroll +1 pages; break"
    bind $w <Prior> "$textw yview scroll -1 pages; break"
    bind $w <Home>  "$textw yview moveto 0; break"
    bind $w <End>   "$textw yview moveto 1; break"
    if [wizardMode] {
      bind $w <F4> [list helpRefresh $w]
    }

    tixButtonBox $w.box
    $w.box add back -text Back -width 6 -command "doHelp back $w"
    $w.box add forward -text Forward -width 6 -command "doHelp forward $w"
    $w.box add search -text Search... -width 6 \
      -command [list startSearch $textw searchHelp 0 .vsdhelpsearch]
    $w.box add dismiss -text Dismiss -width 6 -command [list closeHelpWindow $w]
    set but [$w.box subwidget dismiss]
    wm protocol $w WM_DELETE_WINDOW [list closeHelpWindow $w]

    pack $w.box -fill x -side bottom
    pack $w.top -side top -fill both -expand yes

    set vsd($w:normalCursor) [$textw cget -cursor]
    
    set vsd($w:helpHistory) [list $msgKey]
    set vsd($w:historyPositions) {0}
    set vsd($w:helpHistoryIdx) 0
    set vsd($w:hlinkCount) 0
  }
  if {[string match "Help*" $msgKey]} {
    wm title $w $msgKey
  } else {
    wm title $w "Help: $msgKey"
  }

  set b [$w.box subwidget back]
  if {$vsd($w:helpHistoryIdx) == 0} {
    $b configure -state disabled
  } else {
    $b configure -state normal
  }
  set b [$w.box subwidget forward]
  if {($vsd($w:helpHistoryIdx) + 1) == [llength $vsd($w:helpHistory)]} {
    $b configure -state disabled
  } else {
    $b configure -state normal
  }

  $textw tag configure bold -font boldTextFont
  $textw tag configure italic -font italicTextFont
  $textw tag configure fixed -font fixedTextFont
  $textw tag configure underline -underline 1
  $textw tag configure bulletItem -lmargin1 10 -lmargin2 30
  $textw tag configure bulletItem2 -lmargin1 30 -lmargin2 50
  $textw tag configure bulletItem3 -lmargin1 50 -lmargin2 70
  $textw insert end [getHelpText $msgKey]
  
  # decorate headers
  set idx [$textw search -forwards -regexp -- {^!} "1.0" end]
  while {$idx != {}} {
    # get rid of the exclamation point
    $textw delete $idx
    $textw tag add bold $idx "$idx lineend"
    set idx [$textw search -forwards -regexp -- {^!} $idx end]
  }
  
  # decorate bullet list items
  set idx [$textw search -forwards -regexp -- {^   \* } "1.0" end]
  while {$idx != {}} {
    # get rid of the '   * ' marker
    $textw delete $idx "$idx +5c"
    set imageIdx [$textw image create $idx -image $vsd(bulletImage) -align center -padx 3]
    $textw tag add bulletItem $imageIdx "$idx lineend"
    set idx [$textw search -forwards -regexp -- {^   \* } $idx end]
  }

  # decorate bullet2 list items
  set idx [$textw search -forwards -regexp -- {^      \* } "1.0" end]
  while {$idx != {}} {
    # get rid of the '      * ' marker
    $textw delete $idx "$idx +8c"
    set imageIdx [$textw image create $idx -image $vsd(bulletImage) -align center -padx 3]
    $textw tag add bulletItem2 $imageIdx "$idx lineend"
    set idx [$textw search -forwards -regexp -- {^      \* } $idx end]
  }

  # decorate bullet3 list items
  set idx [$textw search -forwards -regexp -- {^         \* } "1.0" end]
  while {$idx != {}} {
    # get rid of the '         * ' marker
    $textw delete $idx "$idx +11c"
    set imageIdx [$textw image create $idx -image $vsd(bulletImage) -align center -padx 3]
    $textw tag add bulletItem3 $imageIdx "$idx lineend"
    set idx [$textw search -forwards -regexp -- {^         \* } $idx end]
  }

  # decorate numbered list items
  set idx [$textw search -forwards -regexp -- {^   1 } "1.0" end]
  while {$idx != {}} {
    set itemCount 0
    while {1} {
      incr itemCount
      # get rid of the '   1 ' marker
      $textw delete $idx  "$idx +5c"
      $textw insert $idx " ${itemCount}. "
      $textw tag add bulletItem $idx "$idx lineend"
      set idx [$textw index "$idx +1lines linestart"]
      if {![string equal {   1 } [$textw get $idx "$idx +5c"]]} {
	break
      }
    }
    set idx [$textw search -forwards -regexp -- {^   1 } $idx end]
  }

  # decorate numbered list items
  set idx [$textw search -forwards -regexp -- {^      1 } "1.0" end]
  while {$idx != {}} {
    set itemCount 0
    while {1} {
      incr itemCount
      # get rid of the '      1 ' marker
      $textw delete $idx  "$idx +8c"
      $textw insert $idx " ${itemCount}. "
      $textw tag add bulletItem2 $idx "$idx lineend"
      set idx [$textw index "$idx +1lines linestart"]
      if {![string equal {      1 } [$textw get $idx "$idx +8c"]]} {
	break
      }
    }
    set idx [$textw search -forwards -regexp -- {^      1 } $idx end]
  }

  # decorate <b>...</b> tags
  set idx [$textw search -forwards -exact -- {<b>} "1.0" end]
  while {$idx != {}} {
    # get rid of the '<b>' marker
    $textw delete $idx "$idx +3c"
    set idx2 [$textw search -forwards -exact -- {</b>} $idx "$idx lineend"]
    if {$idx2 == {}} {
      setStatus "missing </b> in help"
      bell
      set idx [$textw index "$idx +1c"]
    } else {
      # get rid of the '</b>' marker
      $textw delete $idx2 "$idx2 +4c"
      $textw tag add bold $idx $idx2
      set idx $idx2
    }
    set idx [$textw search -forwards -exact -- {<b>} $idx end]
  }
  
  # decorate <u>...</u> tags
  set idx [$textw search -forwards -exact -- {<u>} "1.0" end]
  while {$idx != {}} {
    # get rid of the '<u>' marker
    $textw delete $idx "$idx +3c"
    set idx2 [$textw search -forwards -exact -- {</u>} $idx "$idx lineend"]
    if {$idx2 == {}} {
      setStatus "missing </u> in help"
      bell
      set idx [$textw index "$idx +1c"]
    } else {
      # get rid of the '</u>' marker
      $textw delete $idx2 "$idx2 +4c"
      $textw tag add underline $idx $idx2
      set idx $idx2
    }
    set idx [$textw search -forwards -exact -- {<u>} $idx end]
  }
  
  # decorate <tt>...</tt> tags
  set idx [$textw search -forwards -exact -- {<tt>} "1.0" end]
  while {$idx != {}} {
    # get rid of the '<tt>' marker
    $textw delete $idx "$idx +4c"
    set idx2 [$textw search -forwards -exact -- {</tt>} $idx "$idx lineend"]
    if {$idx2 == {}} {
      setStatus "missing </tt> in help"
      bell
      set idx [$textw index "$idx +1c"]
    } else {
      # get rid of the '</tt>' marker
      $textw delete $idx2 "$idx2 +5c"
      $textw tag add fixed $idx $idx2
      set idx $idx2
    }
    set idx [$textw search -forwards -exact -- {<tt>} $idx end]
  }
  
  # decorate <em>...</em> tags
  set idx [$textw search -forwards -exact -- {<em>} "1.0" end]
  while {$idx != {}} {
    # get rid of the '<em>' marker
    $textw delete $idx "$idx +4c"
    set idx2 [$textw search -forwards -exact -- {</em>} $idx "$idx lineend"]
    if {$idx2 == {}} {
      setStatus "missing </em> in help"
      bell
      set idx [$textw index "$idx +1c"]
    } else {
      # get rid of the '</em>' marker
      $textw delete $idx2 "$idx2 +5c"
      $textw tag add italic $idx $idx2
      set idx $idx2
    }
    set idx [$textw search -forwards -exact -- {<em>} $idx end]
  }

  # decorate ----
  set idx [$textw search -forwards -regexp -- {^----} "1.0" end]
  while {$idx != {}} {
    # get rid of the '----' marker
    $textw delete $idx "$idx +4c"
    incr vsd(ruleCount)
    set fn "$textw.rule$vsd(ruleCount)"
    frame $fn -relief raised -borderwidth 3 -height 6 -width 300
    $textw window create $idx -window $fn -align center -padx 20 -pady 8
    set idx [$textw search -forwards -regexp -- {^----} $idx end]
  }
  
  # decorate hlinks
  set idx [$textw search -forwards -exact -- \[ "1.0" end]
  while {$idx != {}} {
    # get rid of the '[' marker
    $textw delete $idx
    if {[string equal [$textw get $idx] "\["]} {
      # [[ escapes the bracket
      set idx [$textw index "$idx +1c"]
    } else {
      set idx2 [$textw search -forwards -exact -- \] $idx "$idx lineend"]
      if {$idx2 == {}} {
	setStatus "missing ']' in help"
	bell
	set idx [$textw index "$idx +1c"]
      } else {
	# get rid of the ']' marker
	$textw delete $idx2
	incr vsd($w:hlinkCount)
	set hlinkName "hlink$vsd($w:hlinkCount)"
	set hlinkText [$textw get $idx $idx2]
	if {[string first ">" $hlinkText] != -1} {
	  set hlinkList [split $hlinkText ">"]
	  set hlinkDisplay [string trim [lindex $hlinkList 0]]
	  set hlinkText [string trim [lindex $hlinkList 1]]
	  set newIdx2 [$textw index  "$idx +[string length $hlinkDisplay]c"]
	  $textw delete $newIdx2 $idx2
	  set idx2 $newIdx2
	}
	if {[info exists vsdhelp($hlinkText)]} {
	  set brokenLink 0
	} else {
	  set brokenLink 1
	}
	if {$brokenLink && [wizardMode]} {
	  puts "warning: help link '$hlinkText' does not exist."
	}
	$textw tag add $hlinkName $idx $idx2
	if {$brokenLink} {
	  $textw tag configure $hlinkName -underline 1 -foreground $vsd(brokenLinkColor)
	} elseif [info exists vsdLinkVisited($hlinkText)] {
	  $textw tag configure $hlinkName -underline 1 -foreground $vsd(visitedLinkColor)
	} else {
	  $textw tag configure $hlinkName -underline 1 -foreground $vsd(normalLinkColor)
	}
	$textw tag bind $hlinkName <1> [list followHelpLink $w $hlinkText]
	$textw tag bind $hlinkName <Control-1> [list followHelpLink {} $hlinkText]
	$textw tag bind $hlinkName <2> [list followHelpLink {} $hlinkText]
	$textw tag bind $hlinkName <3> [list followHelpLink {} $hlinkText]
	$textw tag bind $hlinkName <Enter> "$textw configure -cursor hand2"
	$textw tag bind $hlinkName <Leave> "$textw configure -cursor $vsd($w:normalCursor)"
	set idx $idx2
      }
    }
    set idx [$textw search -forwards -exact -- \[ $idx end]
  }
  # do special character substitutions
  set idx [$textw search -forwards -exact -- {&#91;} "1.0" end]
  while {$idx != {}} {
    $textw insert "$idx" \[
    $textw delete "$idx +1c" "$idx +6c"
    set idx [$textw search -forwards -exact -- {&#91;} $idx end]
  }

  $textw configure -state disabled -wrap word
  if {$doHistory} {
    $textw yview moveto [lindex $vsd($w:historyPositions) $vsd($w:helpHistoryIdx)]
  }
  setMasterBgColorForWindow $w
}

event add <<Help>> <KeyPress-F1> <KeyPress-Help>
bind all <<Help>> {ContextSensitiveHelp %W}

proc ContextSensitiveHelp {w} {
  global widgetHelp
  while {$w != ""} {
    if [info exists widgetHelp($w)] {
      doHelp $widgetHelp($w) {} 0
      return
    }
    set w [winfo parent $w]
  }
  setStatus "no help available"
  bell
}

proc showChartHelp {} {
  doHelp "Chart Window Features"
}

proc showMainHelp {} {
  doHelp "Main Window Features"
}

proc HowToHelp {} {
  global vsdhelp
  set howtoTopic "How To Guide"
  if {![info exists vsdhelp($howtoTopic)]} {
    set help {}
    foreach topic [lsort [array names vsdhelp]] {
      if [string match {*How to*} $topic] {
	append help "*\[$topic]\n"
      }
    }
    set vsdhelp($howtoTopic) $help
  }
  doHelp $howtoTopic
}

proc wizardMode {} {
  global env vsd
  return [expr {[info exists env(VSD_WIZARD)] || [info exists vsd(wizard)]}]
}

proc allHelp {} {
  global vsdhelp
  set allTopic "All Topics"
  if [wizardMode] {
    catch {unset vsdhelp($allTopic)}
  }
  if {![info exists vsdhelp($allTopic)]} {
    set help {}
    foreach topic [lsort [array names vsdhelp]] {
      if [isHiddenTopic $topic] {
	continue
      }
      append help "*\[" $topic "]\n"
    }
    set vsdhelp($allTopic) $help
  }
  doHelp $allTopic
}

proc allFlatHelp {} {
  global vsdhelp
  set allTopic "All Help Text"
  if [wizardMode] {
    catch {unset vsdhelp($allTopic)}
  }
  if {![info exists vsdhelp($allTopic)]} {
    set help {}
    foreach topic [lsort [array names vsdhelp]] {
      if [isHiddenTopic $topic] {
	continue
      }
      append help "----\n"
      append help "!\t$topic\n"
      append help [getHelpText $topic]
    }
    set vsdhelp($allTopic) $help
  }
  doHelp $allTopic
}

proc showAboutHelp {} {
  doHelp "About VSD"
}

proc UsageVSD {parent} {
  global vsd
  if {$vsd(warningGiven)} {
    return
  }
  set vsd(warningGiven) 1

  set usagevsd "Problem reports and enhancement requests should be sent to $vsd(maintainer).
If you want to use VSD, even though it is unsupported, click 'Yes'.
If you want to quit now, click 'No'."

  if {[info commands tk_messageBox] != ""} {
    set response [tk_messageBox -title "VSD Usage Notice" \
		    -parent $parent -icon question -type yesno \
		    -default yes -message $usagevsd]
    if {[string equal $response "no"]} {
      exit
    }
  } else {
    set w [tixDialogShell .usagevsd -parent $parent \
	     -title "VSD Usage Notice"]

    frame $w.top -relief raised -bd 1
    pack $w.top -side top -fill both -expand yes

    tixScrolledText $w.top.msg -scrollbar y -width 410 -height 150
    [$w.top.msg subwidget text] insert end $usagevsd
    [$w.top.msg subwidget text] configure -state disabled -wrap word
    pack $w.top.msg -expand yes -fill both

    tixButtonBox $w.box
    $w.box add continue -text "Yes" -width 4 \
      -command "destroy $w"
    $w.box add quit -text "No" -width 4 \
      -command "destroy $w; exit"
    pack $w.box -fill x -side bottom
    $w popup
  }
}

if {[info commands tk_messageBox] != ""} {
  # use tk_messageBox
  proc makeErrorDialog {w} {
    # do nothing
  }

  proc showError {msg} {
    global vsd
    tk_messageBox -icon error -parent $vsd(w) -title "Error" -message $msg \
      -type ok
  }

  proc fatalError {msg} {
    set savedErrorInfo ""
    if {[catch {buildCallStack} callStack]} {
      set savedErrorInfo [string cat "\nError getting Tcl call stack:\n   $callStack\n"]
    } else {
      set savedErrorInfo [string cat $savedErrorInfo $callStack]
    }
    set msg [string cat $msg $savedErrorInfo]
    puts stderr $msg
    showError $msg
    exit
  }

} else {
  proc makeErrorDialog {w} {
    global vsd
    set d [tixDialogShell .vsderror -parent $w -title "Error"]
    set vsd(dlg:error) $d

    frame $d.top -relief raised -bd 1
    pack $d.top -side top -fill both

    label $d.top.msg -wraplength 3i -justify left -text "foo" \
      -font -Adobe-Times-Medium-R-Normal--*-180-*-*-*-*-*-*
    pack $d.top.msg -side right -expand 1 -fill both -padx 3m -pady 3m
    label $d.top.bitmap -bitmap "error"
    pack $d.top.bitmap -side left -padx 3m -pady 3m

    tixButtonBox $d.box
    $d.box add dismiss -text Dismiss -width 6 -underline 0 \
      -command {$vsd(dlg:error) popdown; set vsd(fatalError) 1}
    set but [$d.box subwidget dismiss]
    bind $d <Alt-d> "$but invoke"
    pack $d.box -fill x -expand yes -side bottom
  }

  proc showError {msg} {
    global vsd
    set w $vsd(dlg:error)
    $w.top.msg configure -text $msg
    regexp {(.*x.*)(\+.*\+.*)} [wm geometry $vsd(w)] junk parentSize parentPosition
    wm geometry $w $parentPosition
    $w popup
  }

  proc fatalError {msg} {
    global vsd
    set vsd(fatalError) 0
    showError $msg
    tkwait variable vsd(fatalError)
    exit
  }
}

proc replayLog {str} {
  # NYI
}

proc writeTemplateFile {} {
  global vsd vsdtemplates

  if [catch {open "$vsd(VSDHOME)/$vsd(vsdTemplatesFn)" w+} initFile] {
    showError "Notice: could not write '$vsd(VSDHOME)/$vsd(vsdTemplatesFn)' because:\n$initFile"
    return
  }
  puts $initFile {
    # See the vsd help section 'Template Syntax' for a guide to editing this file.
  }

  foreach tn [lsort [array names vsdtemplates]] {
    puts $initFile "set vsdtemplates($tn) [list $vsdtemplates($tn)]"
  }

  close $initFile
}

proc addFileListMap {dfid} {
  global filelistMap
  global appendedFileMap
  set key [getFileInfoName $dfid]
  set filelistMap($key) $dfid

  if {![info exist appendedFileMap($key)]} {
    set appendedFileMap($key) 0
  }
  return $key
}

# Fix 45854
proc setFileWasAppended {df} {
  global vsd filelistMap appendedFileMap fileInfoNameMap
  set oldName [getFileInfoName $df]

  # Ignore if the not the first append to the file
  if {$appendedFileMap($oldName) == 1} {
    return
  }

  set newName "$oldName \[Appended\]"
  set dfid $filelistMap($oldName)

  # Update filelistMap
  unset filelistMap($oldName)
  set  filelistMap($newName) $dfid

  # Update the filelist object and widget
  $vsd(w:filelist) configure -disablecallback true
  set lb [$vsd(w:filelist) subwidget listbox]
  set idx [lsearch -exact $vsd(filelist) $oldName]
  set vsd(filelist) [lreplace $vsd(filelist) $idx $idx $newName]
  # First entry in widget is "Browse for file"
  # So the index in the filelist object is idx + 1
  incr idx 1
  $lb delete $idx $idx
  $lb insert $idx $newName
  $vsd(w:filelist) configure -disablecallback false

  # Update appendedFileMap
  unset appendedFileMap($oldName)
  set appendedFileMap($newName) 1
  # Update fileInfoNameMap
  set fileInfoNameMap($dfid) $newName
  if {[string equal $oldName $vsd(lastdatafile)]} {
    set vsd(lastdatafile) $newName
  }
}

################################################################################
# getFullFileName
#
# input: full file string:
#   1. /export/bunk2/users/normg/stats/appended/statmon24483-1.out.gz bunk Linux 3.3.1
# output: file name:
#  /export/bunk2/users/normg/stats/appended/statmon24483-1.out.gz
################################################################################
proc getFullFileName {fi} {
  global filelistMap
  if [info exists filelistMap($fi)] {
    set result [lindex [$filelistMap($fi) -info] 0]
  } else {
    set result $fi
  }
  return $result
}

################################################################################
# 46541 - build a list of startup items and then sort it.
################################################################################
proc buildVsdRcContents {} {
  global vsd vsdtemplates
  set myList {}
  lappend myList \
    "\# $vsd(vsdRcFn) is written whenever vsd exits normally.
\# Each time vsd is started it reads $vsd(vsdRcFn) and will use the configuration
\# values in it. If you want to always have some other default put it
\# in $vsd(vsdConfigFn). Any user changes to $vsd(vsdRcFn) will be lost the next time
\# vsd exits.
"
  lappend myList "set vsd(confirmonexit) $vsd(confirmonexit)"
  lappend myList "set vsd(warningGiven) $vsd(warningGiven)"
  foreach cw $vsd(winnames) {
    if [winfo exists $cw] {
      set vsd(geometry:$cw) [wm geometry $cw]
    }
    lappend myList "set vsd(geometry:$cw) [list $vsd(geometry:$cw)]"
  }

  # 48980 - double quote to guard against spaces in path names  
  lappend myList "set vsd(searchNameCaseSensitive) [list $vsd(searchNameCaseSensitive)]"
  lappend myList "set vsd(snapshotDirName) \"[list $vsd(snapshotDirName)]\""
  lappend myList "set vsd(snapshotFileName) \"[list $vsd(snapshotFileName)]\""
  lappend myList "set vsd(prevGraphWindowGeom) [getPrevGraphGeomForVsdRc]"
  lappend myList "set vsd(def:geometry) [wm geometry $vsd(w)]"
  lappend myList "set vsd(autoUpdateIntervalMs) [list $vsd(autoUpdateIntervalMs)]"
  lappend myList "set vsd(fileInactiveTime) $vsd(fileInactiveTime)"
  lappend myList "set vsd(def:showlegend) [list $vsd(def:showlegend)]"
  lappend myList "set vsd(def:xformat) [list $vsd(def:xformat)]"
  lappend myList "set vsd(def:linestyle) [list $vsd(def:linestyle)]"
  lappend myList "set vsd(def:xaxis:showtitle) [list $vsd(def:xaxis:showtitle)]"
  lappend myList "set vsd(def:yaxis:showtitle) [list $vsd(def:yaxis:showtitle)]"
  lappend myList "set vsd(def:y2axis:showtitle) [list $vsd(def:y2axis:showtitle)]"
  lappend myList "set vsd(def:showcrosshairs) [list $vsd(def:showcrosshairs)]"
  lappend myList "set vsd(def:showgridlines) [list $vsd(def:showgridlines)]"
  lappend myList "set vsd(def:showcurxy) [list $vsd(def:showcurxy)]"
  lappend myList "set vsd(def:showminmax) [list $vsd(def:showminmax)]"
  lappend myList "set vsd(def:showlinestats) [list $vsd(def:showlinestats)]"
  lappend myList "set vsd(def:sortbytype) [list $vsd(def:sortbytype)]"
  lappend myList "set vsd(def:sortdecreasing) [list $vsd(def:sortdecreasing)]"
  lappend myList "set vsd(showCtrInfo) [list $vsd(showCtrInfo)]"
  lappend myList "set vsd(absoluteTSMode) [list $vsd(absoluteTSMode)]"
  lappend myList "set vsd(copyChildren) [list $vsd(copyChildren)]"
  lappend myList "set vsd(singleFileMode) [list $vsd(singleFileMode)]"
  lappend myList "set vsd(templatesUseSelection) [list $vsd(templatesUseSelection)]"
  lappend myList "set vsd(wv:statmoninterval) [list $vsd(wv:statmoninterval)]"
  lappend myList "set vsd(wv:statmonflush) [list $vsd(wv:statmonflush)]"
  lappend myList "set vsd(activecolor) $vsd(activecolor)"
  lappend myList "set vsd(combineInstances) $vsd(combineInstances)"
  # 46260 - remove Combine Across Files
  lappend myList "set vsd(noFlatlines) $vsd(noFlatlines)"
  lappend myList "set vsd(noAllZeros) $vsd(noAllZeros)"
  lappend myList "set vsd(traceAPICalls) $vsd(traceAPICalls)"
  lappend myList "set vsd(fileOpenGeometry) $vsd(fileOpenGeometry)"
  # 45838 - remember last directory location
  # backslashes in .vsdrc cause trouble.  Switch to forward slashes since Tcl always understands
  # them, even on windoze.

  # 48980 - double quote to guard against spaces in path names
  lappend myList "set vsd(lastdatadir) \"[string map {\\ /} $vsd(lastdatadir)]\""
  if {[info exists vsd(fileTypePattern)]} {
    lappend myList "set vsd(fileTypePattern) [list $vsd(fileTypePattern)]" ;# 47666
  }
  if {[info exists vsd(fileSaveTypePattern)]} {
    lappend myList "set vsd(fileSaveTypePattern) [list $vsd(fileSaveTypePattern)]";
  }  
  
  if {![info exists vsd(lastVersionRun)] || ($vsd(lastVersionRun) < $vsd(version))} {
    lappend myList "set vsd(lastVersionRun) $vsd(version)"
  } else {
    lappend myList "set vsd(lastVersionRun) $vsd(lastVersionRun)"
  }

  lappend myList "set vsd(instancePaneSize) [.vsd.mainPane panecget instances -size]"
  lappend myList "set vsd(statisticPaneSize) [.vsd.mainPane panecget statistics -size]"

  foreach n [array names vsd *:graphgeometry] {
    set geom $vsd($n)
    # don't bother remembering funny geometries
    if {![string match "1x1+*" $geom]} {
      lappend myList "set vsd($n) [list $geom]"
    }
  }

  lappend myList "set vsd(smallfont) [list [font actual vsdsf]]"
  lappend myList "set vsd(textfont) [list [font actual normalTextFont]]"
  # 46261
  lappend myList "set vsd(mainWindowFont) [list [font actual normalMainWindowFont]]"
  lappend myList "set vsd(dataLogFont) [list [font actual dataLogFont]]"
  
  # 45851 - don't remember previously opened files
  foreach level [array names vsd level:*] {
    lappend myList "set vsd($level) $vsd($level)"
  }
  foreach filter [array names vsd filter:*] {
    lappend myList "set vsd($filter) $vsd($filter)"
    # fix 49835
    set idx [string last ":" $filter]
    if {$idx != -1} {
      incr idx
      set name [string range $filter $idx end]
      # Use catch to make sure we don't get an exception reading .vsdrc
      lappend myList "catch {sl_stat -info $name \[list {} {} $vsd($filter)\]}"
    }
  }

  lappend myList "set vsd(showCommas) $vsd(showCommas)"
  # 46239
  lappend myList "set vsd(autoSaveConfig) $vsd(autoSaveConfig)"
  lappend myList "set vsd(secondarySortKeyIdx) $vsd(secondarySortKeyIdx)"
  lappend myList "set vsd(secondarySortKeyString) $vsd(secondarySortKeyString)"
  lappend myList "set vsd(wv:statmonargs) [list $vsd(wv:statmonargs)]"
  # 46541
  lappend myList "set vsd(wv:statmonCompressGz) $vsd(wv:statmonCompressGz)"
  lappend myList "set vsd(wv:statmonCompressLz4) $vsd(wv:statmonCompressLz4)"
  lappend myList "set vsd(default_left_trim_secs) $vsd(default_left_trim_secs)"
  lappend myList "set vsd(default_left_trim_mins) $vsd(default_left_trim_mins)"
  lappend myList "set vsd(default_left_trim_hrs)  $vsd(default_left_trim_hrs)"
  # 49836
  lappend myList "set vsd(helpGeo) [list $vsd(helpGeo)]"
  set sortedList [lsort $myList]
  return $sortedList
}


proc buildStartupFileFooter {} {
  set result "\}\n# *** End Counter Aliases Section ***\n"
  return $result
}



################################################################################
# buildCounterAliasesStartupHeader
#
# Build a long TCL comment string to be written to the .vsdrc file to document
# how to setup statistic aliases.
################################################################################
proc buildCounterAliasesStartupHeader {} {
  set hdr "\n# *** Begin Counter Aliases Section ***\n"
  set hdr [string cat $hdr \
	     "# Use the setCtrAlias command to create aliases for statistics by name and cache process name\n"]
  set hdr [string cat $hdr "#  setCtrAlias <CachePattern> <StatName> <Alias> <Type> <Filter> <Units> <Description> \[versionPatterns\]\[writeToStartupFile\] \n"]
  set hdr [string cat $hdr "#  Arguments:\n"]
  set hdr [string cat $hdr "#    CachePattern - cache name or list of cache names of processes to match\n"]
  set hdr [string cat $hdr "#    StatName - name of an existing cache statistic\n"]
  set hdr [string cat $hdr "#    Alias - name of the alias\n"]
  set hdr [string cat $hdr "#    Type  - one of counter, counter64, uvalue, svalue, svalue64.\n"]
  set hdr [string cat $hdr "#    Filter - one of none, persecond or persample.\n"]
  set hdr [string cat $hdr "#    Units  - Unit of measure for the statistic.\n"]
  set hdr [string cat $hdr "#    Description - A brief description of the statistic.\n"]
  set hdr [string cat $hdr "#    versionPatterns (optional) - list of GemStone versions that the alias applies to (default: *).\n"]
  set hdr [string cat $hdr "#    writeToStartupFile (optional) - Boolean (1 or 0) indicating if the alias will be written to .vsdrc (default: 1)\n"]
  set hdr [string cat $hdr "#\n"]
  set hdr [string cat $hdr "# Example:\n"]
  set hdr [string cat $hdr "#   setCtrAlias \{WatchDog* watchdog*\} SessionStat00 NumUsersKilled counter64 none \{Sessions Killed\} \\\n"]
  set hdr [string cat $hdr "#     \{Number of sessions killed by this WatchDog process\} * 1\n"]
  set hdr [string cat $hdr "# Skip setting aliases if setCtrAlias command does not exist (older VSDs)\n"]
  set hdr [string cat $hdr "if {\[llength \[namespace which setCtrAlias\]\]\} \{\n"] ;# }
  set hdr [string cat $hdr "# Begin statistic aliases. Add new aliases BELOW this line.\n"]
  return $hdr
}


################################################################################
# buildCounterAliases
#
# Build a long TCL string to be written to the .vsdrc file to document.
# The string will be written to .vsdrc and contains comments and code to set
# statistic aliases configured by the user.
################################################################################
proc doBuildCounterAliasesFor {arg} {
  upvar $arg arrayArg
  set result ""
  set aliases [array names arrayArg]
  if {[llength $aliases]} {
    foreach aName $aliases {
      set pList [split $aName ,]
      set verDict $arrayArg($aName)
      dict for {verPattern attribList} $verDict {
	set writeToVsdRc [lindex $attribList end]
	if {$writeToVsdRc == 1} {
	  set result [string cat $result \
			"  setCtrAlias $pList [lrange $attribList 0 end-1] $verPattern 1\n"]
	} elseif {$writeToVsdRc != 0} {
	  bgerror "Illegal value $writeToVsdRc in attribList $attribList"
	}
      }
    }
  }
  return $result
}

proc buildCounterAliases {} {
  global ctrAliases
  set result [doBuildCounterAliasesFor ctrAliases]
  return $result
}

# 47637 - use VSDHOME instead of ~
proc writeVsdHomeFile {fn dataList} {
  global vsd
  set tempFn "$vsd(VSDHOME)/${fn}_tmp"
  set realFn "$vsd(VSDHOME)/$fn"
  if {[catch {open $tempFn w+} initFile]} {
    showError "Notice: could not write '$tempFn' because:\n$initFile"
    return
  }

  foreach n $dataList {
    puts $initFile $n
  }
  
  if {[catch {close $initFile} errStr]} {
    showError "Notice: could not close '$tempFn' because:\n$errStr"
    return
 }
  
  if {[catch {file rename -force "$tempFn" "$realFn"} errStr]} {
    showError "Notice: could not rename '$tempFn' to '$realFn' because:\n$errStr"
    return
  }
}


proc writeStartupFile {} {
  global vsd
  set sortedList [buildVsdRcContents]
  lappend sortedList [buildCounterAliasesStartupHeader]
  lappend sortedList [buildCounterAliases]
  lappend sortedList [buildStartupFileFooter]
  writeVsdHomeFile $vsd(vsdRcFn) $sortedList
}

################################################################################
# statFromAlias
#
# Given an alias, extract the stat name.
# Example
#
# statFromAlias "SessionStat00 (ReclaimCommitPageReads)"
#   returns SessionStat00
#
# statFromAlias SessionStat17
#   returns SessionStat17
# 
################################################################################
proc statFromAlias {alias} {
  # Find the first space in the string
  set idx [string first " " $alias]
  if {$idx == -1} {
    return $alias ;# No space means not an alias
  } else {
    # Chop off everything from the space onwards.
    return [string replace $alias $idx end]
  }
}

# Return the indexes of the selected instances in vsd(instanceList)
proc getSelectedInstances {} {
  global vsd
  return [$vsd(instanceList) info selection]
}

proc versionMatchesAnyVerPattern {version verPatterns} {
  foreach aVerPattern $verPatterns {
    if {[string match $aVerPattern $version]} {
      return 1
    }
  }
  return 0
}

proc buildCtrAliasMenuList {aVersion} {
  global vsd ctrAliasTemplateTypes
  set m $vsd(processMenu).applyAlias
  set typesToAdd {}
  set names [array names ctrAliasTemplateTypes]
  foreach aType $names {
    set verPatternList [dict keys [dict get $vsd(cacheIdAliasDict) $aType]]
    if {[versionMatchesAnyVerPattern $aVersion $verPatternList]} {
     lappend typesToAdd $aType
    }
  }

  if {[llength $typesToAdd]} {
    clearCtrAliasMenuList
    set slist [lsort $typesToAdd]
    foreach aType $slist {
      $m add command -label $aType -command applyAliasTemplateForType$aType
    }
  }
}

proc clearCtrAliasMenuList {} {
  global vsd
  $vsd(processMenu).applyAlias delete 0 end
}

proc updateApplyAliasMenuState {newState} {
  global vsd
  $vsd(processMenu) entryconfigure "Apply Alias Template for Type" -state $newState
}

proc updateRemoveAliasesMenuState {newState} {
  global vsd
  $vsd(processMenu) entryconfigure "Remove Aliases" -state $newState
}

proc procKindIsGem {procKind} {
  return [string match "Gem*" $procKind]
}

proc procKindHasAliases {procKind} {
  if {[procKindIsGem $procKind]} {
    return 1
  }
  
  if {[string match "Stn*" $procKind]} {
    return 1
  }
  return 0
}

proc fillCounters {force procs} {
  global vsd fileIdToVersionMap

  if {!$force && [string equal $procs $vsd(selectedProcs)]} {
    # Early exit if no change
    return
  }
  set debug 0
  set enableAliasTemplates normal
  set objlist {}
  set instIdList {}
  set numProcs [llength $procs]
  set numProcsWithAliasTemplates 0
  set numProcsWithoutAlias 0
  set numProcsWithBuiltinAliases 0
  set numProcsAreGems 0
  if {$numProcs} {
    foreach ientry $procs {
      set entryData [$vsd(instanceList) info data $ientry]
      lappend instIdList [lindex $entryData 6]
      set procKind [lindex $entryData 1] ; # Gem, Stn, etc
      if {[procKindHasAliases $procKind]} { ; # handle aliases
	if {[procKindIsGem $procKind]} {
	  incr numProcsAreGems
	}
	set instName [lindex $entryData 0]
        set procKind [getProcKindForInstanceName $instName $procKind]
	set version $fileIdToVersionMap([lindex $entryData 5]) ;# 3.5.0
	set verset($version) 1
	if {[info exists vsd(instancesWithAliasTemplates:$instName)]} {
          incr numProcsWithAliasTemplates
	} elseif {[instNameHasAnyAlias $instName]} {
	  incr numProcsWithBuiltinAliases
        } else {
	  incr numProcsWithoutAlias
	}
      } ;# procKindHasAliases
      set objset($procKind) 1
    } ;# foreach
    set objlist [lsort [array names objset]]
  }

  set totalAliasProcs [expr {$numProcsWithAliasTemplates + $numProcsWithBuiltinAliases}]
  if {$debug} {
    set msg "numProcsWithAliasTemplates: $numProcsWithAliasTemplates "
    append msg "numProcsWithBuiltinAliases: $numProcsWithBuiltinAliases "
    append msg "totalAliasProcs: $totalAliasProcs "
    append msg "numProcsWithoutAlias: $numProcsWithoutAlias\n"
    puts stderr $msg
  }
  
  if {$numProcs && ([array size verset] == 1) } {
    set version [lindex [array names verset] 0]
    buildCtrAliasMenuList $version  
    if {$totalAliasProcs == $numProcs} {
      # All selected procs have an alias template already.
      # Enable removing the templates menu
      updateApplyAliasMenuState disabled
      updateRemoveAliasesMenuState normal
    } elseif {$numProcsWithoutAlias == $numProcs && \
	      $numProcsAreGems == $numProcs} {
      # All selected procs are gems that do not have an alias template.
      # Enable applying alias template menu
      updateApplyAliasMenuState normal
      updateRemoveAliasesMenuState disabled
    } else {
      updateApplyAliasMenuState disabled
      updateRemoveAliasesMenuState disabled
    }
  } else {
    clearCtrAliasMenuList
    updateApplyAliasMenuState disabled
    updateRemoveAliasesMenuState disabled
  }
  
  set selCtr [getSelCtr]
  if {$selCtr != {}} {
    set vsd(lastSelCtr) $selCtr
  }
  clearCounters
  set vsd(counterType) $objlist
  set vsd(selectedInstIds) $instIdList
  set vsd(selectedProcs) $procs
  set tc ""
  if [info exists vsd(lastSelCtr)] {
    set tc $vsd(lastSelCtr)
  }
  set ctrlist [getCountersForTypes $objlist]
  set cl $vsd(w:counterHList)
  set anchorSet 0
  foreach ctr $ctrlist {
    set ctrNoAlias [statFromAlias $ctr] ;# trim off any alias
    set isZero [sl_stat -ctrzero $ctrNoAlias $instIdList]
    if {($vsd(noFlatlines) || $vsd(noAllZeros)) && $isZero} {
      lappend vsd(zeroCtrs) $ctrNoAlias
      # Skip this counter
      continue
    }
    set entry [$cl addchild "" -data $ctr -itemtype text -text $ctr \
		 -style $vsd(ctr$isZero:s)]
    if {[lsearch -exact $tc $ctr] != -1} {
      if {!$anchorSet} {
	set anchorSet 1
	$cl anchor set $entry
	$cl see $entry
	showCounterInfo $ctr
      }
      $cl selection set $entry
    }
  }
  updateDisabledItems

}

proc updateDisabledItems {} {
  global vsd
  set newstate "normal"
  set procs [getSelectedInstances]
  if {$procs == {} || [getSelCtr] == {}} {
    set newstate "disabled"
  }

  # newstate should be "normal" or "disabled"
  $vsd(w:addline)   configure -state $newstate
  $vsd(w:newchart)  configure -state $newstate
  $vsd(w:chartmenu) entryconfigure "Add To Chart" -state $newstate
  $vsd(w:chartmenu) entryconfigure "New Chart..." -state $newstate
  $vsd(w:mainmenu)  entryconfigure "Copy Selection" -state $newstate
  return $procs
}

proc updateGraphWidgetState {} {
  global vsd
  $vsd(w:templatemenu) entryconfigure "New Template Chart" -state normal
  fillCounters 0 [updateDisabledItems]
}

proc updateStatSrc {f {fileInfo 0}} {
  global vsd
  if { ![info exists vsd(statsrc)]} {
    set old "nothing"
  } else {
    set old $vsd(statsrc)
  }
  set vsd(statsrc) $f
  if {$fileInfo == 0} {
    set fileInfo [$f -info]
  }
  updateRollingTimeTrimmerForFileSelect
}

proc showFileForAnchor {} {
  global vsd
  set anchor [$vsd(instanceList) info anchor]
  if {$anchor != {}} {
    set data [$vsd(instanceList) info data $anchor]
    set fd [lindex $data 5]
    if {![string equal $fd $vsd(statsrc)]} {
      updateStatSrc $fd
      changeFile $fd
    }
  }
}

proc instanceBrowse {junk} {
  set t [tixEvent type]
  # fix for bug 22784
  set updateEvents {"<ButtonRelease-1>" "<Application>" "<Up>" "<Down>"
    "<Shift-Up>" "<Shift-Down>" "<Left>" "<Right>"}
  if {[lsearch -exact $updateEvents $t] != -1} {
    showFileForAnchor
    updateGraphWidgetState
  }
}

proc getSortedFiles {} {
  global vsd
  
  if {$vsd(def:sortdecreasing)} {
    return [lsort -decreasing -dictionary [sl_stat -enabledfiles]]
  } else {
    return [lsort -dictionary [sl_stat -enabledfiles]]
  }
}

proc sortWithKeys {l} {
  global vsd
  
  if {$vsd(def:sortdecreasing)} {
    return [lsort -decreasing -dictionary -index end $l]
  } else {
    return [lsort -dictionary -index end $l]
  }
}

proc instSort {l} {
  return [sortWithKeys [addSortKeysToList $l]]
}

proc addInst {l inst {makeAllInactive 0}} {
  global vsd
  global vsdinstmap

  set needsPid 1
  set obj [lindex $inst 1]
  # strip off any object version extension
  set obj [file rootname $obj]
  if {[string equal $obj "AppStat"] ||
      [string equal $obj "IOStat"]} {
    set needsPid 0
  }

  set instid [lindex $inst 6]
  set fullname [lindex $inst 0]
  set name [lindex $fullname 0]
  global vsdnametable
  if [info exists vsdnametable($name)] {
    incr vsdnametable($name)
  } else {
    set vsdnametable($name) 1
  }

  set row [$l addchild "" -data $inst]

  set vsdinstmap($instid) $row
  # Start time
  $l item create $row $vsd(ilColumnIdx:StartTime) -itemtype text \
    -text [sl_stat -getdate [lindex $inst 2] $vsd(time_shift_hours) "date"] \
    -style $vsd(time:s)
  # End time - 44163
  $l item create $row $vsd(ilColumnIdx:EndTime) -itemtype text \
    -text [sl_stat -getdate [lindex $inst 7] $vsd(time_shift_hours) "date"] \
    -style $vsd(time:s)
  # File
  $l item create $row $vsd(ilColumnIdx:File) -itemtype text -text \
    [getFileId [lindex $inst 5]] -style $vsd(time:s)
  # Number of samples
  if {$makeAllInactive == 0} {
    set style $vsd(samples[lindex $inst 3]:s)
  } else {
    set style $vsd(samples0:s)
  }
  $l item create $row $vsd(ilColumnIdx:Samples) -itemtype text \
    -text [lindex $inst 4] -style $style
  # Pid
  if {$needsPid} {
    set pid [lindex $fullname 1]
    if {$pid != 0 && $pid != -1} {
      $l item create $row $vsd(ilColumnIdx:ProcessId) -itemtype text \
	-text $pid -style $vsd(time:s)
    }
  }
  # Session ID
  # Fix 43132 - skip very large session ID's.
  set sid [lindex $fullname 2]
  if {$sid > 0 && $sid < 1073741824} {
    $l item create $row $vsd(ilColumnIdx:SessionId) -itemtype text \
      -text $sid -style $vsd(time:s)
  }
  # Type
  $l item create $row $vsd(ilColumnIdx:Type) -itemtype text \
    -text [lindex $inst 1] -style $vsd(time:s)
  # Name
  $l item create $row $vsd(ilColumnIdx:Name) -itemtype text \
    -text $name -style $vsd(name:s)
  return $row
}

proc setInstanceList { } {
  global vsd
  global vsdinstmap
  if {![info exists vsd(instanceList)]} {
    return
  }

  set l $vsd(instanceList)

  set selectedInstances {}
  foreach e [$l info selection] {
    set instid [lindex [$l info data $e] 6]
    lappend selectedInstances $instid
  }
  set oldAnchor [$l info anchor]
  if {$oldAnchor != {}} {
    set instid [lindex [$l info data $oldAnchor] 6]
    set oldAnchor $instid
  }
  
  $l delete all
  if [info exists vsdinstmap] {
    unset vsdinstmap
  }
  set instances [sl_stat -enabledinstanceswithout $vsd(hiddenInstanceTypes)]
  if {$instances == {}} {
    return
  }
  global vsdnametable
  if [info exists vsdnametable] {
    unset vsdnametable
  }
  #    set start [getMillis]
  # 41903 - remove optimization to prevent a resort. It is NOT VALID in
  #         single file mode and sometimes in multi-file mode.

  set sortedInstances [instSort $instances]
  set makeAllInactive 0
  if {$vsd(singleFileMode) || [numDataFiles] == 1} {
    if {[hasInactiveDataFile $vsd(statsrc)]} {
      set makeAllInactive 1
    }
  } else {
    if {[numInactiveDataFiles] > 0} {
      set newSortedInstances {}
      foreach inst $sortedInstances {
	set f [lindex $inst 5]
	if {[hasInactiveDataFile $f]} {
	  lset inst 3 0
	  set foundOne 1
	}
	lappend newSortedInstances $inst
      }
      set sortedInstances $newSortedInstances
    }
  }
  
  #     set end [getMillis]
  #     puts "DEBUG: sorting took [expr {$end - $start}]"
  #     set vsd(stats:addInst:count) 0
  #     set start [getMillis]
  foreach i $sortedInstances {
    #    incr vsd(stats:addInst:count)
    buildInstNameToProcKindTableForInst $i
    set e [addInst $l $i $makeAllInactive]
    set instid [lindex $i 6]
    if {$instid == $oldAnchor} {
      $l anchor set $e
      $l see $e
    }
    if {[lsearch -exact $selectedInstances $instid] != -1} {
      $l selection set $e
    }
  }
  #     set end [getMillis]
  #     puts "DEBUG: process sortedInstances took [expr {$end - $start}]"
  #     puts "DEBUG: addInst count $vsd(stats:addInst:count)"
}

proc getMillis {} {
  #  -milliseconds
  return [clock clicks]
}

proc instanceCommand {e} {
  doChart 0
}

proc getPrimTypeSortKey {inst} {
  return [string tolower [lindex $inst 1]]
}
proc getPrimStartTimeSortKey {inst} {
  return [lindex $inst 2]
}

# 44163
proc getPrimEndTimeSortKey {inst} {
  return [lindex $inst 7]
}

proc getPrimPidSortKey {inst} {
  return [lindex [lindex $inst 0] 1]
}
proc getPrimSidSortKey {inst} {
  set res  [lindex [lindex $inst 0] 2]
  if { $res <= 0 } {
    set res 0
  }
  return $res
}
proc getPrimFileSortKey {inst} {
  return [lindex $inst 5]
}
proc getPrimNameSortKey {inst} {
  set nameData [lindex $inst 0]
  set res [string tolower [lindex $nameData 0]]
  set nameId [lindex $nameData 2]
  if {$nameId != {}} {
    append res "_"
    append res $nameId
  }
  return $res
}

proc getPrimSamplesSortKey {inst} {
  set key [lindex $inst 4]
  if {[lindex $inst 3]} {
    set key [expr {$key ^ 0x04000000}]
  }
  return $key
}

proc getPrimarySortKeyIndex {} {
  global vsd sortKeyStringToIntMap
  return $sortKeyStringToIntMap($vsd(def:sortbytype))
}

proc primSetSecondarySortKey {newIdx} {
  global vsd sortKeyIntToStringMap
  if {[info exists vsd(secondarySortKeyIdx)]} {
    set oldIdx $vsd(secondarySortKeyIdx) 
    set oldString $vsd(secondarySortKeyString)
    set oldW $vsd(instanceList).hdrBut$oldString
    if {[winfo exists $oldW]} {
      # change italic font to back to bold font
      $oldW configure -font boldMainWindowFont
    }
  } else {
    set oldString undefined
  }

  set vsd(secondarySortKeyIdx) $newIdx
  set newString $sortKeyIntToStringMap($newIdx)
  set vsd(secondarySortKeyString) $newString
  if {[info exists vsd(instanceList)]} {
    $vsd(instanceList).hdrBut$newString configure -image "" -font italicMainWindowFont
  }
}

# set the secondary sort key to be the sort key with the
# highest precedence that is NOT the primary sort key.
proc getDefaultSecondarySortKey {} {
  global vsd
  set primarySortIdx [getPrimarySortKeyIndex]

  foreach idx $vsd(defaultSortOrder) {
    if {$idx != $primarySortIdx} {
      return $idx
    }
  }
  # should never reach here!
  error "primFindDefaultSecondarySortKey: no default secondary sort key found"
  return -1
}

proc setDefaultSecondarySortKey {} {
  primSetSecondarySortKey [getDefaultSecondarySortKey]
}

proc addSortKeysToList {instlist} {
  global vsd sortkeyIntToProcNameMap
  set reslist {}
  set primarySortIdx [getPrimarySortKeyIndex] 
  set sortProcs [list $sortkeyIntToProcNameMap($primarySortIdx)]
  set secondarySortIdx $vsd(secondarySortKeyIdx)
  if {$secondarySortIdx != $primarySortIdx} {
    lappend sortProcs $sortkeyIntToProcNameMap($secondarySortIdx)
  }
  foreach procidx $vsd(defaultSortOrder) {
    if {$procidx != $primarySortIdx && $procidx != $secondarySortIdx} {
      lappend sortProcs $sortkeyIntToProcNameMap($procidx)
    }
  }

  foreach inst $instlist {
    set aKey ""
    foreach sortproc $sortProcs {
      append aKey [$sortproc $inst]
      append aKey "_"
    }
    lappend inst $aKey
    lappend reslist $inst
  }
  return $reslist
}

proc destroyProgress {w} {
  global vsd
  
  catch {focus $vsd(progress_oldFocus)}
  unset vsd(progress_oldFocus)
  destroy $w
  if {$vsd(progress_oldGrab) != ""} {
    if {$vsd(progress_grabStatus) == "global"} {
      grab -global $vsd(progress_oldGrab)
    } else {
      grab $vsd(progress_oldGrab)
    }
    unset vsd(progress_oldGrab)
    unset vsd(progress_grabStatus)
  }
  update
}

proc showProgress {percentCompleted} {
  global vsd
  set parent $vsd(w)
  set w "$parent.progress"
  set exists [winfo exists $w]

  # Fix 43750 
  # Always clean up and get out if progress == 100%
  #
  if {$percentCompleted == 100} {
    if {$exists} {
      destroyProgress $w
    }
    if {$vsd(progressCanceled)} {
      return 0
    }
    return 1
  }

  if {$exists} {
    update
    if {$vsd(progressCanceled)} {
      destroyProgress $w
      return 0
    }
  } else {
    set vsd(progressCanceled) 0
    toplevel $w -class Dialog
    wm title $w "Loading Data File"
    wm iconname $w Dialog
    wm protocol $w WM_DELETE_WINDOW { }
    wm transient $w $parent

    frame $w.bot -relief raised -bd 1
    pack $w.bot -side bottom -fill both
    frame $w.top -relief raised -bd 1
    pack $w.top -side top -fill both -expand 1

    label $w.top.title -text "Load in progress ...."
    pack $w.top.title -side top -expand 1 -fill x -padx 3m -pady 3m
    if {$vsd(hasMeter)} {
      tixMeter $w.msg -value 0 -text "0%"
    } else {
      label $w.msg -justify center -anchor center
    }
    pack $w.msg -in $w.top -side bottom -expand 1 -fill both -padx 3m -pady 3m -anchor c
    tixButtonBox $w.bot.box
    $w.bot.box add cancel -text "Interrupt" \
      -command {set vsd(progressCanceled) 1}
    pack $w.bot.box -fill both -expand 1
    $vsd(balloon) bind $w.bot.box -msg "Interrupts can be continued later by doing a \"File|Update\""

    regexp {(.*x.*)(\+.*\+.*)} [wm geometry $parent] junk parentSize parentPosition
    wm geometry $w $parentPosition

    set vsd(progress_oldFocus) [focus]
    set vsd(progress_oldGrab) [grab current $w]
    if {$vsd(progress_oldGrab) != ""} {
      set vsd(progress_grabStatus) [grab status $vsd(progress_oldGrab)]
    }
    setMasterBgColorForWindow $w

    # Next line fixes 45048, but perhaps not on Windows.
    tkwait visibility $w
    grab $w
    focus $w
  }
  if {$vsd(hasMeter)} {
    $w.msg config -value [expr {double($percentCompleted) / 100}] \
      -text "$percentCompleted%"
  } else {
    $w.msg configure -text "loaded $percentCompleted%"
  }
  update
  return 1
}

proc getLastDataFileName {} {
  global vsd
  return [getFullFileName $vsd(lastdatafile)]
}

proc updateFileTrimMenuItems {finfo} {
  global vsd
  
  set firstTS [lindex $finfo 9]
  set trimLeftTS [lindex $finfo 10]
  set trimRightTS [lindex $finfo 11]
  #    puts "updateFileTrimMenuItems: firstTS=$firstTS trimLeftTS=$trimLeftTS trimRightTS=$trimRightTS"
  if {$firstTS != $trimLeftTS} {
    $vsd(w:filemenu) entryconfigure "Untrim Left" -state normal
  } else {
    $vsd(w:filemenu) entryconfigure "Untrim Left" -state disabled
  }
  if {$trimRightTS != -1} {
    $vsd(w:filemenu) entryconfigure "Untrim Right" -state normal
  } else {
    $vsd(w:filemenu) entryconfigure "Untrim Right" -state disabled
  }
  
}

proc setFileListValue {} {
  global vsd
  $vsd(w:filelist) configure -disablecallback true
  $vsd(w:filelist) configure -value $vsd(lastdatafile)
  $vsd(w:filelist) configure -disablecallback false
}

proc changeFile {fd} {
  global vsd isWindows
  set vsd(autoUpdate) $vsd(autoUpdate:$fd)
  if {! $isWindows} {
    set vsd(autoAppend) $vsd(autoAppend:$fd)
  }
  if {$vsd(updateok:$fd)} {
    $vsd(w:filemenu) entryconfigure "Update" -state normal
    $vsd(w:filemenu) entryconfigure "Auto Update" -state normal
    if {! $isWindows} {
      $vsd(w:filemenu) entryconfigure "Auto Append Next File" -state normal
    }
  } else {
    $vsd(w:filemenu) entryconfigure "Update" -state disabled
    $vsd(w:filemenu) entryconfigure "Auto Update" -state disabled
    if {! $isWindows} {
      $vsd(w:filemenu) entryconfigure "Auto Append Next File" -state disabled
    }
  }
  
  set statsrcInfo [$fd -info]
  set vsd(file:isEnabled) [lindex $statsrcInfo 7]

  updateFileTrimMenuItems $statsrcInfo
  
  # If the file info window is up then update it
  # BUG this was deadcoded because fileInfo currently always steal the focus
  #     if [winfo exists .vsdfileinfo] {
  # 	fileInfo
  #     }
  set oldval $vsd(lastdatafile)
  set vsd(lastdatafile) [addFileListMap $fd]
  setFileListValue
}

proc getAllCountersKey {type pattern} {
  return "$type,$pattern"
}

proc getCtrAliasesKey {cacheName counter} {
  return "$cacheName,$counter"
}


# Called when a data file is loaded
proc buildStatAliasForTypeAndPattern {type counters cachePattern verPatterns} {
  global vsd
  # Get the dictionary of aliases for this cachePattern
  # `vsd(cacheIdAliasDict) at: GcReclaim*`
  # Answers a dictionary like this:
  #   { SessionStat17 ->  Foo}
  #   { SessionStat18 ->  Bar}

  set dotrace 0
  if {$dotrace} {
    puts "\[trace\]Begin tracing pattern $cachePattern verPatterns $verPatterns"
  }

  foreach aVerPattern $verPatterns {
    if {[dict exists $vsd(cacheIdAliasDict) $cachePattern $aVerPattern]} {
      set dictOfAliases [dict get $vsd(cacheIdAliasDict) $cachePattern $aVerPattern]
      # set dictOfAliases [dict get $vsd(cacheIdAliasDict) $cachePattern]
      # For each counter and alias pair, build the alias counter
      # name and replace the entry in the list of counters
      dict for { thisCounter thisAlias } $dictOfAliases {
	set index [lsearch -sorted $counters $thisCounter]
	if {$index != -1} {
	  # replace the old counter name with the alias
	  # Since new counter names is "counterName (alias)", sort order is unchanged.
	  set newCounter [getStatNameForCounterAndAlias $thisCounter $thisAlias]
	  set counters [lreplace $counters $index $index $newCounter]
	  if {$dotrace} {
	    puts "\[trace\] Replaced $thisCounter with $newCounter at index $index"
	  }
	} else {
	  if {$dotrace} {
	    puts "\[trace\] $thisCounter was not found"
	  }
	}
      }
    } else {
      if {$dotrace} {
	puts "\[trace\]: aliases for cache pattern $cachePattern and version pattern $aVerPattern do not exist"
      }
    }
  }
  
  # Now add a new key in the vsd(allCounters) dictionary
  set key [getAllCountersKey $type $cachePattern]
  if {[dict exists $vsd(allCounters) $key]} {
    bgerror "The key $key should not already exist in vsd\(allCounters\)!"
  }

  # install the list of counters and aliases
  dict set vsd(allCounters) $key $counters
}

proc dumpCachePatterns {} {
  global vsd
  puts "Dump of vsd(listOfCachePatterns):"
  if {![llength $vsd(listOfCachePatterns)]} {
    puts "  <empty>"
  } else {
    foreach p $vsd(listOfCachePatterns) {
      puts "  \"$p\""
    }
  }
}

proc dumpCacheIdAliasDict {} {
  global vsd
  puts "Dump of cacheIdAliasDict:"
  if {![dict size $vsd(cacheIdAliasDict)]} {
    puts "  <empty>"
  } else {
    dict for {cPattern vDict} $vsd(cacheIdAliasDict) {
      set versions [dict keys $vDict]
      puts "  $cPattern -> $versions"
    }
  }
}

proc dumpCtrAliases {} {
  global ctrAliases
  puts "Dump of ctrAliases:"
  if {![array size ctrAliases]} {
    puts "  <empty>"
  } else {
    foreach key [array names ctrAliases] {
      puts "  $key -> [dict keys $ctrAliases($key)]"
    }
  }
}

proc dumpAllCtrsKeys {} {
  global vsd
  puts "Dump vsd(allCounters) keys:"
  if {![dict size $vsd(allCounters)]} {
    puts "  <empty>"
  } else {
    foreach key [lsort -dictionary [dict keys $vsd(allCounters)]] {
      puts "  $key"
    }
  }
}

proc dumpAliasTemplateTypes {} {
  global ctrAliasTemplateTypes
  puts "Dump of ctrAliasTemplateTypes"
  if {![array size ctrAliasTemplateTypes]} {
    puts "  <empty>"
  } else {
    foreach inst [array names ctrAliasTemplateTypes] {
      puts "  $inst"
    }
  }
}

proc dumpInstNameToProcKindDict {} {
  global vsd
  puts "Dump of vsd(instNameToProcKindDict)"
  if {![dict size $vsd(instNameToProcKindDict)]} {
    puts "  <empty>"
  } else {
    dict for {key val} $vsd(instNameToProcKindDict) {
      puts "  $key -> $val"
    }
  }
}

proc setInternalCtrAliasTemplateType {type} {
  global ctrAliasTemplateTypes
  set ctrAliasTemplateTypes($type) 1
}

proc findVerPatternForCachePattern {cachePattern listOfVerPatterns} {
  global vsd

  set results {}
  foreach aVerPattern $listOfVerPatterns {
    if {[dict exists $vsd(cacheIdAliasDict) $cachePattern $aVerPattern]} {
      lappend results $aVerPattern
    }
  }
  return [lindex $results 0]
}

proc removeFromInstancesWithAliasTemplates {instName} {
  global vsd
  if {![info exists vsd(instancesWithAliasTemplates:$instName)]} {
    bgerror "instance $instName not found in vsd(instancesWithAliasTemplates)"
  } else {
    unset vsd(instancesWithAliasTemplates:$instName)
  }
}

proc addToInstancesWithAliasTemplates {instName template} {
  global vsd
  if {[info exists vsd(instancesWithAliasTemplates:$instName)]} {
    bgerror "instance $instName already exists in vsd(instancesWithAliasTemplates)"
  } else {
    set vsd(instancesWithAliasTemplates:$instName) $template
  }
}

# templateName ~= MarkForCollection
# templateName is an element of ctrAliasTemplateTypes
proc applyAliasTemplateForType {templateName} {
  global vsd fileIdToVersionMap
  set dotrace 0
  set cacheNameList {}
  set verList {}
  set appliedAlias 0
  foreach inst [getSelectedInstances] {
    set entryData [$vsd(instanceList) info data $inst]
    set instName [lindex $entryData 0]
    set procKind [lindex $entryData 1] ; # Gem, Stn, etc
    set newValue [getAllCountersKey $procKind $templateName]
    if {$dotrace} {
      # 47721 - ensure key exists. It won't if process started after file was loaded
      set oldValue unknown
      if {[dict exists $vsd(instNameToProcKindDict) $instName]} {
	set oldValue [dict get $vsd(instNameToProcKindDict) $instName]
      }
#      puts "vsd(instNameToProcKindDict): $instName -> $newValue (was $oldValue)"
    }
    dict set vsd(instNameToProcKindDict) $instName $newValue
    addToInstancesWithAliasTemplates $instName $templateName
    set appliedAlias 1
  }
  
  if {$appliedAlias} {
    updateApplyAliasMenuState disabled
    updateRemoveAliasesMenuState normal
    fillCounters 1 [getSelectedInstances]
  }
}

proc buildInstNameToProcKindTableForInst {anInst} {
  global vsd

  set instName [lindex $anInst 0]
  if {![dict exists $vsd(instNameToProcKindDict) $instName]} {
    set cacheName [lindex $instName 0]
    set pattern [patternForCacheName $cacheName]
    set type [lindex $anInst 1]
    if {[llength $pattern]} {
      set value [getAllCountersKey $type $pattern]
    } else {
      set value $type
    }
    dict set vsd(instNameToProcKindDict) $instName $value
  }
}

proc buildInstNameToProcKindTableForType {type} {
  set allGemInstances [sl_stat -enabledobjinstances [list $type]]
  foreach gemInst $allGemInstances {
    buildInstNameToProcKindTableForInst $gemInst
  }
}

proc removeAliases {} {
  global vsd
  set procs [getSelectedInstances]
  foreach aProc $procs {
    set entryData [$vsd(instanceList) info data $aProc]
    set instName [lindex $entryData 0]
    set procKind [lindex $entryData 1] ; # Gem, Stn, etc
    if {[info exists vsd(instancesWithAliasTemplates:$instName)]} {
      removeFromInstancesWithAliasTemplates $instName
    }
    dict set vsd(instNameToProcKindDict) $instName $procKind
  }

  updateApplyAliasMenuState normal
  updateRemoveAliasesMenuState disabled
  fillCounters 1 $procs
}

################################################################################
# buildStatAliasesForType
#
# Type is usually "Gem" or "Gem2" or "Gem.2" or something.
# Each statmon file loaded will share the same name (Gem) *unless*
# a statmon file with a different number of gem counters than a
# previously loaded file. In that case, a new type (Gem2) is
# created.
#
# Counters is list of counters for that type without aliases
# Scan the list of alias patterns and build a list of counters
# with the correct alias names for each pattern.
# Add result to the vsd(allCounters) dictionary under the ke
# "$type.$pattern"
################################################################################
proc buildStatAliasesForType {type counters verPatterns} {
  global vsd
  foreach cachePattern $vsd(listOfCachePatterns) {
    buildStatAliasForTypeAndPattern $type $counters $cachePattern $verPatterns
  }
}

################################################################################
# allCountersForType
#
# Get the (pre-)sorted list of counters (including aliases) for the given type.
# Type is process type like Gem, Gem.2, etc
################################################################################
proc allCountersForType {type} {
  global vsd
  set result {}
  if {[dict exists $vsd(allCounters) $type]} {
    set result [dict get $vsd(allCounters) $type]
  }
  return $result
}

################################################################################
# allCountersForAllTypes
#
# Get the list of counters (including aliases) for all types without duplicates.
# To do this, we take the set union of all the lists of counters and sort the
# resulting list.
################################################################################
proc allCountersForAllTypes {} {
  global vsd
  return [lsort -dictionary [::struct::set union {*}[dict values $vsd(allCounters)]]]
}

################################################################################
# getCountersForTypes
#
# Get the list of counters (including aliases) for all types in listOfTypes
# without duplicates.
#
# If listOfTypes is empty, get all counters for all types without duplicates.
# If listOfTypes has one entry, get all counters for that type without duplicates.
# If listOfTypes has multiple entries, get all counters that are common to all
# types in the list.  We use set intersection to compute the resulting list.

# To do this, we take the set union of all the lists of counters and sort the
# resulting list.
################################################################################
proc getCountersForTypes {listOfTypes} {
  global vsd

  set numTypes [llength $listOfTypes]
  if {$numTypes == 0} {
    # empty list means return all counters
    # allCountersForAllTypes does the sort
    return [allCountersForAllTypes]
  } elseif {$numTypes == 1} {
    # fetch sorted list of counters for 1 type
    set result [allCountersForType [lindex $listOfTypes 0]]
    return $result
  } else {
    # more than 1 type if we get here
    # build a list of lists, then compute the intersection, then sort the result
    set listOfListsOfCounters {}
    foreach type $listOfTypes {
      lappend listOfListsOfCounters [allCountersForType $type]
    }
    return [lsort -dictionary [::struct::set intersect {*}$listOfListsOfCounters]]
  }
}

proc setDataFile {fname {df 0}} {
  global vsd fileIdToVersionMap isWindows
  
  if {$fname != {} && [string equal "relative" [file pathtype $fname]]} {
    set fname [file join [pwd] $fname]
  }
  set lastDfName [getLastDataFileName]
  if {$fname != {} && $fname != $lastDfName} {
    replayLog "setDataFile $fname"
    if {$vsd(appendMode) && $vsd(statsrc) != {}} {
      if {[fileNameWasAppended $fname]} {
	puts "Rejecting already appended file $fname"
	set vsd(appendMode) 0
	return
      }
      if {$df == 0} {
	if {$vsd(statsrc) == {}} {
	  # should never happen
	  showError "Cannot first data file (nothing to append to)"
	  return
	} else {
	  set df $vsd(statsrc)
	}
      }
      if [catch {$df -append $fname} result] {
	set msg "Could not append data file \'$fname\' because: "
	append msg $result
	showError $msg
      } else {
	setFileWasAppended $df
	addAppendedFileName $fname
	# NYI: Its not clear how much needs to be done after an append
	setInstanceList
	updateGraphWidgetState
	setFileListValue
	setLastUpdateTimeForDataFile $df
      }
      set vsd(appendMode) 0
      return
    }
    if [catch {sl_create -file $fname showProgress} result] {
      set msg "Could not load data file \'$fname\' because: "
      append msg $result
      showError $msg
      # set the w:filelist back to the lastdatfile loaded
      setFileListValue
      return
    }

    set fileInfo [$result -info]
    
    if {![dataFileIdExists $result]} {
      # only do this stuff the first time
      # 50375 - auto set the correct time shift 
      set tsHrs [lindex $fileInfo 15]
      if {$tsHrs == 25} {
        # 25 means we could not determine the timezone offset
	set vsd(time_shift_hours) 0
	set msg "Could not determine correct timezone offset for this file."
	setStatus $msg
      } else {
	if {$vsd(time_shift_hours) != $tsHrs} {
	  set vsd(time_shift_hours) $tsHrs
	  set msg "Setting timezone offset to $tsHrs hours."
	  setStatus $msg
	}
      }
      
      set vsd(noUpdateCount:$result) 0
      addUniqueFileName $result $fileInfo
      # next line must come after addUniqueFileName
      setLastUpdateTimeForDataFile $result
      set fileVersion [string trimright [lindex [lindex $fileInfo 3] 0] ","]
      set fileIdToVersionMap($result) $fileVersion
      set verPatterns [patternsForVersion $fileVersion]
      
      # create vsd(allCounters) if this is the first data file
      
      foreach procKind [lindex $fileInfo 8] {
	if {![dict exists $vsd(allCounters) $procKind]} {
	  set counters [lsort -dictionary [sl_stat -objctrs $procKind]]
	  dict set vsd(allCounters) $procKind $counters
	  if {[procKindHasAliases $procKind]} {
	    # This one is for gem or stone.  Setup aliases.
	    buildStatAliasesForType $procKind $counters $verPatterns
	  }
#	  generateTypeHelp $procKind
	}
	buildInstNameToProcKindTableForType $procKind
      }
    }
    
    if {$vsd(singleFileMode)} {
      if {$vsd(statsrc) != {}} {
	$vsd(statsrc) -disable
      }
      $result -enable
    }
    
    updateStatSrc $result $fileInfo
    saveContentsOfDir [getUniqueFileDirName $result]
    fillCounters 1 {}
    updateCounterList
    createRollingTimeTrimmerValuesForFile $result

    if {![info exists vsd(updateok:$result)]} {
      set vsd(updateok:$result) 1
      set vsd(autoUpdate:$result) 0
      set vsd(autoAppend:$result) 0
    }

    changeFile $result

    if {[$vsd(w:filemenu) entrycget "Info..." -state] == "disabled"} {
      # We have a file loaded so enable the 'file' menu items
      $vsd(w:filemenu) entryconfigure "Info..." -state normal
      # $vsd(w:filemenu) entryconfigure "Absolute TimeStamps" -state normal
      $vsd(w:filemenu) entryconfigure "Enabled" -state normal
      $vsd(w:filemenu) entryconfigure  $vsd(rollingTimeWindowMenuText) -state normal
      if [wizardMode] {
	$vsd(w:filemenu) entryconfigure "Dump" -state normal
      }
    }

    # 45853 - do not put selected file at top of the list
    set idx [lsearch -exact $vsd(filelist) $vsd(lastdatafile)]
    if {$idx == -1} {
      # file is new, add to the end of the list.
      $vsd(w:filelist) insert end $vsd(lastdatafile)
      set lb [$vsd(w:filelist) subwidget listbox]
      # 47660 - adjust listbox height
      set newHeight [$lb index end]
      if {$isWindows} {
	# tixComboBox needs extra help on Windoze
	incr newHeight
      }
      set newHeight [expr {[$lb index end] + 1}]
      $lb configure -height $newHeight
      set vsd(filelist) [linsert $vsd(filelist) end $vsd(lastdatafile)]
    }
    setInstanceList
    updateGraphWidgetState
  } else {
    # set the w:filelist back to the lastdatfile loaded
    setFileListValue
  }
  set vsd(appendMode) 0
  return
}

proc basicGetVsdDataDir {} {
  global vsd isWindows
  # If user selected a directory, use that.
  if {$vsd(cwd) != {}} {
    return $vsd(cwd)
  } else {
    set lastfn [getLastDataFileName]
    if {$lastfn != {}} {
      set dir [file dirname $lastfn]
      if {$isWindows} {
	regsub -all "/" $dir "\\" dir
      }
    } else {
      # 45838 - remember use directory location if valid
      if {$vsd(lastdatadir) != {} && [file isdirectory $vsd(lastdatadir)]} {
	set dir $vsd(lastdatadir)
      } else {
	set dir "." 
      }
    }
  }
  return $dir
}

# fix 50104, chdir to $HOME if last directory is gone
proc getVsdDataDir {} {
  set dir [basicGetVsdDataDir]
  if {! [file exists $dir]} {
    showError "Directory '$dir' does not exist"
    # getting desparate, use $HOME
    set dir [glob ~]
  }
  return $dir
}

# 45335 - Always use tk_getOpenFile so we can select multiple files.
proc browseDataFile {{appendMode 0}} {
  global vsd isWindows
  set dir [getVsdDataDir]
  # 45196 - Add lz4 support
  set types {
    {"All Statistic Data Files" {.gfs .out*}}
    {"All Compressed files" {.gz .lz4}}
    {"Uncompressed StatMonitor data files" {.out}}
    {"Uncompressed GBS archive files" {.gfs}}
    {"Compressed StatMonitor data files" {.out.*z*}}
    {"All files" {*}}
  }

  # 50104: cwd must be valid otherwise tk_getOpenFile blows up
  if {! [file exists [pwd]]} {
    cd $dir
  }
  
  if {$isWindows} {
    set fname [tk_getOpenFile -title "Open StatMonitor File" \
		 -parent $vsd(w) \
		 -initialdir $dir \
		 -multiple true \
		 -filetypes $types \
	         -typevariable vsd(fileTypePattern)] ;# 47666
  } else {
    set fname [tk_getOpenFile -title "Open StatMonitor File" \
		 -parent $vsd(w) \
		 -initialdir $dir \
		 -multiple true \
		 -background $vsd(currentBgColor) \
		 -filetypes $types \
                 -typevariable vsd(fileTypePattern)] ;# 47666
  }
  if {$fname != {}} {
    foreach e $fname {
      if {$appendMode != 0 && $vsd(statsrc) != {} } {
	set vsd(appendMode) 1
      }
      setDataFile $e
      # 45838 - remember last directory location
      set vsd(lastdatadir) [file dirname $e]
    }
  }
}

proc browseDataFileAppend {} {
  browseDataFile 1
}

# loadBrowsedDataFile deleted

proc chooseDataFile {fname} {
  if {$fname == "Browse For File..."} {
    setFileListValue
    # Bring up a dialog that lets the user browse for a file
    browseDataFile
  } else {
    setDataFile [getFullFileName $fname]
  }
}

proc printGraph {graph name} {
  global vsd
  myBusyOn $graph
  if [catch {$graph postscript output $name \
	       -landscape true -decorations false \
	       -colormode grey -padx .25i -pady .25i} result] {
    showError $result
  } elseif [catch {eval exec $vsd(exec:lpr) $name} result] {
    showError "$vsd(exec:lpr) $name failed: $result"
  } else {
    catch {file delete -force $name}
  }
  myBusyOff $graph
}

proc snapshotGraph {graph name} {
  global vsd isWindows
  set types {
    {"All gif files" {*.gif}}
    {"All files" {*}}
  }

  # fix 48623

  if {$isWindows} {
    set tmpFn [tk_getSaveFile -title "Save Chart Snapshot to File" \
		 -parent $vsd(w) \
		 -initialdir $vsd(snapshotDirName) \
		 -initialfile $vsd(snapshotFileName) \
		 -filetypes $types \
	         -typevariable vsd(fileSaveTypePattern)] ;# 47666
  } else {
   set tmpFn [tk_getSaveFile -title "Save Chart Snapshot to File" \
		 -parent $vsd(w) \
		 -initialdir $vsd(snapshotDirName) \
		 -initialfile $vsd(snapshotFileName) \
		 -background $vsd(currentBgColor) \
		 -filetypes $types \
                 -typevariable vsd(fileSaveTypePattern)] ;# 47666
  }

  if {$tmpFn == ""} {
    # user clicked cancel, bail out now
    return
  }

  set tmpFn [file normalize $tmpFn]
  set vsd(snapshotFileName) [file tail $tmpFn]
  set vsd(snapshotDirName) [file dirname $tmpFn]
  myBusyOn $graph
  set photo [image create photo -format gif]
  $graph snap $photo
  # fix 48423 - catch exceptions like "too many colors" and
  #             "permission denied"
  if {[catch {$photo write $tmpFn -format gif} out]} {
    showError [string cat "Error writing snapshot to gif file: " $out]
  }
  image delete $photo
  myBusyOff $graph
}

# 46244 - Update to strip all trailing zeros to the right of the
#         decimal, and the decimal itself if all digits to the right
#         were zeros.
proc stripFloat {v} {
  return [string trimright [string trimright $v 0] .]
}

proc updateAllLineTrimMenuItems {} {
  global bltActiveEntry
  global vsd

  foreach g [array names bltActiveEntry] {
    set e $bltActiveEntry($g)
    if {$e != -1} {
      set mf "[winfo parent $g].mf"
      set linfo [$vsd(ed:$e:$g) -info]
      updateLineTrimMenuItems $mf.line.m [lindex $linfo 7]
    }
  }
}

proc updateLineTrimMenuItems {m datafiles} {
  set isTrimmedLeft 0
  set isTrimmedRight 0
  set isRollingTrimmedLeft 0
  foreach dfid $datafiles {
    set dfinfo [$dfid -info]
    set firstTS [lindex $dfinfo 9]
    set trimLeftTS [lindex $dfinfo 10]
    set trimRightTS [lindex $dfinfo 11]
    set rollingLeftTrimSecs [lindex $dfinfo 13]

    if {$rollingLeftTrimSecs > 0} {
      set isRollingTrimmedLeft 1
      break
    }
    
    if {$firstTS != $trimLeftTS} {
      set isTrimmedLeft 1
    }
    if {$trimRightTS != -1} {
      set isTrimmedRight 1
    }
    if {$isTrimmedLeft && $isTrimmedRight} {
      break;
    }
  }
  
  if {$isRollingTrimmedLeft} {
    $m entryconfigure "Untrim Left" -state disabled
    $m entryconfigure "Untrim Right" -state disabled
    $m entryconfigure "Trim Left" -state disabled
    $m entryconfigure "Trim Right" -state disabled
  } else {
    $m entryconfigure "Trim Left" -state normal
    $m entryconfigure "Trim Right" -state normal
    if {$isTrimmedLeft} {
      $m entryconfigure "Untrim Left" -state normal
    } else {
      $m entryconfigure "Untrim Left" -state disabled
    }
    if {$isTrimmedRight} {
      $m entryconfigure "Untrim Right" -state normal
    } else {
      $m entryconfigure "Untrim Right" -state disabled
    }
  }
}

proc showLineInfo {graph element} {
  global vsd
  set mf "[winfo parent $graph].mf"
  if {$element == {}} {
    $mf.line configure -state disabled
    set min ""
    set max ""
    set filter none
    set symbol none
    set style linear
    set counter ""
    hideMarker $graph triStart
    hideMarker $graph triEnd
    set isDerived 0
  } else {
    $mf.line configure -state normal
    if {[$graph element cget $element -mapy] == "y"} {
      set vsd($graph:graphonleft) 1
    } else {
      set vsd($graph:graphonleft) 0
    }
    set edata [$vsd(ed:$element:$graph) -info]
    set vsd($graph:normalized) [lindex $edata 11]
    set instName [lindex $edata 0]
    set fileId [lindex $edata 7]
    set counter [getCtrAlias [lindex $edata 1] $instName $fileId]
    set yvname [lindex $edata 3]
    set scale [lindex $edata 4]
    #set filter [lindex $edata 5]
    set filter [getCommonFilter $graph [lindex $edata 5]]
    #	set obj [lindex $edata 6]
    #	set dfids [lindex $edata 7]
    if {[lindex $edata 9] == {}} {
      set isDerived 0
    } else {
      set isDerived 1
    }
    set symbol [$graph element cget $element -symbol]
    set style [$graph element cget $element -smooth]
    global $yvname
    set min [set ${yvname}(min)]
    if {$min == {}} {
      set min 0.0
    }
    set max [set ${yvname}(max)]
    if {$max == {}} {
      set max 0.0
    }
    if {$scale != 1} {
      # Need to unscale min and max
      set min [expr {$min / $scale}]
      set max [expr {$max / $scale}]
    }
    updateLineTrimMenuItems $mf.line.m [lindex $edata 7]
    showCounterInfo $counter
  }
  set vsd($graph:linesym) $symbol
  set vsd($graph:linestyle) $style

  set f [$mf.min subwidget frame]
  $f.l configure -text [formatFloat $min]

  set f [$mf.max subwidget frame]
  $f.l configure -text [formatFloat $max]

  $mf.linef.counter configure -state normal
  $mf.linef.counter configure -disablecallback true
  # Note that the contents of the counter listbox is not computed
  # until the user drops it down. At that time chartComputeCounters
  # is called.
  if {$isDerived} {
    $mf.linef.counter configure -value [getCtrAlias $counter $instName $fileId]
    $mf.linef.counter configure -state disabled
  } else {
    $mf.linef.counter configure -value $counter
    $mf.linef.counter configure -disablecallback false
  }
  if {$filter != {}} {
    $mf.linef.filtertype configure -state normal
    $mf.linef.filtertype configure -disablecallback true
    $mf.linef.filtertype configure -value $filter
    $mf.linef.filtertype configure -disablecallback false
  } else {
    $mf.linef.filtertype configure -state disabled
  }
  if {$element == {}} {
    $mf.linef.counter configure -state disabled
    $mf.linef.filtertype configure -state disabled
    $mf.linef.opMenu configure -state disabled
    $mf.linef.const configure -state disabled
  } else {
    $mf.linef.opMenu configure -state normal
    $mf.linef.const configure -state normal
    changeConstOp $graph {}
  }

  set f [$mf.curx subwidget frame]
  $f.l configure -text ""
  set f [$mf.cury subwidget frame]
  $f.l configure -text ""

  # 44955 - restore right click to previous setting 
  bind $graph <ButtonPress-3> {
    ChartHandleRightClick %W
  }
}

proc lineIncrConst {newVal} {
  global vsd
  switch -exact $vsd(constOp) {
    "divide" -
    "multiply" -
    "scale" {
      return [expr {$newVal * 2.0}]
    }
    "add" -
    "subtract" {
      return [expr {$newVal + 1}]
    }
  }
}

proc lineDecrConst  {newVal} {
  global vsd
  switch -exact $vsd(constOp) {
    "divide" -
    "multiply" -
    "scale" {
      return [expr {$newVal / 2.0}]
    }
    "add" -
    "subtract" {
      return [expr {$newVal - 1}]
    }
  }
}

proc lineValidateConst {g newVal} {
  global vsd
  if [catch {expr {double($newVal)}} val] {
    setStatus "The string \"$newVal\" is not a valid number."
    bell
    return 1
  }
  switch -exact $vsd(constOp) {
    "scale" -
    "multiply" {
      if {$val == 0} {
	setStatus "Can't multiply by zero."
	bell
	return 1
      }
    }
    "divide" {
      if {$val == 0} {
	setStatus "Can't divide by zero."
	bell
	return 1
      }
    }
    "add" {
      set e [getSelectedLine $g]
      if {$e != {}} {
	set l $vsd(ed:$e:$g)
	set linfo [$l -info]
	set normalized [lindex $linfo 11]
	set offset [expr {- [lindex $linfo 12]}]
	if {$normalized && $offset != $val} {
	  # can't change adder/subtractor if line is normalized
	  setStatus "Can't add to normalized line."
	  bell
	  return $offset
	}
      }
    }
    "subtract" {
      set e [getSelectedLine $g]
      if {$e != {}} {
	set l $vsd(ed:$e:$g)
	set linfo [$l -info]
	set normalized [lindex $linfo 11]
	set offset [lindex $linfo 12]
	if {$normalized && $offset != $val} {
	  # can't change subtractor if line is normalized
	  setStatus "Can't subtract from normalized line."
	  bell
	  return $offset
	}
      }
    }
  }
  return $newVal
}

proc lineChangeConst {g newVal} {
  global vsd
  switch -exact $vsd(constOp) {
    "scale" {
      lineChangeScale $g $newVal
    }
    "divide" {
      lineDivideConst $g $newVal
    }
    "multiply" {
      lineDivideConst $g [expr {1.0 / $newVal}]
    }
    "add" {
      lineAddConst $g $newVal
    }
    "subtract" {
      lineAddConst $g [expr {- $newVal}]
    }
  }
}

proc changeConstOp {g constOp} {
  global vsd
  set e [getSelectedLine $g]
  if {$e != {}} {
    set mf "[winfo parent $g].mf"
    set l $vsd(ed:$e:$g)
    set linfo [$l -info]
    set scale [lindex $linfo 4]
    set normalized [lindex $linfo 11]
    set offset [lindex $linfo 12]
    set divider [lindex $linfo 13]
    if {$constOp == {}} {
      # pick the best one
      set hasScale [expr {$scale != 1.0}]
      set hasOffset [expr {$offset != 0.0}]
      set hasDivider [expr {$divider != 1.0}]
      # first check to see if constOp already set to something non-default for this line
      switch -exact $vsd(constOp) {
	"scale" {
	  if {$hasScale} {
	    set constOp $vsd(constOp)
	  }
	}
	"multiply" -
	"divide" {
	  if {$hasDivider} {
	    set constOp $vsd(constOp)
	  }
	}
	"subtract" -
	"add" {
	  if {$hasOffset} {
	    set constOp $vsd(constOp)
	  }
	}
      }
      if {$constOp == {}} {
	# still have not picked one so look for one for
	# which this line has a non-default value
	if {$hasDivider} {
	  # make it "divide" or "multiply"
	  if {[expr {abs($divider)}] >= 1.0} {
	    set vsd(constOp) "divide"
	  } else {
	    set vsd(constOp) "multiply"
	  }
	} elseif {$hasOffset} {
	  # make it "add" or "subtract"
	  if {$offset < 0} {
	    set vsd(constOp) "add"
	  } else {
	    set vsd(constOp) "subtract"
	  }
	} elseif {$hasScale} {
	  set vsd(constOp) "scale"
	}
	set constOp $vsd(constOp)
      }
    }
    $mf.linef.const configure -disablecallback true
    switch -exact $constOp {
      "scale" {
	$mf.linef.const configure -value $scale
      }
      "divide" {
	$mf.linef.const configure -value $divider
      }
      "multiply" {
	$mf.linef.const configure -value [expr {1.0 / $divider}]
      }
      "add" {
	if {$offset != 0} {
	  $mf.linef.const configure -value [expr {- $offset}]
	} else {
	  $mf.linef.const configure -value $offset
	}
      }
      "subtract" {
	$mf.linef.const configure -value $offset
      }
    }
    $mf.linef.const configure -disablecallback false
  }
}

proc lineAddConst {g const} {
  global vsd
  set e [getSelectedLine $g]
  if {$e != {}} {
    set l $vsd(ed:$e:$g)
    $l -adder $const
    updateLineDataDisplay $g $e $l
  }
}

proc lineDivideConst {g const} {
  global vsd
  set e [getSelectedLine $g]
  if {$e != {}} {
    set l $vsd(ed:$e:$g)
    $l -divider $const
    updateLineDataDisplay $g $e $l
  }
}

proc lineChangeScale {g newVal} {
  global vsd
  set e [getSelectedLine $g]
  if {$e != {}} {
    set l $vsd(ed:$e:$g)
    $l -scale $newVal
    updateLineDataDisplay $g $e $l
  }
}

# 44955 - copy Y value to the clipboard
proc copyYvalue {} {
  global vsd
  clipboard clear
  clipboard append $vsd(Yvalue)
}

proc setCurXY {graph elem x y} {
  global vsd
  set mf "[winfo parent $graph].mf"

  set linfo [$vsd(ed:$elem:$graph) -info]
  set scale [lindex $linfo 4]
  if {$scale != 1} {
    # Need to remove the scale so that the Y value reported will be real
    set y [expr {$y / $scale}]
  }

  set f [$mf.curx subwidget frame]
  set xstr [sl_stat -getdate [expr {wide($x)}] $vsd(time_shift_hours) $vsd($graph:xformat)]
  $f.l configure -text $xstr
  set f [$mf.cury subwidget frame]
  $f.l configure -text [formatFloat $y]

  # 44955 - Enable right click open pop-up menu to copy Y to clipboard
  set vsd(Yvalue) [formatFloat $y]
  bind $graph <ButtonPress-3> {tk_popup %W.popupMenu %X %Y}
}

proc closeAllCharts {} {
  global vsd
  foreach name [array names vsd  ct:*] {
    set w $vsd($name)
    set g $w.graph
    closeGraph $w $g
  }
}

# Fix 48517
# Create new graph windows to be the same size as the last opened graph window
# which still exists.
# Create new graph window to be the same position as the last opened graph window
# which still exists, offset X and Y by 25 pixels.
proc getPrevGraphGeomForVsdRc {} {
  global vsd
  # use geom from most recently opened graph window which still exists
  if {[llength $vsd(prevGraphWindowList)] != 0} {
    return [wm geometry [lindex $vsd(prevGraphWindowList) end]]
  }
  # no open graph windows, so use geom of last closed graph window
  if {$vsd(prevGraphWindowGeom) != "" } {
    return $vsd(prevGraphWindowGeom)
  }
  # no graphs opened. use default geom
  return "600x300+1+5"
}

proc getNewGeomFromOldGeom {oldGeom} {
  # fix 50038 - remove any +- sequence from the geometry string
  regsub -all {\+\-} $oldGeom {-} oldGeom
  lassign [split $oldGeom {+-x}] width height xloc yloc
  incr xloc 25
  incr yloc 25
  set result [format "%dx%d%+d%+d" $width $height $xloc $yloc]
  return $result
}

proc getGraphGeometry {w} {
  global vsd
  if {$vsd(prevGraphWindowList) == {}} {
    if {$vsd(prevGraphWindowGeom) == "" } {
      set result "600x300+1+5"
    } else {
#      "use geom from last graph window closed by user"
      set result $vsd(prevGraphWindowGeom)
    }
  } else {
    # most recent graph is on the end
    set graph [lindex $vsd(prevGraphWindowList) end]
    set geom [wm geometry $graph]
    set result [getNewGeomFromOldGeom $geom]
  }
  lappend vsd(prevGraphWindowList) $w
  return $result
}

proc closeGraph {w g} {
  global vsd
  global bltActiveGraph
  global bltActiveEntry

  incr vsd(graphcount) -1

  if {[string equal $vsd(longOpW) $w]} {
    cancelLongOp
  }

# 48517 - remove window about to die from list of graph windows  
  set idx [lsearch -exact $vsd(prevGraphWindowList) $w]
  if {$idx != -1} {
    set vsd(prevGraphWindowList) [lreplace $vsd(prevGraphWindowList) $idx $idx]
  }

# user is deleting the last chart, so remember the geom for next time  
  if {[llength $vsd(prevGraphWindowList)] == 0} {
    set vsd(prevGraphWindowGeom) [wm geometry $w]
  }
  update idletasks

  if {[info exists bltActiveGraph($g,alarmid)]} {
    after cancel $bltActiveGraph($g,alarmid)
    unset bltActiveGraph($g,alarmid)
    unset bltActiveGraph($g,x)
    unset bltActiveGraph($g,y)
  }

  if [info exists bltActiveEntry($g)] {
    unset bltActiveEntry($g)
  }
  if [info exists bltActiveGraph($g)] {
    unset bltActiveGraph($g)
  }
  if [info exists bltActiveGraph($g,closest)] {
    unset bltActiveGraph($g,closest)
  }
  if [info exists bltActiveGraph($g,oldSelectedLines)] {
    unset bltActiveGraph($g,oldSelectedLines)
  }

  foreach e [$g element names] {
    $vsd(ed:$e:$g) -free
    unset vsd(ed:$e:$g)
  }

  set title [wm title $w]
  unset vsd(ct:$title)
  set idx 0
  set listbox [$vsd(w:chartlist) subwidget listbox]
  foreach t [$listbox get 0 end] {
    if {[string equal $t $title]} {
      $listbox delete $idx
      if {$idx == 0} {
	set curwintitle [$listbox get 0]
	if [info exists vsd(ct:$curwintitle)] {
	  $vsd(w:chartlist) pick 0
	} else {
	  $vsd(w:chartlist) configure -disablecallback true
	  $vsd(w:chartlist) pick 0
	  $vsd(w:chartlist) configure -disablecallback false
	}
      }
      break
    }
    incr idx
  }
  destroy $w
}

proc chartFocus {w} {
  global vsd
  set title [wm title $w]
  if {![info exists vsd(ct:$title)]} {
    return
  }
  if {[string equal $vsd(ct:$title) $w]} {
    if {![string equal $vsd(curchart) $title]} {
      $vsd(w:chartlist) configure -disablecallback true
      $vsd(w:chartlist) addhistory $title
      $vsd(w:chartlist) pick 0
      $vsd(w:chartlist) configure -disablecallback false
    }
  }
}

proc getAxisTitle {g axis} {
  global vsd
  global bltActiveEntry

  if {[info exists bltActiveEntry($g)] && $bltActiveEntry($g) != -1} {
    set e $bltActiveEntry($g)
    if {[string equal $axis "xaxis"]} {
      return [getElementXAxisTitle $g $e]
    } else {
      set activeAxis [$g element cget $e -mapy]
      append activeAxis "axis"
      if {[string equal $activeAxis $axis]} {
	return [getElementYAxisTitle $g $e]
      }
    }
  }
  return $vsd($axis:defaultTitle)
}

proc chartAxisTitle {g axis} {
  global vsd
  if {$vsd($g:$axis:showtitle)} {
    set title [getAxisTitle $g $axis]
  } else {
    set title ""
  }
  $g $axis configure -title $title
}

proc chartShowCrossHairs {g} {
  global vsd
  if {$vsd($g:showcrosshairs)} {
    Blt_CrosshairsOn $g
  } else {
    Blt_CrosshairsOff $g
  }
}

proc chartShowLegend {g} {
  global vsd
  mapLegend $g $vsd($g:showlegend)
}

proc chartShowGridLines {g} {
  global vsd
  if {$vsd($g:showgridlines)} {
    $g grid on 
  } else {
    $g grid off
  }
}

proc chartShowCurXY {w mf} {
  global vsd
  if {[lsearch -exact [pack slaves $mf] $mf.min] != -1} {
    set afterW $mf.min
  } else {
    set afterW $mf.linef
  }
  if {$vsd($w:showcurxy)} {
    pack $mf.cury $mf.curx -after $afterW -side right -padx 2
  } else {
    pack forget $mf.cury $mf.curx
  }
}

proc chartShowMinMax {w mf} {
  global vsd
  if {$vsd($w:showminmax)} {
    pack $mf.max $mf.min -after $mf.linef -side right -padx 2
  } else {
    pack forget $mf.max $mf.min
  }
}

proc chartCancelOp {g} {
  # default to zoom out
  chartCancelOpZoom $g
}

proc chartZoom {g} {
  global vsd

  if {$vsd($g:chartOp) != {}} {
    # do the above check so that default right click action not taken
    chartCancelOp$vsd($g:chartOp) $g
  }

  set vsd($g:chartOp) "Zoom"
  $g config -cursor top_left_corner
  hideMarker $g activeLine
  set title "Click to start zoom, right click to cancel"
  $g marker create text -name "bltZoom_title" -text $title \
    -coords {-Inf Inf} -anchor nw -bg {} -font vsdsf
}

proc chartSetMinTime {g} {
  global vsd

  if {$vsd($g:chartOp) != {}} {
    # do the above check so that default right click action not taken
    chartCancelOp$vsd($g:chartOp) $g
  }

  set vsd($g:chartOp) "MinTime"
  $g config -cursor left_side
  set title "Click to set min time, right click to cancel"
  hideMarker $g activeLine
  $g marker create text -name "chartop_title" -text $title \
    -coords {-Inf Inf} -anchor nw -bg {} -font vsdsf
}

proc chartSetMaxTime {g} {
  global vsd

  if {$vsd($g:chartOp) != {}} {
    # do the above check so that default right click action not taken
    chartCancelOp$vsd($g:chartOp) $g
  }

  set vsd($g:chartOp) "MaxTime"
  $g config -cursor right_side
  set title "Click to set max time, right click to cancel"
  hideMarker $g activeLine
  $g marker create text -name "chartop_title" -text $title \
    -coords {-Inf Inf} -anchor nw -bg {} -font vsdsf
}

proc getTemplateProcName {pname ptype} {
  # added to fix bug 18996
  set res "*"
  switch -exact [string tolower $ptype] {
    disk -
    pgsvr -
    session -
    gcsession -
    gem {
      # Need to strip unique id off end of name and replace with a *
      regsub {[0-9]+$} $pname {*} res
    }
    vm {
      switch -glob [string tolower $pname] {
	"gsjvm*" { set res "gsjVM*"}
	"gsvm*" { set res "gsVM*"}
	"gns*" { set res "gns*"}
	"*sap" { set res "*sap"}
	"*gc" { set res "*GC"}
	"*activator" { set res "*Activator"}
	"*otsagent" { set res "*OTSAgent"}
	"*rnsagent" { set res "*RNSAgent"}
      }
    }
  }
  return $res
}

proc getTemplateLineSpec {li yaxis} {
  global vsd
  # proc-type proc-name stat-name scale filter axis
  set ptlist {}
  foreach pt [lindex $li 6] {
    # strip off any object type version
    lappend ptlist [file rootname $pt]
  }
  set procType $ptlist
  set instList [lindex $li 8]
  set operator [lindex $li 9]
  if {[llength $instList] > 1} {
    if {$operator != {}} {
      set ls1 [getTemplateLineSpec [lindex $instList 0] $yaxis]
      set ls2 [getTemplateLineSpec [lindex $instList 1] $yaxis]
      switch -exact -- $operator {
	"+" {set opcode "plus"}
	"-" {set opcode "diff"}
	"/" {set opcode "divide"}
      }
      set lineSpec [list $opcode $ls1 $ls2 \
		      [lindex $li 4] [lindex $li 5] $yaxis \
		      [lindex $li 13] [lindex $li 12] [lindex $li 11]]
      return $lineSpec
    } else {
      append procType "+"
      set procName "*"
    }
  } else {
    set procName [getTemplateProcName [lindex [lindex $li 0] 0] $procType]
  }
  set lineSpec [list $procType $procName [lindex $li 1] \
		  [lindex $li 4] [lindex $li 5] $yaxis \
		  [lindex $li 13] [lindex $li 12] [lindex $li 11]]
  return $lineSpec
}

proc getTemplateName {parent defaultName} {
  global vsd
  set vsd(template:tv) "$defaultName"
  update ;# allows startSearch to be called from popup menu
  set vsd(template:parent) $parent
  set vsd(template:oldfocus) [focus]
  set vsd(template:result) {}
  set w ".vsdtemplateName"
  set entry [$w.f.entry subwidget entry]
  
  set width [winfo width $parent]
  set x [winfo rootx $parent]
  set y [winfo rooty $parent]
  set dialogheight [winfo height $w]
  if {$dialogheight == 1} {
    set dialogheight [winfo reqheight $w]
    if {$dialogheight == 1} {
      set dialogheight 32
    }
  }
  set y [expr {$y - $dialogheight}]
  raise $w
  wm deiconify $w
  wm geometry $w "${width}x$dialogheight+$x+$y"
  focus $entry
  tkwait variable vsd(template:result)
  return $vsd(template:result)
}

proc saveGraphTemplate {g n} {
  global vsd vsdtemplates
  set n [getTemplateName $g $n]
  if {![info exists vsdtemplates($n)]} {
    addTemplateMenu $vsd(w:templatecascade) $n
  }
  set vsdtemplates($n) {} ;# template line list
  foreach e [lsort -integer [$g element names]] {
    set yaxis [$g element cget $e -mapy]
    set li [$vsd(ed:$e:$g) -info]
    set lineSpec [getTemplateLineSpec $li $yaxis]
    if {[lsearch -exact $vsdtemplates($n) $lineSpec] == -1} {
      lappend vsdtemplates($n) $lineSpec
    }
  }
  writeTemplateFile
}

proc lineUpdate {g} {
  global vsd
  set e [getSelectedLine $g]
  if {$e != {}} {
    set l $vsd(ed:$e:$g)
    foreach dfid [lindex [$l -info] 7] {
      fileUpdate $dfid
    }
  }
}

proc getLineStyle {g e} {
  $g element cget $e -smooth
}

proc addLineToClipboard {l g e} {
  global vsd
  emptyClipboard
  lappend vsd(clipboard) "linecmd"
  lappend vsd(clipboard) [list $l [$g element cget $e -mapy] [$g element cget $e -symbol] [getLineStyle $g $e]]
  
}

proc lineCut {g} {
  global vsd
  set e [getSelectedLine $g]
  if {$e != {}} {
    addLineToClipboard $vsd(ed:$e:$g) $g $e
    deleteElement $g $e 0
  }
}

proc lineCopy {g} {
  global vsd
  set e [getSelectedLine $g]
  if {$e != {}} {
    addLineToClipboard [$vsd(ed:$e:$g) -copy] $g $e
  }
}


proc lineDelete {g} {
  global vsd
  # 48628 - don't allow the last line to be deleted
  set numlines [llength [$g element names]]
  set lines [getSelectedLines $g]
  if {$numlines == [llength $lines]} {
    bell
    return
  }
  
  foreach e $lines {
    deleteElement $g $e
  }
}

proc lineCombine {g op title} {
  global vsd

  set e [getSelectedLine $g]
  if {$e == {}} {
    return
  }
  if {$vsd($g:chartOp) != {}} {
    # do the above check so that default right click action not taken
    chartCancelOp$vsd($g:chartOp) $g
  }
  set vsd($g:combineData) [list $e $op]
  set vsd($g:chartOp) "Combine"
  $g config -cursor crosshair
  hideMarker $g activeLine
  append title ", right click to cancel"
  $g marker create text -name "chartop_title" -text $title \
    -coords {-Inf Inf} -anchor nw -bg {} -font vsdsf
}

proc lineAdd {g} {
  lineCombine $g "+" "Select line to add with"
}
proc lineDivide {g} {
  lineCombine $g "/" "Select line to divide by"
}
proc lineDiff {g} {
  lineCombine $g "-" "Select line to diff with"
}

proc getLineInfoStr {linfo {indent ""}} {
  set nextindent "$indent  "
  foreach dfid [lindex $linfo 7] {
    lappend dfnames [lindex [$dfid -info] 0]
  }

  set ctrunits [getStatUnits [lindex $linfo 1]]
  set infoStr "${indent}Line: [lrange $linfo 0 1] "
  set subtractor [lindex $linfo 12]
  set divider [lindex $linfo 13]
  if {$divider != 1.0} {
    if {[expr {abs($divider)}] >= 1.0} {
      append infoStr "/ $divider "
    } else {
      append infoStr "* [expr {1.0 / $divider}] "
    }
  }
  if {$subtractor != 0} {
    if {$subtractor < 0} {
      append infoStr "+ [expr {- $subtractor}] "
    } else {
      append infoStr "- $subtractor "
    }
  }
  set instName [lindex $linfo 0]
  set ctr [lindex $linfo 1]
  set fileId [lindex $linfo 7]
  set ctrAlias [getCtrAlias $ctr $instName $fileId]
  if {![string equal $ctr $ctrAlias]} {
    append infoStr "alias: $ctrAlias "
  }
  append infoStr "filter: [lindex $linfo 5]  units: $ctrunits "
  if {[lindex $linfo 11] != 0} {
    append infoStr "normalized "
  }
  if {[llength $dfnames] > 1} {
    append infoStr "files: $dfnames"
  } else {
    append infoStr "file: $dfnames"
  }

  set instList [lindex $linfo 8]
  set operator [lindex $linfo 9]
  if {$operator != {}} {
    append infoStr "\n${indent}    derived using \"$operator\" from these two lines:"
    append infoStr "\n${indent}    [getLineInfoStr [lindex $instList 0] $nextindent]"
    append infoStr "\n${indent}    [getLineInfoStr [lindex $instList 1] $nextindent]"
  } elseif {[llength $instList] > 1} {
    append infoStr " dataSources:"
    foreach id $instList {
      append infoStr "\n${indent}    [lindex [sl_stat -instinfo $id] 0]"
    }
  }
  return $infoStr
}

proc lineInfo {g} {
  global vsd

  set infoStr {}
  foreach e [getSelectedLines $g] {
    set l $vsd(ed:$e:$g)
    append infoStr [getLineInfoStr [$l -info]]

    set xStart [$g xaxis cget -min]
    set xEnd [$g xaxis cget -max]
    set lstats [$l -stats $xStart $xEnd]
    set samples [lindex $lstats 0]
    set min [formatFloat [lindex $lstats 1]]
    set max [formatFloat [lindex $lstats 2]]
    set ave [formatFloat [lindex $lstats 3]]
    set stddev [formatFloat [lindex $lstats 4]]
    set startString [xToStr $g [lindex $lstats 5]]
    set endString [xToStr $g [lindex $lstats 6]]
    append infoStr "\n  Starting at $startString and ending at $endString:"
    append infoStr "\n    sample count: $samples  min: $min  max: $max"
    append infoStr "\n    average: $ave  stddev: $stddev\n"
  }
  if {$infoStr != {}} {
    showData $infoStr
  }
}

proc lineDelta {g} {
  global vsd

  set e [getSelectedLine $g]
  if {$e == {}} {
    return
  }

  if {$vsd($g:chartOp) != {}} {
    # do the above check so that default right click action not taken
    chartCancelOp$vsd($g:chartOp) $g
  }

  set vsd($g:deltaLine) $e
  if [info exists vsd($g:deltapoint1)] {
    unset vsd($g:deltapoint1)
  }
  set vsd($g:chartOp) "Delta"
  $g config -cursor crosshair
  hideMarker $g activeLine
  set title "Click on first data point, right click to cancel"
  $g marker create text -name "chartop_title" -text $title \
    -coords {-Inf Inf} -anchor nw -bg {} -font vsdsf
}

proc lineTrimLeft {g} {
  global vsd

  set e [getSelectedLine $g]
  if {$e == {}} {
    return
  }

  if {$vsd($g:chartOp) != {}} {
    # do the above check so that default right click action not taken
    chartCancelOp$vsd($g:chartOp) $g
  }

  set vsd($g:trimLine) $e
  set vsd($g:trimLeft) 1
  set vsd($g:chartOp) "Trim"
  $g config -cursor crosshair
  hideMarker $g activeLine
  set title "Click on first good data point, right click to cancel"
  $g marker create text -name "chartop_title" -text $title \
    -coords {-Inf Inf} -anchor nw -bg {} -font vsdsf
}

proc lineTrimRight {g} {
  global vsd

  set e [getSelectedLine $g]
  if {$e == {}} {
    return
  }

  if {$vsd($g:chartOp) != {}} {
    # do the above check so that default right click action not taken
    chartCancelOp$vsd($g:chartOp) $g
  }

  set vsd($g:trimLine) $e
  set vsd($g:trimLeft) 0
  set vsd($g:chartOp) "Trim"
  $g config -cursor crosshair
  hideMarker $g activeLine
  set title "Click on last good data point, right click to cancel"
  $g marker create text -name "chartop_title" -text $title \
    -coords {-Inf Inf} -anchor nw -bg {} -font vsdsf
}


proc lineUntrimLeft {g} {
  global vsd

  set e [getSelectedLine $g]
  if {$e == {}} {
    return
  }

  $vsd(ed:$e:$g) -trimleft 0
  trimUpdate $g $e
}

proc lineUntrimRight {g} {
  global vsd

  set e [getSelectedLine $g]
  if {$e == {}} {
    return
  }

  $vsd(ed:$e:$g) -trimright -1
  trimUpdate $g $e
}

proc lineNormalize {g} {
  global vsd

  set e [getSelectedLine $g]
  if {$e == {}} {
    return
  }

  set l $vsd(ed:$e:$g)
  if {$vsd($g:normalized)} {
    $l -normalize
  } else {
    $l -unnormalize
  }
  updateDerivedLines
}

proc trimUpdate {graph element} {
  global vsd

  updateFileTrimMenuItems [$vsd(statsrc) -info]
  updateAllLineTrimMenuItems
  setInstanceList
  updateGraphWidgetState
  updateAllActiveLines
}

proc chartCompare {g} {
  global vsd

  if {$vsd($g:chartOp) != {}} {
    # do the above check so that default right click action not taken
    chartCancelOp$vsd($g:chartOp) $g
  }

  if [info exists vsd($g:compareLine1)] {
    unset vsd($g:compareLine1)
  }
  if [info exists vsd($g:comparepoint1)] {
    unset vsd($g:comparepoint1)
  }
  set vsd($g:chartOp) "Compare"
  $g config -cursor crosshair
  hideMarker $g activeLine
  set title "Click on first data point, right click to cancel"
  $g marker create text -name "chartop_title" -text $title \
    -coords {-Inf Inf} -anchor nw -bg {} -font vsdsf
}

proc emptyClipboard {} {
  global vsd
  foreach {type data} $vsd(clipboard) {
    switch -exact $type {
      "linecmd" {
	[lindex $data 0] -free
      }
    }
  }
  set vsd(clipboard) {}
}

proc chartPaste {w g} {
  global vsd
  foreach {type data} $vsd(clipboard) {
    switch -exact $type {
      "linecmd" {
	set mapaxis [lindex $data 1]
	set symbol [lindex $data 2]
	set style [lindex $data 3]
	addLineToChart [lindex $data 0] 0 1 $mapaxis $symbol $style
      }
      "selection" {
	addToChart 0 [lindex $data 0] [lindex $data 1]
      }
    }
  }
  set vsd(clipboard) {}
}

################################################################################
# patternForCacheName
#
# Given the cache name of a process, lookup which alias pattern matches.
# Each pattern ends with an asterisk (*).
# Returns an empty string if the cacheName matches no alias pattern.
################################################################################
proc patternForCacheName {cacheName} {
  global vsd
  # 49312 - scan in reverse order
  foreach pattern [lreverse $vsd(listOfCachePatterns)] {
    if {[string match $pattern $cacheName] == 1} {
      return $pattern
    }
  }
  # no match if we get here
  return ""
}

################################################################################
# patternsForVersion
#
# Given a version string, return a list of all known version patterns that match.
################################################################################
proc patternsForVersion {version} {
  global vsd
  set result {}
  # 49312 - scan in reverse order
  foreach pattern [lreverse $vsd(listOfVersionPatterns)] {
    if {[string match $pattern $version]} {
      lappend result $pattern
    }
  }
  return $result
}

proc instNameHasAnyAlias {instName} {
  global vsd
  set allCountersKey [dict get $vsd(instNameToProcKindDict) $instName]
  return [expr {[string first , $allCountersKey] != -1}]
}

proc getProcKindForInstanceName {instName procKind} {
  global vsd 
  return [dict get $vsd(instNameToProcKindDict) $instName]
}

################################################################################
# checkStatType
#
# Given a stat type, verify it is valid.  If not, raise an error and exit
################################################################################
proc checkStatType {aType} {
  set validTypes [list counter counter64 svalue svalue64 uvalue ]
  if {[lsearch -exact $validTypes $aType] == -1} {
    set msg "\[Error\]: Invalid type $aType detected"
    puts stderr $msg
    fatalError $msg
  }
}

################################################################################
# checkStatFilter
#
# Given a filter, verify it is valid.  If not, raise an error and exit
################################################################################
proc checkStatFilter {aFilter} {
  set validFilters [list none persecond persample]
  if {[lsearch -exact $validFilters $aFilter] == -1} {
    set msg "\[Error\]: Invalid filter $aFilter detected"
    puts stderr $msg
    fatalError $msg
  }
}

################################################################################
# getFilterForStatName
#
# Given a stat, see if it has an alias. If so, return the filter defined for
# the alias.  If not, return $defaultFilter
################################################################################
proc getFilterForStatName {stat defaultFilter} {
  global statFilters
  set result $defaultFilter
  if {[info exists statFilters($stat)]} {
    set result $statFilters($stat)
  }
  return $result
}

################################################################################
# setInternalCtrAlias
#
# Same as setCtrAlias except the alias is not written to .vsdrc
#
################################################################################
proc setInternalCtrAlias {cacheNamePatterns statName alias type filter units desc \
			    {verPatterns *}} {
  setCtrAlias $cacheNamePatterns $statName $alias $type $filter $units $desc $verPatterns 0
}		  
		  
################################################################################
# setCtrAlias
#
# Users may call this method to setup an alias.
# Args:
#   cacheNamePatterns - A pattern or list of patterns used to match cache names
#                      for this alias.
#
#   statName         - Name of a built-in statistic
#
#   alias            - user-defined alias for statName
#   
#   type             - one of: counter, counter64, uvalue, svalue, or svalue64
#
#   filter           - one of none, persecond or persample
#
#   units            - String describing units of the stat
#
#   desc             - String describing that stat
#
#   verPatterns (optional) - list of version patterns
#
#   writeToStartupFile (optional) - Boolean indicating if the alias will be written
#                      to .vsdrc (defaults to 1)
#
# Example:
#
# setCtrAlias GcReclaim* SessionStat00 ReclaimCommitPageReads \
#    svalue64 persecond Pages {Number of pages read by the reclaim}
################################################################################
proc setCtrAlias {cacheNamePatterns statName alias type filter units desc \
		    {verPatterns *} {writeToStartupFile 1}} {

  
  global vsd ctrAliases statDefinitions statFilters
  checkStatType $type
  checkStatFilter $filter

  set dotrace 0
#  set dotrace [string match MarkForCollection [lindex $cacheNamePatterns 1]]
#  set dotrace [expr {$dotrace && [string match 6.* $verPatterns]}]
#  set dotrace [expr {$dotrace && [string match $statName SessionStat01]}]
 
  set level common
  set isOS 0
  set fullAttribs [list $type $level $filter $isOS $units $desc]
  set attribList  [list $alias $type $filter $units $desc $writeToStartupFile]

  
  if {$dotrace} {
    set args "setCtrAlias: \"$cacheNamePatterns\" \"$statName\" \"$alias\""
    set args [string cat $args "\"$verPatterns\" \"$writeToStartupFile\""]
    puts stderr "setCtrAlias: $args\n"
 #   puts stderr "attribList is $attribList"
  }

  foreach aVerPattern $verPatterns {    
    # If this is a new ver pattern, add it to the master list of version patterns
    if {[lsearch -exact $vsd(listOfVersionPatterns) $aVerPattern] == -1} {
      lappend vsd(listOfVersionPatterns) $aVerPattern
      if {$dotrace} {
	puts "Adding $aVerPattern to vsd(listOfVersionPatterns)"
      }
    } elseif {$dotrace} {
      puts "$aVerPattern already exists in vsd(listOfVersionPatterns)"
    }

    foreach cnPattern $cacheNamePatterns {
      if {[lsearch -exact $vsd(listOfCachePatterns) $cnPattern] == -1} {
	# This is a new pattern, add it to the list
	lappend vsd(listOfCachePatterns) $cnPattern
      }
      
      set arrayKey [getCtrAliasesKey $cnPattern $statName]
      if {![info exists ctrAliases($arrayKey)]} {
	# create the dictionary and store in the array
	if {$dotrace} {
	  puts "creating ctrAliases($arrayKey)"
	}
	set ctrAliases($arrayKey) [dict create]
      }  elseif {$dotrace} {
	puts "ctrAliases($arrayKey) exists"
      }
      
      dict set ctrAliases($arrayKey) $aVerPattern $attribList
      dict set vsd(cacheIdAliasDict) $cnPattern $aVerPattern $statName $alias
      if {$dotrace} {
        puts "Adding to ctrAlaises($arrayKey): $aVerPattern -> $attribList"
	puts "Adding to vsd(cacheIdAliasDict): $cnPattern ->$aVerPattern->$statName -> $alias"
      }
    }
    # GcReclaim* -> 3.5* ->SessionStat00 -> SessionStat00 (Page Reads)
  }

  set statName [getStatNameForCounterAndAlias $statName $alias]
  set statDefinitions($statName) [list $type common $units $isOS]
  set statDocs($statName) $desc
  set statFilters($statName) $filter
  sl_stat -info $statName $fullAttribs
}

proc getStatNameForCounterAndAlias {counter alias} {
  return "$counter \($alias\)"
}

proc setBuiltinCtrAliases {} {
  sourceVsdBinFile vsd.internal.stat.aliases.tcl
}

proc doGetCtrAlias {counter instName fileId dotrace} {
  global vsd ctrAliases fileIdToVersionMap
  set result $counter

  # Fix 47719
  if {![dict exists $vsd(instNameToProcKindDict) $instName]} {
    # Multi-instance lines from templates take this path. "2Gems 0 0"
    return $result
  }
  
  set allCountersKey [dict get $vsd(instNameToProcKindDict) $instName]
  # Gem or Gem,MarkForCollection. Comma indicates an alias
  set parts [split $allCountersKey ,]
  
  if {[llength $parts] == 1} {
    if {$dotrace} {
      puts "No aliases for instance"
    }
    # no comma, so instance has no aliases for any counter
    return $result
  }

  # Second part is the alias pattern
  # Get the cache name pattern from vsd(instNameToProcKindDict)
  # It might not be the original pattern if an alias template
  # has since been applied.
  set cacheNamePattern [lindex $parts 1]
  set key [getCtrAliasesKey $cacheNamePattern $counter]

  if {![info exists ctrAliases($key)]} {
    # instance has aliases but not for this counter
    if {$dotrace} {
      puts "No alias for counter $counter"
    }    
    return $result
  }
  
  set verDict $ctrAliases($key)
  set fileVersion $fileIdToVersionMap($fileId)
  set verPatterns [patternsForVersion $fileVersion]
  foreach aVerPattern $verPatterns {
    if {[dict exists $verDict $aVerPattern]} {
      set verList [dict get $verDict $aVerPattern]
      set aliasName [lindex $verList 0]
      set result [getStatNameForCounterAndAlias $counter $aliasName]
      break
    } elseif {$dotrace} {
      puts "getCtrAlias: dict exists verDict $verPattern is false"
    }
  }

  return $result
}

proc getCtrAlias {counter instName fileId} {
  set dotrace 0
  if {$dotrace} {
    puts "getCtrAlias \"$counter\" \"$instName\" \"$fileId\""
  }  
  set result [doGetCtrAlias $counter $instName $fileId $dotrace]
  if {$dotrace} {
    puts "  result is $result"
  }
  return $result
}

proc getLineLegendLabel {i lineinfo} {
  global vsd
  set instinfo [lindex $lineinfo 0]
  set lineinst [lindex $instinfo 0]
  set obj [lindex $lineinfo 6]
  # strip off any object version extension
  set obj [file rootname $obj]
  if {[string equal $obj "Gem"] ||
      [string equal $obj "Session"] ||
      [string equal $obj "Extent"] ||
      [string equal $obj "GcSession"]} {
    # add session id on end of name
    set session [lindex $instinfo 2]
    if {$session != 0 || [string equal $obj "Extent"]} {
      append lineinst "-$session"
    }
  }
  global vsdnametable
  if {[info exists vsdnametable($lineinst)] && $vsdnametable($lineinst) > 1} {
    set pid [lindex $instinfo 1]
    if {$pid != 0 && $pid != -1} {
      append lineinst "_[lindex $instinfo 1]"
    }
    if {[string equal $lineinst "_Total"]} {
      set lineinst "$obj$lineinst"
    }
  }

  
  set fileId [lindex $lineinfo 7]
  set instName [lindex $lineinfo 0]
  # 48010 - use the counter display name (w/ alias name) in the last slot.
  #         if that's empty, fall back to the counter name in
  #         slot 1.
  set counter [lindex $lineinfo 14]
  if {[string length $counter] == 0} {
    set counter [lindex $lineinfo 1]
  }
  set linectr [getCtrAlias $counter $instName $fileId]
  set linefilter [lindex $lineinfo 5]
  set fid [string map {"slFile" ""} [lindex $lineinfo 7]]
  if {[string equal $linefilter "none"]} {
    set label "$i.  $lineinst, $linectr"
  } elseif {[string equal $linefilter "aggregate"]} {
    set label "$i.  $lineinst, $linectr+"
  } elseif {[string equal $linefilter "smooth"]} {
    set label "$i.  $lineinst, $linectr~"
  } else {
    if {[string equal $linefilter "persecond"]} {
      set label "$i.  $lineinst, $linectr/sec"
    } elseif {[string equal $linefilter "persample"]} {
      set label "$i.  $lineinst, $linectr/sample"
    } else {
      set label "$i.  $lineinst, $linectr, $linefilter"
    }
  }
  if { $fid > 1 } {
    # Fix 46202
    append label "\[$fid\]"
  }
  return $label
}

# request 48366
proc allLinesFilter {g filter} {
  global vsd
  set idxlst [$g element names]
  foreach e $idxlst {
    set line $vsd(ed:$e:$g)
    $line -refilter $filter
    updateLineDataDisplay $g $e $line
    set label [getLineLegendLabel $e [$line -info]]
    $g element configure $e -label $label    
  }
}

proc lineFilter {g filter} {
  global vsd
  foreach e [getSelectedLines $g] {
    set line $vsd(ed:$e:$g)
    $line -refilter $filter
  }
  updateDerivedLines
}

proc changeIntTypeForLine {g newIntType} {
  global vsd
  foreach e [getSelectedLines $g] {
    set line $vsd(ed:$e:$g)
    $line -changeinttype $newIntType
  }
  updateDerivedLines
}

proc lineSymbol {g symbol} {
  global vsd
  set e [getSelectedLine $g]
  if {$e != {}} {
    $g element configure $e -symbol $symbol
  }
}

proc lineMenuSymbol {g} {
  global vsd
  lineSymbol $g $vsd($g:linesym)
}

proc lineStyle {g style} {
  global vsd
  set e [getSelectedLine $g]
  if {$e != {}} {
    $g element configure $e -smooth $style
  }
}

proc lineMenuStyle {g} {
  global vsd
  lineStyle $g $vsd($g:linestyle)
}

proc lineGraphOnLeft {g} {
  global vsd
  set e [getSelectedLine $g]
  if {$e != {}} {
    if {$vsd($g:graphonleft)} {
      set mapaxis "y"
      set unmap "y2"
    } else {
      set mapaxis "y2"
      set unmap "y"
    }
    $g element configure $e -mapy $mapaxis
    deactivateAxis $g
    activateAxis $g $e

    showAxis $g ${mapaxis}axis
    if {[string equal $mapaxis "y"]} {
      $g configure -leftmargin 0
    } else {
      $g configure -rightmargin 0
    }

    foreach e [$g element names] {
      if {[string equal $unmap [$g element cget $e -mapy]]} {
	return;
      }
    }
    # No more lines using this axis so unmap it
    hideAxis $g ${unmap}axis
    if {[string equal $unmap "y"]} {
      $g configure -leftmargin 2
    } else {
      $g configure -rightmargin 2
    }
  }
}

proc updateLineDataDisplay {g e line} {
  $g marker configure activeLine -text [getLineLabel $g $e]
  set label [getLineLegendLabel $e [$line -info]]
  $g element configure $e -label $label
  chartAxisTitle $g [$g element cget $e -mapy]axis
  chartAxisTitle $g "xaxis"
  showLineInfo $g $e
}

proc chartChangeCounter {g newctr} {
  global vsd
  set e [getSelectedLine $g]
  if {$e != {}} {
    set line $vsd(ed:$e:$g)
    $line -changectr $newctr
    updateDerivedLines
  }
}

# call updateDerivedLines if a line has been changed in a way that can
# cause the info about a derived line to change.
proc updateDerivedLines {} {
  global bltActiveEntry
  global vsd

  # make sure every active line's info is correct
  foreach g [array names bltActiveEntry] {
    if {$bltActiveEntry($g) != -1} {
      set e $bltActiveEntry($g)
      set line $vsd(ed:$e:$g)
      updateLineDataDisplay $g $e $line
    }
  }
  # make sure the names in the legend of each graph
  foreach n [array names vsd ed:*] {
    set nl [split $n ':']
    set e [lindex $nl 1]
    set g [lindex $nl 2]
    set line $vsd($n)
    set label [getLineLegendLabel $e [$line -info]]
    $g element configure $e -label $label
  }
}

proc chartBrowseCounters {c} {
  showCounterInfo $c
}

proc chartComputeCounters {g mf} {
  global vsd
  set e [getSelectedLine $g]
  if {$e != {}} {
    set linfo [$vsd(ed:$e:$g) -info]
    set obj [lindex $linfo 6]
    set instId [lindex $linfo 10]
    set counter [$mf.linef.counter cget -value]
    if {$vsd(noFlatlines) || $vsd(noAllZeros)} {
      set newctrlistid [list $obj $instId]
    } else {
      set newctrlistid $obj
    }
    if {$vsd($g:ctrlistid) != {}} {
      if {[string equal $newctrlistid $vsd($g:ctrlistid)]} {
	# the listbox already contains the right stuff
	set lb [$mf.linef.counter subwidget listbox]
	set endIdx [$lb size]
	set idx 0
	while {$idx < $endIdx} {
	  if {[string equal $counter [$lb get $idx]]} {
	    $mf.linef.counter configure -disablecallback true
	    $mf.linef.counter pick $idx
	    $mf.linef.counter configure -disablecallback false
	    break
	  }
	  incr idx
	}
	return
      }
    }
    set vsd($g:ctrlistid) $newctrlistid
    set cb $mf.linef.counter
    set cl [$cb subwidget listbox]
    $cl delete 0 end
    set idx 0
    set fi -1
    set ctrlist [allCountersForType $obj]
    if {$vsd(noFlatlines) || $vsd(noAllZeros)} {
      if {$instId != -1} {
	set lineCtr [lindex $linfo 1]
	set newlist {}
	foreach ctr $ctrlist {
	  if [sl_stat -ctrzero $ctr $instId] {
	    if {![string equal $ctr $lineCtr]} {
	      # dont add this guy
	      continue
	    }
	  }
	  lappend newlist $ctr
	}
	set ctrlist $newlist
      }
    }
    set ctrlist [lsort -dictionary $ctrlist]
    
    eval $cl insert end $ctrlist
    set fi [lsearch -exact $ctrlist $counter]
    if {$fi != -1} {
      $cb configure -disablecallback true
      $cb pick $fi
      $cb configure -disablecallback false
    }
  }
}

proc searchHList {w str {next 0} {forwards 1}} {
  global vsd
  if {$str == ""} {
    return
  }
  set anchor [$w info anchor]
  set l [$w info children]
  if {$anchor != ""} {
    set idx [lsearch -exact $l $anchor]
    if {$idx != -1} {
      set oldidx $idx
      incr idx
      if {$idx <= [llength $l]} {
	set l [concat [lrange $l $idx end] [lrange $l 0 $oldidx]]
      }
    }
  }
  if [string is digit [string index $str 0]] {
    foreach e $l {
      if [$w item exists $e $vsd(ilColumnIdx:ProcessId)] {
	if {[string equal $str [$w item cget $e $vsd(ilColumnIdx:ProcessId) -text]]} {
	  $w selection clear
	  $w anchor set $e
	  $w selection set $e
	  $w see $e
	  showFileForAnchor
	  setStatus "Found Pid \"$str\"."
	  bell
	  return
	}
      }
    }
  } else {
    set pattern "*$str*"
    foreach e $l {
      if [string match -nocase $pattern [$w entrycget $e -data]] {
	$w anchor set $e
	$w see $e
	showFileForAnchor
	return
      }
    }
    setStatus "Nothing matches \"$pattern\"."
    bell
  }
}

proc searchCtrHList {w str {next 0} {forwards 1}} {
  global vsd
  if {$str == ""} {
    return
  }
  set l [$w info children]
  if {$next == 1} {
    set anchor [$w info anchor]
    if {$anchor != ""} {    
      set idx [lsearch -exact $l $anchor]
      if {$idx != -1} {
	set oldidx $idx
	if {$forwards == 1} {
	  # Start serach at the entry
  	  incr idx
	  if {$idx <= [llength $l]} {
	    set l [concat [lrange $l $idx end] [lrange $l 0 $oldidx]]
	  }
	} else {
	  # not forwards so a reverse search
	  incr idx -1
	  if {$idx >= 0} {
	    set l [concat [lreverse [lrange $l 0 $idx]] [lreverse [lrange $l $oldidx end]]]
	  }
	}	
      }   
    }
  }

  # delete code that starts search mid way down the list
  
  set pattern "*$str*"
  foreach e $l {
    set ctr [$w entrycget $e -text]
    if [string match -nocase $pattern $ctr] {
      $w selection clear
      $w anchor set $e
      $w selection set $e
      $w see $e
      showCounterInfo $ctr
      return
    }
  }
  setStatus "Nothing matches \"$pattern\"."
  bell
}

proc searchComboBox {w str {next 0} {forwards 1}} {
  global vsd
  set lb [$w subwidget listbox]
  set curIdx [$lb index active]
  set pattern "*$str*"
  if {$curIdx == -1} {
    set curIdx 0
  } else {
    incr curIdx
  }
  set endIdx [$lb size]
  set idx $curIdx
  while {$idx < $endIdx} {
    set c [$lb get $idx]
    if [string match -nocase $pattern $c] {
      $w configure -disablecallback true
      $w pick $idx
      $w configure -disablecallback false
      set browsecmd [$w cget -browsecmd]
      if {$browsecmd != {}} {
	$browsecmd $c
      }
      return
    }
    incr idx
  }
  if {$curIdx != 0} {
    set endIdx $curIdx
    set idx 0
    while {$idx < $endIdx} {
      set c [$lb get $idx]
      if [string match -nocase $pattern $c] {
	$w configure -disablecallback true
	$w pick $idx
	$w configure -disablecallback false
	set browsecmd [$w cget -browsecmd]
	if {$browsecmd != {}} {
	  $browsecmd $c
	}
	return
      }
      incr idx
    }
  }
  setStatus "Nothing matches \"$pattern\"."
  bell
}

proc searchTrace {n1 n2 op} {
  global vsd
  $vsd(search:func) $vsd(search:parent) $vsd($n2)
}

proc nextSearch {} {
  global vsd
  set p $vsd(search:parent)
  $vsd(search:func) $p $vsd($p:st) 1
}

proc prevSearch {} {
  global vsd
  set p $vsd(search:parent)
  $vsd(search:func) $p $vsd($p:st) 1 0
}

proc endSearch {w restoreFocus} {
  global vsd
  wm withdraw $w
  if {[info exists vsd(search:parent)]} {
    set p $vsd(search:parent)
    trace vdelete vsd($p:st) w searchTrace
    if {$vsd(search:oldfocus) != ""} {
      focus $vsd(search:oldfocus)
    }
  }
}

proc createTemplateName {} {
  global vsd widgetHelp
  set w .vsdtemplateName
  toplevel $w
  wm withdraw $w
  wm overrideredirect $w 1
  frame $w.f -bd 3 -relief raised
  pack $w.f -fill both -expand yes
  tixLabelEntry $w.f.entry -label "Template Name:"

  set widgetHelp($w) {String Entry Editing}

  set entry [$w.f.entry subwidget entry]
  set vsd(template:tv) ""
  $entry config -textvariable vsd(template:tv)
  pack $w.f.entry -fill both -expand yes
  update
  bind $w <FocusOut>  [list endTemplateName $w $entry 0]
  bind $entry <Return> [list endTemplateName $w $entry 1]
}

proc endTemplateName {w entry restoreFocus} {
  global vsd
  wm withdraw $w
  if {[info exists vsd(template:parent)]} {
    set p $vsd(template:parent)
    if {$vsd(template:oldfocus) != ""} {
      focus $vsd(template:oldfocus)
    }
    unset vsd(template:parent)
    set vsd(template:result) $vsd(template:tv)
  }
}

proc showSearchHelp {} {
  doHelp "List Search Control"
}

proc createSearch {} {
  global vsd widgetHelp
  set w .vsdsearch
  toplevel $w
  wm withdraw $w
  wm overrideredirect $w 1
  frame $w.f -bd 3 -relief raised
  pack $w.f -fill both -expand yes
  tixLabelEntry $w.f.entry -label "Search:"

  set widgetHelp($w) {String Entry Editing}

  set entry [$w.f.entry subwidget entry]
 
  button $w.f.helpButton -text "?" -command showSearchHelp
  pack $w.f.helpButton -side right
  pack $w.f.entry -fill both -expand yes
#  pack $w.f.entry $w.f.helpButton
  update
  bind $w <FocusOut>  [list endSearch $w 0]
  bind $entry <Return> [list endSearch $w 1]
  bind $entry <Control-f> nextSearch
  bind $entry <Control-s> nextSearch
  bind $entry <Control-r> prevSearch
  bind $entry <Tab> {nextSearch; break}
}

proc createHelpSearch {} {
  global vsd widgetHelp
  set w .vsdhelpsearch
  toplevel $w
  wm withdraw $w
  wm overrideredirect $w 1

  set widgetHelp($w) {Help Search Control}

  frame $w.f -bd 3 -relief raised
  pack $w.f -fill both -expand yes
  tixLabelEntry $w.f.entry -label "Search:"

  set entry [$w.f.entry subwidget entry]
  pack $w.f.entry -fill both -expand yes -side left
  button $w.f.all -text "All Topics" -command searchAllTopics
  button $w.f.next -text "Next" -command nextSearch
  button $w.f.prev -text "Prev" -command prevSearch
  pack $w.f.all $w.f.next $w.f.prev -side right -expand no -fill none
  update
  bind $w <FocusOut>  [list endSearch $w 0]
  bind $entry <Return> [list endSearch $w 1]
  bind $entry <Control-s> nextSearch
  bind $entry <Control-f> nextSearch
  bind $entry <Control-r> prevSearch
  bind $entry <Tab> {nextSearch; break}
  bind $entry <Meta-s> searchAllTopics
}

proc extraStartSearch {parent searchFunc g mf} {
  chartComputeCounters $g $mf
  startSearch $parent $searchFunc
  [$parent subwidget entry] icursor 0 
}

proc startSearch {parent searchFunc {yoffset 0} {w .vsdsearch}} {
  global vsd
  update ;# allows startSearch to be called from popup menu
  set vsd(search:parent) $parent
  set vsd(search:func) $searchFunc
  set vsd(search:oldfocus) [focus]
  set vsd(search:unfound) {}
  if {! [info exists vsd($parent:st)]} {
    set vsd($parent:st) ""
  }
  set entry [$w.f.entry subwidget entry]
  $entry config -textvariable vsd($parent:st)
  trace variable vsd($parent:st) w searchTrace
  
  set width [winfo width $parent]
  set x [winfo rootx $parent]
  set y [winfo rooty $parent]
  set searchheight [winfo height $w]
  if {$searchheight == 1} {
    set searchheight [winfo reqheight $w]
    if {$searchheight == 1} {
      set searchheight 32
    }
  }
  set y [expr {$y - $searchheight - $yoffset}]
  raise $w
  wm deiconify $w
  wm geometry $w "${width}x$searchheight+$x+$y"
  focus $entry
}

proc findByFirstLetter {cb letter iscombo} {
  global vsd
  if {$letter >= "A" && $letter <= "Z"} {
    set letter [string tolower $letter]
  }
  if {$letter >= "a" && $letter <= "z"} {
    set lb [$cb subwidget listbox]
    set curIdx [$lb index active]
    if {$curIdx == -1} {
      set curIdx 0
    } else {
      incr curIdx
    }
    set endIdx [$lb size]
    set idx $curIdx
    while {$idx < $endIdx} {
      set c [$lb get $idx]
      set fc [string tolower [string index $c 0]]
      if {$fc == $letter} {
	if {$iscombo} {
	  $cb configure -disablecallback true
	  $cb pick $idx
	  $cb configure -disablecallback false
	  set browsecmd [$cb cget -browsecmd]
	  if {$browsecmd != {}} {
	    $browsecmd $c
	  }
	} else {
	  $lb selection clear 0 end
	  $lb selection set $idx
	  $lb activate $idx
	  $lb see $idx
	  showCounterInfo $c
	}
	return
      }
      incr idx
    }
    if {$curIdx != 0} {
      set endIdx $curIdx
      set idx 0
      while {$idx < $endIdx} {
	set c [$lb get $idx]
	set fc [string tolower [string index $c 0]]
	if {$fc == $letter} {
	  if {$iscombo} {
	    $cb configure -disablecallback true
	    $cb pick $idx
	    $cb configure -disablecallback false
	    set browsecmd [$cb cget -browsecmd]
	    if {$browsecmd != {}} {
	      $browsecmd $c
	    }
	  } else {
	    $lb selection clear 0 end
	    $lb selection set $idx
	    $lb activate $idx
	    $lb see $idx
	    showCounterInfo $c
	  }
	  return
	}
	incr idx
      }
    }
    setStatus "No statistic starts with \"$letter\"."
    bell
  }
}

proc HListPrefixClear {w} {
  global vsd
  set vsd(prefix:$w) {}
}

proc HListPrefixSearch {w letter} {
  global vsd
  if {$letter >= "A" && $letter <= "Z"} {
    set letter [string tolower $letter]
  }
  if {$letter >= "a" && $letter <= "z"} {
    if [info exists vsd(prefix:$w)] {
      set prefix $vsd(prefix:$w)
      append prefix $letter
    } else {
      set prefix $letter
    }
    set vsd(prefix:$w) $prefix
    
    set l [$w info children]
    foreach e $l {
      set ctr [string tolower [$w entrycget $e -text]]
      if {[string first $prefix $ctr] == 0} {
	$w selection clear
	$w selection set $e
	$w anchor set $e
	$w see $e
	showCounterInfo $ctr
	return
      }
    }
    setStatus "No statistic starts with \"$vsd(prefix:$w)\"."
    bell
  }
  HListPrefixClear $w
}

proc extraFindByFirstLetter {cb letter g mf} {
  if {$letter >= "A" && $letter <= "Z"} {
    set letter [string tolower $letter]
  }
  if {$letter >= "a" && $letter <= "z"} {
    chartComputeCounters $g $mf
    findByFirstLetter $cb $letter 1
    [$cb subwidget entry] icursor 0 
  }
}

proc showElapsedOnTicks {g x} {
  global vsd
  set x [expr {wide($x)}]
  if [catch {sl_stat -getdate $x $vsd(time_shift_hours) "elapsed"} result] {
    return $x
  } else {
    return $result
  }
}

proc showTimeOnTicks {g x} {
  global vsd
  set x [expr {wide($x)}]
  if [catch {sl_stat -getdate $x $vsd(time_shift_hours) "time"} result] {
    return $x
  } else {
    return $result
  }
}

proc showDateOnTicks {g x} {
  global vsd
  set x [expr {wide($x)}]
  if [catch {sl_stat -getdate $x $vsd(time_shift_hours) "date"} result] {
    return $x
  } else {
    return $result
  }
}

proc chartMenuTime {g} {
  global vsd
  chartAxisTitle $g xaxis
  if {[string equal $vsd($g:xformat) "elapsed"]} {
    $g xaxis configure -command showElapsedOnTicks
  } elseif {[string equal $vsd($g:xformat) "time"]} {
    $g xaxis configure -command showTimeOnTicks
  } else {
    $g xaxis configure -command showDateOnTicks
  }
}

proc computeScale {g e} {
  global vsd
  set l $vsd(ed:$e:$g)
  set yaxis [$g element cget $e -mapy]
  append yaxis "axis"
  scan [$g xaxis limits] "%s %s" xmin xmax
  scan [$g $yaxis limits] "%s %s" ymin ymax

  set edata [$l -info]
  set xvname [lindex $edata 2]
  set yvname [lindex $edata 3]
  set oldscale [lindex $edata 4]
  global $xvname
  set lxmin [set ${xvname}(min)]
  set lxmax [set ${xvname}(max)]
  if {$lxmin > $xmax || $lxmax < $xmin} {
    # No data points in current graph
    return
  }
  if {$lxmin < $xmin} {
    set lxmin $xmin
  }
  if {$lxmax > $xmax} {
    set lxmax $xmax
  }
  # calculate minidx
  set xvsize [$xvname length]
  incr xvsize -1
  set minidx 0
  set maxidx $xvsize

  while {[set ${xvname}($minidx)] < $lxmin} {
    incr minidx
    if {$minidx > $xvsize} {
      return
    }
  }
  while {[set ${xvname}($maxidx)] < $lxmax} {
    if {$maxidx == $xvsize} {
      break
    }
    incr maxidx
  }
  while {[set ${xvname}($maxidx)] > $lxmax} {
    incr maxidx -1
    if {$maxidx <= $minidx} {
      return
    }
  }
  # Need to unscale so that y values used will be correct
  vector create tmpy
  tmpy set [$yvname range $minidx $maxidx]
  set tmpyMax [vector expr {max(tmpy)}]
  vector destroy tmpy
  if {$tmpyMax == 0} {
    return
  }
  # NYI: Should handle negative numbers better.
  set newscale  [expr {$ymax / $tmpyMax}]
  if {$newscale >= 1.5} {
    set newscale [expr {$newscale * $oldscale}]
    $l -scale $newscale
    #updateLineDataDisplay $g $e $l
  }
}

proc lineComputeScale {g} {
  global vsd
  foreach e [getSelectedLines $g] {
    catch {computeScale $g $e} result
    if [wizardMode] {
      if {$result != {}} {
	showError "computeScale: $result"
      }
    }
    if {[string equal $e [getSelectedLine $g]]} {
      set l $vsd(ed:$e:$g)
      updateLineDataDisplay $g $e $l
    }
  }
}

proc lineUnscale {g} {
  lineChangeScale $g 1
  global vsd
  foreach e [getSelectedLines $g] {
    set l $vsd(ed:$e:$g)
    $l -scale 1
    if {[string equal $e [getSelectedLine $g]]} {
      updateLineDataDisplay $g $e $l
    }
  }
}

proc allComputeScale {g} {
  global vsd
  foreach e [$g element names] {
    catch {computeScale $g $e} result
    if [wizardMode] {
      if {$result != {}} {
	showError "computeScale: $result"
      }
    }
  }
  set e [getSelectedLine $g]
  if {$e != {}} {
    set l $vsd(ed:$e:$g)
    updateLineDataDisplay $g $e $l
  }
}

proc allUnscale {g} {
  global vsd
  foreach e [$g element names] {
    $vsd(ed:$e:$g) -scale 1
  }
  set e [getSelectedLine $g]
  if {$e != {}} {
    set l $vsd(ed:$e:$g)
    updateLineDataDisplay $g $e $l
  }
}

proc createGraphWindow {title} {
  global vsd isWindows widgetHelp
  global bltActiveEntry
  global bltActiveGraph
  

  set w ".chart$vsd(graphcreates)"
  set geom [getGraphGeometry $w]
  toplevel $w
  wm geometry $w $geom

  set widgetHelp($w) {Chart Window Features}

  if [info exists vsd(ct:$title)] {
    set title {}
  }
  if {$title == {}} {
    set title "Chart $vsd(graphcreates)"
    set templateName "chart$vsd(graphcreates)"
  } else {
    regsub -all { } $title {} templateName
  }

  incr vsd(graphcount)
  incr vsd(graphcreates)

  set vsd(ct:$title) $w
  $vsd(w:chartlist) configure -disablecallback true
  $vsd(w:chartlist) addhistory $title
  $vsd(w:chartlist) pick 0
  $vsd(w:chartlist) configure -disablecallback false
  set g $w.graph
  set bltActiveGraph($g) 0
  set bltActiveEntry($g) -1
  
  wm title $w $title

  set mf [frame $w.mf -bd 2 -relief raised]
  menubutton $mf.chart -menu $mf.chart.m -text "Chart" -underline 0 \
    -takefocus 0 -font boldMainWindowFont
  menu $mf.chart.m -tearoff 0

  set mftmp $mf.chart.m.templates
  set widgetHelp($mftmp) {Chart Menu Add From Template SubMenu}
  menu $mftmp -tearoff 0
  createGraphTemplateMenu $mftmp $title
  $mf.chart.m add cascade -label "Add From Template" \
    -menu $mftmp -underline 0 -font boldMainWindowFont

  $mf.chart.m add command -label "Save Template" -underline 0 \
    -command "saveGraphTemplate $g $templateName" -font boldMainWindowFont
  $mf.chart.m add command -label "Paste" \
    -command [list chartPaste $w $g] \
    -accelerator "Ctrl+v" -font boldMainWindowFont
  if {! $isWindows} {
    $mf.chart.m add command -label "Print"  \
      -command "printGraph $g $w.ps" \
      -accelerator "Ctrl+p" -font boldMainWindowFont
  }
  $mf.chart.m add command -label "Snapshot"  \
    -command "snapshotGraph $g $w.ps" -font boldMainWindowFont
  $mf.chart.m add command -label "Help..." -underline 0 \
    -command {showChartHelp} -font boldMainWindowFont
  $mf.chart.m add separator

  set vsd($g:chartOp) {}
  set vsd($g:showlegend) $vsd(def:showlegend)
  set vsd($g:xformat) $vsd(def:xformat)
  set vsd($g:xaxis:showtitle) $vsd(def:xaxis:showtitle)
  set vsd($g:yaxis:showtitle) $vsd(def:yaxis:showtitle)
  set vsd($g:y2axis:showtitle) $vsd(def:y2axis:showtitle)
  set vsd($g:showcrosshairs) $vsd(def:showcrosshairs)
  set vsd($g:showgridlines) $vsd(def:showgridlines)
  set vsd($w:showcurxy) $vsd(def:showcurxy)
  set vsd($w:showminmax) $vsd(def:showminmax)
  set vsd($w:showlinestats) $vsd(def:showlinestats)

  $mf.chart.m add command -label "Zoom In" \
    -command [list chartZoom $g] \
    -accelerator "Ctrl+z" -font boldMainWindowFont
  # 45172 - call ChartHandleRightClick instead of ChartCancelOpZoom
  $mf.chart.m add command -label "Zoom Out" \
    -command [list ChartHandleRightClick $g] -font boldMainWindowFont
  # 46251 - remove Compare Two Points
  if 0 {
    $mf.chart.m add command -label "Set Min Time" \
      -command [list chartSetMinTime $g] -font boldMainWindowFont
    $mf.chart.m add command -label "Set Max Time" \
      -command [list chartSetMaxTime $g] -font boldMainWindowFont
  }
  $mf.chart.m add command -label "Compute Scale All" \
    -command [list allComputeScale $g] \
    -accelerator "Ctrl+S" -font boldMainWindowFont
  $mf.chart.m add command -label "Unscale All" \
    -command [list allUnscale $g] \
    -accelerator "Ctrl+N" -font boldMainWindowFont
  $mf.chart.m add separator

  set mtime $mf.chart.m.time
  set widgetHelp($mtime) {Chart Menu Time Format SubMenu}
  menu $mtime -tearoff 0
  $mtime add radio -label "Seconds" -value "elapsed" \
    -variable vsd($g:xformat) \
    -command [list chartMenuTime $g] -font boldMainWindowFont
  $mtime add radio -label "Hour:Minute:Second" -value "time" \
    -variable vsd($g:xformat) \
    -command [list chartMenuTime $g] -font boldMainWindowFont
  $mtime add radio -label "Month/Day Hour:Minute:Second" -value "date" \
    -variable vsd($g:xformat) \
    -command [list chartMenuTime $g] -font boldMainWindowFont

  $mf.chart.m add check -label "Show Legend" \
    -command [list chartShowLegend $g] \
    -variable vsd($g:showlegend) -font boldMainWindowFont
  $mf.chart.m add cascade -label "Time Format" -menu $mtime -underline 0 -font boldMainWindowFont
  $mf.chart.m add check -label "Show Time Axis Title" \
    -command [list chartAxisTitle $g "xaxis"] \
    -variable vsd($g:xaxis:showtitle) -font boldMainWindowFont
  $mf.chart.m add check -label "Show Left Axis Title" \
    -command [list chartAxisTitle $g "yaxis"] \
    -variable vsd($g:yaxis:showtitle) -font boldMainWindowFont
  $mf.chart.m add check -label "Show Right Axis Title" \
    -command [list chartAxisTitle $g "y2axis"] \
    -variable vsd($g:y2axis:showtitle) -font boldMainWindowFont
  $mf.chart.m add check -label "Show Current Values" \
    -command [list chartShowCurXY $w $mf] \
    -variable vsd($w:showcurxy) -font boldMainWindowFont
  $mf.chart.m add check -label "Show Min and Max" \
    -command [list chartShowMinMax $w $mf] \
    -variable vsd($w:showminmax) -font boldMainWindowFont
  $mf.chart.m add check -label "Show Line Stats" \
    -variable vsd($w:showlinestats) -font boldMainWindowFont
  $mf.chart.m add check -label "Show CrossHairs" \
    -command [list chartShowCrossHairs $g] \
    -variable vsd($g:showcrosshairs) -font boldMainWindowFont
  $mf.chart.m add check -label "Show Grid Lines" \
    -command [list chartShowGridLines $g] \
    -variable vsd($g:showgridlines) -font boldMainWindowFont
  $mf.chart.m add separator
  $mf.chart.m add command -label "Close   " -command "closeGraph $w $g" -font boldMainWindowFont
  pack $mf.chart -side left
  set widgetHelp($mf.chart) {Chart Window Chart Menu}

  menubutton $mf.line -menu $mf.line.m -text "Line" -underline 0 \
    -takefocus 0 -state disabled -font boldMainWindowFont
  menu $mf.line.m -tearoff 0
  $mf.line.m add command -label "Log Info" -underline 4 \
    -command [list lineInfo $g] -font boldMainWindowFont
  # 46251 - Make Log Delta behave like Compare Two Points
  $mf.line.m add command -label "Log Delta" \
    -command [list chartCompare $g] \
    -accelerator "Ctrl+d" -font boldMainWindowFont
  $mf.line.m add command -label "Compute Scale" \
    -command [list lineComputeScale $g] \
    -accelerator "Ctrl+s" -font boldMainWindowFont
  $mf.line.m add command -label "Unscale" \
    -command [list lineUnscale $g] \
    -accelerator "Ctrl+n" -font boldMainWindowFont
  $mf.line.m add check -label "Graph on Left Axis" \
    -command [list lineGraphOnLeft $g] \
    -variable vsd($g:graphonleft) \
    -accelerator "Ctrl+a" -font boldMainWindowFont

  set msym $mf.line.m.symbols
  set widgetHelp($msym) {Line Menu Symbol SubMenu}
  menu $msym -tearoff 0
  set vsd($g:linesym) none
  foreach s $vsd(symbollist) {
    $msym add radio -label $s -variable vsd($g:linesym) \
      -command [list lineMenuSymbol $g] -font boldMainWindowFont
  }
  $mf.line.m add cascade -label "Symbol" -menu $msym -underline 0 -font boldMainWindowFont
  set mstyle $mf.line.m.styles
  set widgetHelp($mstyle) {Line Menu Sytle SubMenu}
  menu $mstyle -tearoff 0
  set vsd($g:linestyle) linear
  foreach style {linear step natural quadratic} {
    $mstyle add radio -label $style -variable vsd($g:linestyle) \
      -command [list lineMenuStyle $g] -font boldMainWindowFont
  }
  $mf.line.m add cascade -label "Style" -menu $mstyle -font boldMainWindowFont
  $mf.line.m add command -label "Update" \
    -command [list lineUpdate $g] \
    -accelerator "Ctrl+u" -font boldMainWindowFont

  $mf.line.m add separator
  $mf.line.m add command -label "Add Lines" \
    -command [list lineAdd $g] \
    -accelerator "Ctrl++" -font boldMainWindowFont
  $mf.line.m add command -label "Diff Lines" \
    -command [list lineDiff $g] \
    -accelerator "Ctrl+-" -font boldMainWindowFont
  $mf.line.m add command -label "Divide Lines" \
    -command [list lineDivide $g] \
    -accelerator "Ctrl+/" -font boldMainWindowFont


  # Request 48221
  $mf.line.m add separator
  set mchgint $mf.line.m.chgint
  menu $mchgint -tearoff 0
  $mchgint add command -label "Signed 32 bit" \
    -command [list changeIntTypeForLine $g "int32"] -font boldMainWindowFont
  $mchgint add command -label "Unsigned 32 bit" \
    -command [list changeIntTypeForLine $g "uint32"] -font boldMainWindowFont
  $mchgint add command -label "Signed 64 bit" \
    -command [list changeIntTypeForLine $g "int64"] -font boldMainWindowFont
  $mchgint add command -label "Unsigned 64 bit" \
    -command [list changeIntTypeForLine $g "uint64"] -font boldMainWindowFont
  $mf.line.m add cascade -label "Change Integer Type" -menu $mchgint -font boldMainWindowFont
  
  $mf.line.m add separator
  $mf.line.m add check -label "Normalize" \
    -command [list lineNormalize $g] \
    -variable vsd($g:normalized) -font boldMainWindowFont
  $mf.line.m add command -label "Trim Left" \
    -command [list lineTrimLeft $g] -font boldMainWindowFont
  $mf.line.m add command -label "Trim Right" \
    -command [list lineTrimRight $g] -font boldMainWindowFont
  $mf.line.m add command -label "Untrim Left" \
    -command [list lineUntrimLeft $g] -font boldMainWindowFont
  $mf.line.m add command -label "Untrim Right" \
    -command [list lineUntrimRight $g] -font boldMainWindowFont

  $mf.line.m add separator
  $mf.line.m add command -label "Copy" -command "lineCopy $g" \
    -accelerator "Ctrl+c" -font boldMainWindowFont
  $mf.line.m add command -label "Cut" -command "lineCut $g" \
    -accelerator "Ctrl+x" -font boldMainWindowFont
  $mf.line.m add command -label "Delete" -command "lineDelete $g" \
    -accelerator "Del" -font boldMainWindowFont

  pack $mf.line -side left
  set widgetHelp($mf.line) {Chart Window Line Menu}

# request 48366
  menubutton $mf.alllines -menu $mf.alllines.m -text "All Lines" -underline 0 \
    -takefocus 0 -state normal -font boldMainWindowFont
  menu $mf.alllines.m -tearoff 0
  $mf.alllines.m add command -label "No Filter" \
    -command [list allLinesFilter $g "none"] -font boldMainWindowFont
  $mf.alllines.m add command -label "Per Second"  \
    -command [list allLinesFilter $g "persecond"] -font boldMainWindowFont
  $mf.alllines.m add command -label "Per Sample"  \
    -command [list allLinesFilter $g "persample"] -font boldMainWindowFont
  $mf.alllines.m add command -label "Aggregate"  \
    -command [list allLinesFilter $g "aggregate"] -font boldMainWindowFont  
  pack $mf.alllines -side left
# end request 48366  
  
  tixLabelFrame $mf.curx -label "X:" -labelside left
  [$mf.curx subwidget label] configure -font vsdsf
  set f [$mf.curx subwidget frame]
  label $f.l -text "" -font vsdsf
  pack $f.l
  set widgetHelp($mf.curx) {Chart Window X: Display}
  $vsd(balloon) bind $mf.curx -msg "Show time offset of data at mouse pointer"

  tixLabelFrame $mf.cury -label "Y:" -labelside left
  [$mf.cury subwidget label] configure -font vsdsf
  set f [$mf.cury subwidget frame]
  label $f.l -text "" -font vsdsf
  pack $f.l
  set widgetHelp($mf.cury) {Chart Window Y: Display}
  $vsd(balloon) bind $mf.cury -msg "Show statistic value of data at mouse pointer"

  tixLabelFrame $mf.min -label "Min:" -labelside left
  [$mf.min subwidget label] configure -font vsdsf
  set f [$mf.min subwidget frame]
  label $f.l -text "" -font vsdsf
  set widgetHelp($mf.min) {Chart Window Min: Display}
  pack $f.l
  tixLabelFrame $mf.max -label "Max:" -labelside left
  [$mf.max subwidget label] configure -font vsdsf
  set f [$mf.max subwidget frame]
  label $f.l -text "" -font vsdsf
  set widgetHelp($mf.max) {Chart Window Max: Display}
  pack $f.l

  frame $mf.linef -bd 0

  set vsd($g:ctrlistid) {}

  tixComboBox $mf.linef.counter -dropdown true -editable false -value "" \
    -listcmd [list chartComputeCounters $g $mf] \
    -browsecmd chartBrowseCounters \
    -command [list chartChangeCounter $g] \
    -state disabled	-options {
      entry.width 18
      listbox.height 4
      listbox.width 33
    }
  set widgetHelp($mf.linef.counter) {Chart Window Statistic ComboBox}
  [$mf.linef.counter subwidget entry] configure -font vsdsf
  [$mf.linef.counter subwidget listbox] configure -font vsdsf
  bind [$mf.linef.counter subwidget entry] <KeyPress> \
    [list extraFindByFirstLetter $mf.linef.counter %A $g $mf]
  bind [$mf.linef.counter subwidget entry] <Control-s> \
    [list extraStartSearch $mf.linef.counter searchComboBox $g $mf]
  $vsd(balloon) bind $mf.linef.counter -msg "Change the line's statistic"

  set fw $mf.linef.filtertype
  tixOptionMenu $fw -command [list lineFilter $g] \
    -state disabled -dynamicgeometry 1
  set widgetHelp($fw) {Chart Window Filter MenuButton}
  [$fw subwidget menubutton] configure -font vsdsf
  [$fw subwidget menu] configure -font vsdsf
  $fw configure -disablecallback true
  $fw add command none -label "No Filter"
  $fw add command persecond -label "PerSecond"
  $fw add command persample -label "PerSample"
  #    $fw add command smooth -label "Smooth"
  $fw add command aggregate -label "Aggregate"
  $fw configure -disablecallback false
  $vsd(balloon) bind $fw -msg "Change the line's filter"

  
  set ow $mf.linef.opMenu
  tixOptionMenu $ow -command [list changeConstOp $g] -variable vsd(constOp) \
    -state disabled
  set widgetHelp($ow) {Chart Window Operator MenuButton}
  $vsd(balloon) bind $ow -msg "Select (S)cale, /, *, +, or -"
  [$ow subwidget menubutton] configure -font vsdsf -indicatoron 0 -padx 1
  [$ow subwidget menu] configure -font vsdsf
  $ow configure -disablecallback true
  $ow add command scale -label "S"
  $ow add command divide -label "/"
  $ow add command multiply -label "*"
  $ow add command add -label "+"
  $ow add command subtract -label "-"
  $ow configure -disablecallback false
  tixControl $mf.linef.const -label ":" -state disabled \
    -autorepeat false \
    -incrcmd lineIncrConst -decrcmd lineDecrConst \
    -validatecmd [list lineValidateConst $g] \
    -command [list lineChangeConst $g]
  set widgetHelp($mf.linef.const) {Chart Window Number Entry}
  [$mf.linef.const subwidget label] configure -font vsdsf
  [$mf.linef.const subwidget entry] configure -font vsdsf -width 7

  $vsd(balloon) bind $mf.linef.const -msg "Change the constant the line is modified with"

  pack $mf.linef.counter $fw $ow $mf.linef.const -side left ;# -padx 2
  pack $mf.linef -side right
  if {$vsd($w:showcurxy)} {
    pack $mf.cury $mf.curx -after $mf.linef -side right -padx 2
  }
  if {$vsd($w:showminmax)} {
    pack $mf.max $mf.min -after $mf.linef -side right -padx 2
  }

  graph $g -title "" -topmargin 3 -leftmargin 2 -rightmargin 2 -takefocus 1 \
    -plotbackground $vsd(currentPlotBgColor) -cursor $vsd(graphcursor)

  $g pen configure "activeLine" -color $vsd(activecolor) -fill "" -symbol ""
  #      -outline [lindex $vsd(colorlist) $cidx]
  set widgetHelp($g) {Chart Window Graph}
  hideAxis $g yaxis
  hideAxis $g y2axis

  if {! $isWindows} {
    bind $g <Control-p> [list $mf.chart.m invoke "Print"]
  }
  bind $g <Control-z> [list $mf.chart.m invoke "Zoom In"]
  bind $g <Control-S> [list $mf.chart.m invoke "Compute Scale All"]
  #    bind $g <Control-c> [list $mf.chart.m invoke "Compare Two Points"]
  bind $g <Control-N> [list $mf.chart.m invoke "Unscale All"]
  bind $g <Control-plus> [list $mf.line.m invoke "Add Lines"]
  bind $g <Control-minus> [list $mf.line.m invoke "Diff Lines"]
  bind $g <Control-slash> [list $mf.line.m invoke "Divide Lines"]
  bind $g <Control-d> [list $mf.line.m invoke "Log Delta"]
  bind $g <Control-s> [list $mf.line.m invoke "Compute Scale"]
  bind $g <Control-n> [list $mf.line.m invoke "Unscale"]
  bind $g <Control-a> [list $mf.line.m invoke "Graph on Left Axis"]
  bind $g <Control-u> [list $mf.line.m invoke "Update"]
  bind $g <Delete> [list $mf.line.m invoke "Delete"]
  bind $g <Left> [list ZoomHorizontal $g -2]
  bind $g <Right> [list ZoomHorizontal $g +2]
  bind $g <Control-Left> [list ZoomHorizontalPage $g -1]
  bind $g <Control-Right> [list ZoomHorizontalPage $g +1]
  #    bind $g <Up> [list ZoomVertical $g +1]
  #    bind $g <Down> [list ZoomVertical $g -1]
  bind $g <<Paste>> [list $mf.chart.m invoke "Paste"]
  bind $g <<Copy>> [list $mf.line.m invoke "Copy"]
  bind $g <<Cut>> [list $mf.line.m invoke "Cut"]

  if {1} {
    $g configure -font vsdsf
    $g xaxis configure -tickfont vsdsf -titlefont vsdsf  -background $vsd(currentBgColor)
    $g yaxis configure -tickfont vsdsf -titlefont vsdsf  -background $vsd(currentBgColor)
    $g y2axis configure -tickfont vsdsf -titlefont vsdsf -background $vsd(currentBgColor)
    # 46253, add commas to numbers
    $g yaxis configure -command formatTick
    $g y2axis configure -command formatTick
  }
  chartMenuTime $g
  chartAxisTitle $g yaxis
  chartAxisTitle $g y2axis
  
  $g legend configure -position bottom -anchor w -font vsdsf -activeforeground \
    $vsd(activecolor) -activerelief raised -background $vsd(currentBgColor)
  mapLegend $g $vsd($g:showlegend)

  if {$vsd($g:showcrosshairs)} {
    Blt_CrosshairsOn $g
  }
  $g grid configure -dashes {4 6}
  if {$vsd($g:showgridlines)} {
    $g grid on
  }
  Blt_ZoomStack $g
  Blt_ActiveGraph $g

  pack $mf -side top -fill x
  pack $g -side bottom -fill both -expand yes
  wm protocol $w WM_DELETE_WINDOW "closeGraph $w $g"

  # 45882 - try to keep labels away from lines
  if {$isWindows} {
    set yshift -22
  } else {
    set yshift -20
  }
  $g marker create text -name activeLine -text "" \
    -coords {-Inf Inf} -anchor nw -bg {} -under 0 -font vsdsf -yoffset $yshift
  $g marker create bitmap -name triStart -under 1 \
    -fg $vsd(activecolor) \
    -fill "" -yoffset 6
  $g marker create bitmap -name triEnd -under 1 \
    -fg $vsd(activecolor) \
    -fill "" -yoffset 6

  # 44955 - Make right click open pop-up menu to copy Y to clipboard
  set m [menu $g.popupMenu -font vsdsf -tearoff 0]
  $m add command -label "Copy Y Value" -command copyYvalue

  # The <Configure> type can be used to get resize and window move notification
  # bind $w <Configure> {+puts "Configure %W %R %S [wm geometry %W]"}
  setMasterBgColorForWindow $w
  return $w
}

proc getFreeGraphSlot {gw} {
  set elements [$gw.graph element names]
  if {[llength $elements] == 0} {
    return 1
  } else {
    set slot [lindex $elements 0]
    foreach e $elements {
      if {$e > $slot} {
	set slot $e
      }
    }
    incr slot
    return $slot
  }
}

# statFilter returns 1 if the line is ok, 0 if the filter does not want it
proc statFilter {line filter} {
  global vsd
  set stats [$line -stats {} {}]
  #stats = {count min max average stddev}
  set fcount [lindex $filter 0]
  if {$fcount != {}} {
    if {[llength $fcount] == 2} {
      # we only wants lines who have the number of samples in the range
      set actualCount [lindex $stats 0]
      if {$actualCount < [lindex $fcount 0] ||
	  $actualCount > [lindex $fcount 1]} {
	return 0
      }
    } elseif {$fcount < 0} {
      # we only wants lines who have less samples than the filter
      set fcount [expr {- $fcount}]
      if {[lindex $stats 0] > $fcount} {
	# to many samples so reject it
	return 0
      }
    } else {
      # we only wants lines who have more samples than the filter
      if {[lindex $stats 0] < $fcount} {
	# not enough samples so reject it
	return 0
      }
    }
  }
  set fmin [lindex $filter 1]
  if {$fmin != {}} {
    if {[llength $fmin] == 2} {
      # we only wants lines who have a min in the range
      set actualMin [lindex $stats 1]
      if {$actualMin < [lindex $fmin 0] ||
	  $actualMin > [lindex $fmin 1]} {
	return 0
      }
    } elseif {$fmin < 0} {
      # we only wants lines whose min is less than the filter
      if {[lindex $stats 1] > $fmin} {
	# smallest item is larger than the min filter
	return 0
      }
    } else {
      # we only wants lines whose min is greater than the filter
      if {[lindex $stats 1] < $fmin} {
	# smallest item is less than the min filter
	return 0
      }
    }
  }
  set fmax [lindex $filter 2]
  if {$fmax != {}} {
    if {[llength $fmax] == 2} {
      # we only wants lines who have a max in the range
      set actualMax [lindex $stats 2]
      if {$actualMax < [lindex $fmax 0] ||
	  $actualMax > [lindex $fmax 1]} {
	return 0
      }
    } elseif {$fmax < 0} {
      set fmax [expr {- $fmax}]
      # we only wants lines whose max is less than the filter
      if {[lindex $stats 2] > $fmax} {
	# largest item is greater than the max filter
	return 0
      }
    } else {
      # we only wants lines whose max is greater than the filter
      if {[lindex $stats 2] < $fmax} {
	# largest item is less than the max filter
	return 0
      }
    }
  }
  set fmean [lindex $filter 3]
  if {$fmean != {}} {
    if {[llength $fmean] == 2} {
      # we only wants lines who have a mean in the range
      set actualMean [lindex $stats 3]
      if {$actualMean < [lindex $fmean 0] ||
	  $actualMean > [lindex $fmean 1]} {
	return 0
      }
    } elseif {$fmean < 0} {
      set fmean [expr {- $fmean}]
      # we only wants lines whose mean is less than the filter
      if {[lindex $stats 3] > $fmean} {
	# average item is greater than the mean filter
	return 0
      }
    } else {
      # we only wants lines whose mean is greater than the filter
      if {[lindex $stats 3] < $fmean} {
	# average item is less than the mean filter
	return 0
      }
    }
  }
  set fstddev [lindex $filter 4]
  if {$fstddev != {}} {
    if {[llength $fstddev] == 2} {
      # we only wants lines who have a stddev in the range
      set actualSd [lindex $stats 3]
      if {$actualSd < [lindex $fstddev 0] ||
	  $actualSd > [lindex $fstddev 1]} {
	return 0
      }
    } elseif {$fstddev < 0} {
      set fstddev [expr {- $fstddev}]
      # we only wants lines whose stddev is less than the filter
      if {[lindex $stats 4] > $fstddev} {
	# line stddev is greater than the stddev filter
	return 0
      }
    } else {
      # we only wants lines whose stddev is greater than the filter
      if {[lindex $stats 4] < $fstddev} {
	# line stdev is less than the stddev filter
	return 0
      }
    }
  }
  return 1
}

proc lineMin {l} {
  set yvec [lindex [$l -info] 3]
  global $yvec
  return [set ${yvec}(min)]
}

proc lineMax {l} {
  set yvec [lindex [$l -info] 3]
  global $yvec
  return [set ${yvec}(max)]
}

proc addLineToChart {line new activate mapaxis symbol {style {}}} {
  global vsd

  if {! $new} {
    if {![info exists vsd(ct:$vsd(curchart))]} {
      setStatus "no title for $vsd(curchart)"
      bell
      return
      #	    set new 1
    }
  }
  set lineinfo [$line -info]
  set lineyv [lindex $lineinfo 3]
  global $lineyv
  set linexv [lindex $lineinfo 2]
  global $linexv

  if {$new} {
    set gw [createGraphWindow $vsd(curchart)]
    if {$new == 2} {
      startLongOp $gw
    }
  } else {
    set gw $vsd(ct:$vsd(curchart))
    if {![winfo exists $gw]} {
      if {$vsd(longOpW) != ""} {
	cancelLongOp
      } else {
	setStatus "missing chart window $gw"
	bell
      }
      return
    }
  }
  raise $gw
  wm deiconify $gw

  set numColors [llength $vsd(colorlist)]
  set i [getFreeGraphSlot $gw]
  set cidx [expr {($i - 1) % $numColors}]
  set dashes [expr {(($i - 1) / $numColors) * 2}]
  if {$cidx >= $numColors} {
    set cidx 0
    incr dashes 2
  }
  set g $gw.graph
  set vsd(ed:$i:$g) $line
  showAxis $g ${mapaxis}axis
  if {[string equal $mapaxis "y"]} {
    $g configure -leftmargin 0
  } else { ;# y2 axis
    $g configure -rightmargin 0
  }
  set label [getLineLegendLabel $i $lineinfo]
  if {$style == {}} {
    set style $vsd(def:linestyle)
  }
  $g element create $i -label $label -mapy $mapaxis -symbol $symbol \
    -xdata $linexv -ydata $lineyv -dashes $dashes \
    -color [lindex $vsd(colorlist) $cidx] \
    -outline [lindex $vsd(colorlist) $cidx] \
    -pixels 2 -fill "" -smooth $style

  setMasterBgColorForWindow $g

  # The following is done to get the graph to redraw.
  #    puts "DEBUG: [lsort -integer [$g element names]]"
  #    $g element show [lsort -integer [$g element names]]
  if {$activate} {
    activateElement $g $i 0
  }
}

proc createOperatorLine {lineList op copyChildren {filter "default"} {scale 1}
			 {divider 1} {offset 0} {normalized 0}
			 {statFilter {}}} {
  global vsd
  if [catch {sl_create -derivedline $lineList $op $filter $copyChildren} line] {
    setStatus $line
    bell
    return {}
  }
  if {$scale != 1} {
    $line -scale $scale
  }
  if {$divider != 1} {
    $line -divider $divider
  }
  if {$normalized} {
    $line -normalize
  } elseif {$offset != 0} {
    $line -adder [expr {- $offset}]
  }
  if {$statFilter != {}} {
    if {! [statFilter $line $statFilter]} {
      $line -free
      return {}
    }
  }
  return $line
}

proc addDerivedLine {lineList op {filter "default"} {scale 1} {mapaxis "y"}} {
  global vsd
  replayLog "addDerivedLine $lineList $op $filter $scale $mapaxis"
  set line [createOperatorLine $lineList $op $vsd(copyChildren) $filter $scale]
  if {$line != {}} {
    addLineToChart $line 0 1 $mapaxis "none"
  }
}

proc createSimpleLine {instList counter
		       {filter "default"} {scale 1}
		       {divider 1} {offset 0} {normalized 0}
		       {statFilter {}}} {
  global vsd

  set filter [getFilterForStatName $counter $filter]
  # 48010 - also pass counter as the last arg. counter is the name of the
  #         the counter including any alias name.
  set line [sl_create -line $instList [statFromAlias $counter] \
	      $filter $vsd(absoluteTSMode) $counter]
  if {$vsd(noFlatlines)} {
    if {[lineMax $line] == 0 && [lineMin $line] == 0} {
      $line -free
      return {}
    }
  }
  if {$scale != 1} {
    $line -scale $scale
  }
  if {$divider != 1} {
    $line -divider $divider
  }
  if {$normalized} {
    $line -normalize
  } elseif {$offset != 0} {
    $line -adder [expr {- $offset}]
  }
  if {$statFilter != {}} {
    if {! [statFilter $line $statFilter]} {
      $line -free
      return {}
    }
  }
  return $line
}

proc doOneCounter {new instList activate counter
                   {filter "default"} {scale 1} {mapaxis "y"}
		   {divider 1} {offset 0} {normalized 0}
                   {statFilter {}}} {
  global vsd
  set msg "doOneCounter $new $instList $activate $counter $filter $scale $mapaxis "
  set msg [string cat $msg "$divider $offset $normalized $statFilter"]
  replayLog $msg
  set line [createSimpleLine $instList $counter $filter $scale $divider \
	      $offset $normalized $statFilter]
  if {$line != {}} {
    addLineToChart $line $new $activate $mapaxis "none"
    return 1
  } else {
    return 0
  }
}

proc addToChart {new procs ctrs} {
  global vsd

  set count 0
  disableAutoUpdates
  set fileCount [llength [sl_stat -enabledfiles]]
  if {[llength $ctrs] == 1 && ([llength $procs] == 1 || \
			       ($vsd(combineInstances) && \
				  ($vsd(combineFiles) || \
				     $fileCount == 1)))} {
    set activate 1
  } else {
    set activate 0
  }
  
  set chartname [$vsd(w:chartlist) cget -selection]
  if {![string equal $chartname $vsd(curchart)]} {
    set vsd(curchart) $chartname
  }
  if {!$activate} {
    if {$new} {
      # Setting new to 2 means that startLongOp should be done when
      # a window is created
      set new 2
    } else {
      set gw $vsd(ct:$vsd(curchart))
      startLongOp $gw
    }
  }
  if {$vsd(combineInstances)} {
    if {$vsd(combineFiles)} {   
      set instList {}
      foreach ientry $procs {
	set instData [$vsd(instanceList) info data $ientry]
	lappend instList [lindex $instData 6]
      }
      foreach ctr $ctrs {
	if {[doOneCounter $new $instList $activate $ctr]} {
	  incr count
	  set new 0
	  update
	  if {$vsd(longOpCancel)} {break}
	}
      }
      
    } else {
      foreach df [getSortedFiles] {
	set instList {}
	foreach ientry $procs {
	  set instData [$vsd(instanceList) info data $ientry]
	  if {[string equal $df [lindex $instData 5]]} {
	    lappend instList [lindex $instData 6]
	  }
	}
	if {$instList != {}} {
	  foreach ctr $ctrs {
	    if {[doOneCounter $new $instList $activate $ctr]} {
	      incr count
	      set new 0
	      update
	      if {$vsd(longOpCancel)} {break}
	    }
	  }
	}
	if {$vsd(longOpCancel)} {break}
      }
    }
  } else {
    foreach ientry $procs {
      set instData [$vsd(instanceList) info data $ientry]
      set instId [lindex $instData 6]
      foreach ctr $ctrs {
	if {[doOneCounter $new [list $instId] $activate $ctr]} {
	  incr count
	  set new 0
	  update
	  if {$vsd(longOpCancel)} {break}
	}
      }
      if {$vsd(longOpCancel)} {break}
    }
  }
  if {$count == 0} {
    if {$vsd(noFlatlines) && !$vsd(longOpCancel)} {
      # ring the bell since no lines were added due to flatlines
      setStatus "Line was not created because \"No Flatlines\" is enabled."
      bell
    }
  }
  enableAutoUpdates
  endLongOp
}

proc doChart {new} {
  global vsd

  set procs [getSelectedInstances]
  set ctrs [getSelCtr]
  if {$procs == {} || $ctrs == {}} {
    if {$new} {
      createGraphWindow {}
    }
  } else {
    if {!$new && $vsd(curchart) == {}} {
      set new 1
    }
    addToChart $new $procs $ctrs
  }
}

proc doExitVSD {} {
  global vsd
  # 46239 - only save if enabled
  if {$vsd(autoSaveConfig)} {
    writeStartupFile
  }
  foreach smon [array names vsd statmonpid:*] {
    catch {sl_kill $vsd($smon) 2}
  }
  destroy $vsd(w)
  exit
}

proc ExitVSD {} {
  global vsd

  if {! $vsd(confirmonexit)} {
    doExitVSD
  }

  set msgtext "Are you sure you want to exit?"

  if {[info commands tk_messageBox] != ""} {
    set response [tk_messageBox -title "Confirm Exit" \
		    -parent $vsd(w) -icon question -type yesno \
		    -default yes -message $msgtext]
    if {[string equal $response "yes"]} {
      doExitVSD
    }
  } else {
    set d [tixDialogShell .vsdexit -parent $vsd(w) -title "Confirm Exit"]

    frame $d.top -relief raised -bd 1
    pack $d.top -side top -fill both

    label $d.top.msg -text $msgtext
    pack $d.top.msg -side right -expand 1 -fill both -padx 3m -pady 3m
    label $d.top.bitmap -bitmap "question"
    pack $d.top.bitmap -side left -padx 3m -pady 3m
    

    tixButtonBox $d.box
    $d.box add yes -text Yes -command doExitVSD
    $d.box add no -text No -command {destroy .vsdexit}

    pack $d.box -fill x -expand yes

    $d popup
    focus [$d.box subwidget yes]
  }
}

proc ChangeXAuto {w} {
  global vsd

  if {$vsd(xautoscale)} {
    $w.min configure -state disabled
    $w.max configure -state disabled
  } else {
    $w.min configure -state normal
    $w.max configure -state normal
  }
}

proc ChangeYAuto {w} {
  global vsd

  if {$vsd(yautoscale)} {
    $w.min configure -state disabled
    $w.max configure -state disabled
  } else {
    $w.min configure -state normal
    $w.max configure -state normal
  }
}

proc getSelCtr {} {
  global vsd
  set res {}
  foreach i [$vsd(w:counterHList) info selection] {
    lappend res [$vsd(w:counterHList) info data $i]
  }
  return $res
}

proc clearCounters {} {
  global vsd
  set selCtr [getSelCtr]
  if {$selCtr != {}} {
    set vsd(lastSelCtr) $selCtr
  }
  $vsd(w:counterHList) delete all
  set vsd(selectedCtr) {}
  set vsd(counterType) {}
  set vsd(selectedInstIds) {}
  set vsd(selectedProcs) {}
  set vsd(zeroCtrs) {}
}

proc showCounter {c} {
  global vsd

  if {$c == {}} {
    return
  }
  set vsd(lastctr) $c
  $vsd(doctext:w) configure -state normal
  $vsd(doctext:w) delete 1.0 end
  set ctype [getStatType $c]
  if {[string equal -length 6 $ctype "uvalue"]} {
    set ctype "unsigned value"
  } elseif {[string equal -length 6 $ctype "svalue"]} {
    set ctype "signed value"
  }
  $vsd(ctrtype:w) configure -text $ctype
  $vsd(ctrunits:w) configure -text [getStatUnits $c]
  set objList [getStatObjList $c]
  if {[string length $objList] == 0} {
    $vsd(ctrobjs:w) configure -text "unknown"
  } else {
    $vsd(ctrobjs:w) configure -text $objList
  }

  $vsd(ctrfilter:w) configure -disablecallback true -state normal
  set vsd(ctrfilter) [getStatFilter $c]
  $vsd(ctrfilter:w) configure -disablecallback false
  set counterDoc [getPlainStatDescription $c]
  if {$counterDoc != {}} {
    $vsd(doctext:w) insert 1.0 $counterDoc
  }
  $vsd(doctext:w) configure -state disabled
}

# Print the vsd call stack, which excludes tk and tix cruft by default
proc printVsdCallStack {{includeCruft 0}} {
  set fr [info frame]
  incr fr -1 
  set theProc [dict get [info frame $fr] proc]
  puts "Printing Tcl call stack to proc $theProc"
  incr fr -1 
  while {$fr > 0} {
    set theInfo [info frame $fr]
    if {![dict exists $theInfo proc]} {
      break
    }
    set theProc [dict get $theInfo proc]
    if {$includeCruft == 0} {
      if {[string match ::tix* $theProc]} {
	break ;# skip all the ::tix cruft
      }
      if {[string match ::tk* $theProc]} {
	break ;# skip all the ::tk cruft
      }
    }
    set line [dict get $theInfo line]
    puts "  $theProc at $line"
    incr fr -1
  }
  puts "End of Tcl call stack"
}

proc buildCallStack {} {
  global errorInfo
  if {[info exists errorInfo]} {
    set result "\nerrorInfo is\n$errorInfo\n"
  } else {
    set result "\nerrorInfo is not available\n"
  }
  
  set fr [info frame]
  incr fr -1
  set result [string cat $result "\n\nBegin Tcl Call Stack\n"]
  while {$fr > 0} {
    set theInfo [info frame $fr]
    if {![dict exists $theInfo proc]} {
      break
    }
    set file ""
    if {[dict exists $theInfo file]} {
      set file "in [dict get $theInfo file]"
    }
    set theProc [dict get $theInfo proc]
    set line [dict get $theInfo line]
    set result [string cat $result  "  $theProc at $line $file\n"]
    incr fr -1
  }
  set result [string cat $result "End Tcl Call Stack\n"]
  return $result
}

proc showCounterInfo {c} {
  global vsd
  
  if {$c == {}} {
    return
  }

  set oldctr $vsd(lastctr)
  if {[string equal $c $vsd(lastctr)]} {
    return
  }

  set vsd(lastctr) $c
  if {![string equal withdrawn [wm state .ctrinfo]]} {
    if {[info exists vsd(ctrbox:w)] && [winfo exists $vsd(ctrbox:w)]} {
      set lb [$vsd(ctrbox:w) subwidget listbox]
      set idx 0
      foreach ctr [$lb get 0 end] {
	if {[string equal $c $ctr]} {
	  $vsd(ctrbox:w) pick $idx
	  break
	}
	incr idx
      }
    }
  }
}

proc buildAppendedFileListString {fileInfo {indent 0}} {
  set fileList [lindex $fileInfo 16]
  set str ""
  set idx 0
  set spaces [string repeat " " $indent]
  foreach fnlist $fileList {
    if { $idx > 0} {
      set str [string cat $str "\n"]
    }
    set fn [lindex $fnlist 0]
    set ts [lindex $fnlist 1]
    set str [string cat $str $spaces "$fn, starting: $ts"]
    incr idx
  }
  return $str
}

proc fileLogInfo {} {
  global vsd appendedFileMap
  if {$vsd(statsrc) == {}} {
    return
  }

  set info [$vsd(statsrc) -info]
  set filename [file tail [lindex $info 0]]
  set gsver "[lindex $info 3] for [lindex $info 2]"
  set machine [lindex $info 4]
  set interval [lindex $info 6]
  if {$interval < 1000} {
    set units "millisecond"
  } else {
    set units "second"
    set interval [expr {$interval / 1000}]
  }
  set date "[lindex $info 5] in $interval $units intervals"
  set firstTS [lindex $info 9]
  set trimLeftTS [lindex $info 10]
  set trimRightTS [lindex $info 11]

  set fileName [getFileInfoName $vsd(statsrc)]
  if {$appendedFileMap($fileName) == 1} {
    set str [buildAppendedFileListString $info 2]
    set fileData "\nAppended Files:\n$str\nGemstone Version: $gsver\nMachine: $machine\nTime: $date"
  } else {
    set fileData "\nFile: [lindex $info 0]\nGemstone Version: $gsver\nMachine: $machine\nTime: $date"
  }
  if {$firstTS != $trimLeftTS} {
    append fileData "\nTrimLeft: [sl_stat -getdate $trimLeftTS $vsd(time_shift_hours) date]"
  }
  if {$trimRightTS != -1} {
    append fileData "\nTrimRight: [sl_stat -getdate $trimRightTS $vsd(time_shift_hours) date]"
  }
  
  showData $fileData
}

proc getFileKind {kind} {
  if {$kind == 0} {
    return "plain text"
  }
  if {$kind == 1} {
    return "gzip-compressed"
  }
  if {$kind == 2} {
    return "lz4-compressed"
  }
  if {$kind == 4} {
    return "GFS binary"
  }
  return "unknown"
}

proc fileInfo {} {
  global vsd widgetHelp appendedFileMap
  if {$vsd(statsrc) == {}} {
    return
  }

  set info [$vsd(statsrc) -info]
  set filename [file tail [lindex $info 0]]
  set gsver "[lindex $info 3] for [lindex $info 2]"
  set machine [lindex $info 4]
  set interval [lindex $info 6]
  # cmdLine may be empty string for older versions of statmonitor
  set cmdLine [lindex $info 14] 
  if {$interval < 1000} {
    set units "millisecond"
  } else {
    set units "second"
    set interval [expr {$interval / 1000}]
  }
  set date "timestamps since [lindex $info 5] in $interval $units intervals"

  set w .vsdfileinfo
  if [winfo exists $w] {
    regexp {(.*x.*)(\+.*\+.*)} [wm geometry $w] junk parentSize position
    destroy $w
  } else {
    regexp {(.*x.*)(\+.*\+.*)} [wm geometry $vsd(w)] junk parentSize position
  }
  
  set widgetHelp($w) {File Information Window}
  toplevel $w
  wm geometry $w $position
  wm title $w "info on $filename"

  frame $w.top -relief raised -bd 1

  tixLabelFrame $w.top.gsver -label "GemStone Version"
  set f [$w.top.gsver subwidget frame]
  label $f.l -text $gsver -font fixedTextFont
  pack $f.l -side left
  pack $w.top.gsver -fill x -expand yes -side top

  tixLabelFrame $w.top.machine -label "Machine Information"
  set f [$w.top.machine subwidget frame]
  label $f.l -text $machine -font fixedTextFont
  pack $f.l -side left
  pack $w.top.machine -fill x -expand yes -side top

  tixLabelFrame $w.top.time -label "Time Information"
  set f [$w.top.time subwidget frame]
  label $f.l -text $date -font fixedTextFont
  pack $f.l -side left
  pack $w.top.time -fill x -expand yes -side top
  
  set compStr [getFileKind [lindex $info 12]]
  tixLabelFrame $w.top.comp -label "File Format"
  set f [$w.top.comp subwidget frame]
  label $f.l -text $compStr -font fixedTextFont
  pack $f.l -side left
  pack $w.top.comp -fill x -expand yes -side top

  # 48594 - show statmonitor command line args if available
  if {[string length $cmdLine] > 0} {
    tixLabelFrame $w.top.cmdLine -label "Statmonitor Command Line"
    set f [$w.top.cmdLine subwidget frame]
    label $f.l -text $cmdLine -font fixedTextFont
    pack $f.l -side left
    pack $w.top.cmdLine -fill x -expand yes -side top
  }

  # 50441 - list any appended files here
  set fileName [getFileInfoName $vsd(statsrc)]
  if {$appendedFileMap($fileName) == 1} {
    set str [buildAppendedFileListString $info]
    tixLabelFrame $w.top.fileList -label "Appended Files"
    set f [$w.top.fileList subwidget frame]
    label $f.l -text $str -font fixedTextFont
    pack $f.l -side left
    pack $w.top.fileList -fill x -expand yes -side top
  }
  
  pack $w.top -side top -fill both -expand yes

  tixButtonBox $w.box
  $w.box add dismiss -text Dismiss -width 6 -command "destroy $w"
  $w.box add log -text "Log..." -width 6 -command fileLogInfo
  set but [$w.box subwidget dismiss]
  pack $w.box -fill x -side bottom
  setMasterBgColorForWindow $w
}

proc fileDump {} {
  global vsd
  if {$vsd(statsrc) != {}} {
    # NYI: hack implementation
    $vsd(statsrc) -dump
  }
}

proc singleFileMode {} {
  global vsd
  if {$vsd(singleFileMode)} {
    set vsd(lastEnabledFiles) [sl_stat -enabledfiles]
    foreach f $vsd(lastEnabledFiles) {
      if {![string equal $f $vsd(statsrc)]} {
	$f -disable
      }
    }
  } else {
    set files $vsd(lastEnabledFiles)
    if {$files == {}} {
      # enable them all
      set files [sl_stat -allfiles]
    } else {
      if {$vsd(statsrc) != {}} {
	lappend files $vsd(statsrc)
      }
    }
    foreach f $files {
      $f -enable
    }
  }
  setInstanceList
  updateGraphWidgetState
}

proc fileEnableMode {} {
  global vsd
  if {$vsd(statsrc) != {}} {
    if {$vsd(file:isEnabled)} {
      $vsd(statsrc) -enable
    } else {
      $vsd(statsrc) -disable
    }
    setInstanceList
    updateGraphWidgetState
  }
}

proc autoAppendMode {} {
  global vsd

  set f $vsd(statsrc)
  if {$f == {}} {
    return
  }

  set vsd(autoAppend:$f) $vsd(autoAppend)
  
  if {$vsd(autoAppend)} {
    if {![info exists vsd(autoUpdateId:$f)]} {
      if {! $vsd(autoUpdate)} {
	set vsd(autoUpdate) 1
	autoUpdateMode
      }
    }
  } 
}

proc fileUntrimLeft {f} {
  if {$f != {}} {
    $f -trimleft 0
  }
  updateFileTrimMenuItems [$f -info]
  updateAllLineTrimMenuItems
}

proc fileUntrimRight {f} {
  if {$f != {}} {
    $f -trimright -1
  }
  updateFileTrimMenuItems [$f -info]
  updateAllLineTrimMenuItems
}

proc fileUpdateAll {} {
  global vsd
  foreach f [sl_stat -enabledfiles] {
    fileUpdate $f
  }
}

proc fileUpdate {f} {
  global vsd isWindows
  set result 0
  if {$f != {} && $vsd(updateok:$f)} {
    if [catch {$f -update} updateCount] {
      set vsd(updateok:$f) 0
      set vsd(autoUpdate:$f) 0
      set vsd(autoAppend:$f) 0
      if [info exists vsd(autoUpdateId:$f)] {
	after cancel $vsd(autoUpdateId:$f)
	unset vsd(autoUpdateId:$f)
      }
      if {$f == $vsd(statsrc)} {
	$vsd(w:filemenu) entryconfigure "Update" -state disabled
	$vsd(w:filemenu) entryconfigure "Auto Update" -state disabled
	if {! $isWindows} {
	  $vsd(w:filemenu) entryconfigure "Auto Append Next File" -state disabled
	}
	set vsd(autoUpdate) 0
      }
      showError "Update failed because: $updateCount"
    } else {
#      printMicroSecTimeStamp "Finished update, got $updateCount"
      set result $updateCount
    }
    if {$result != 0} {
      # we read new data
      removeInactiveDataFile $f
      setLastUpdateTimeForDataFile $f
      set vsd(noUpdateCount:$f) 0
      if {$vsd(noFlatlines) || $vsd(noAllZeros)} {
	# NYI optimize this so it doesn't unconditionally zap everyone
	foreach idx [array names vsd {*:ctrlistid}] {
	  set vsd($idx) {}
	}
      }
      set isEnabled [lindex [$f -info] 7]
      if {$isEnabled} {
	foreach c $vsd(zeroCtrs) {
	  if {![sl_stat -ctrzero $c $vsd(selectedInstIds)]} {
	    fillCounters 1 [getSelectedInstances]
	    break
	  }
	}
	setInstanceList
	# Next line fixes 43834
	updateAllActiveLines
      }
    } else { # no updates this time
      if {[info exists vsd(autoUpdate:$f)]} {	
	if {$vsd(autoUpdate:$f) == 1} {
	  # we are auto updating, not just ctrl-u
	  incr vsd(noUpdateCount:$f)
	  showAllInstancesInactiveIfNeededForFile $f
	}
      }
    }
  }
  return $result
}

# Redraw the instance list with the Samples column shown in black
# instead of green.
proc showAllInstancesInactiveForFile {df} {
  addInactiveDataFile $df
  setInstanceList
}

proc doAutoUpdate {f t} {
  global vsd
  if {!$vsd(autoUpdatesDisabled)} {
    set numUpdates [fileUpdate $f]
    if {$numUpdates == 0} {
      if {$vsd(noUpdateCount:$f) > 2} {
        if {$vsd(autoAppend) == 1} {
	  # look for a new file to append to this one
	  set newFiles [newFilesInDir [getUniqueFileDirName $f]]
	  if {[llength $newFiles] > 0} {
	    disableAutoUpdates
	    foreach fn $newFiles {
	      set vsd(appendMode) 1
	      setDataFile $fn $f
	    }
	    enableAutoUpdates
	  }
	}
      }
    }
  }
 
  set vsd(autoUpdateId:$f) [after $t [list doAutoUpdate $f $t]]
}

proc disableAutoUpdates {} {
  global vsd
  set vsd(autoUpdatesDisabled) 1
}

proc enableAutoUpdates {} {
  global vsd
  set vsd(autoUpdatesDisabled) 0
}

proc clearStatus {} {
  global vsd
  set vsd(status) ""
}

proc setStatus {msg} {
  global vsd
  if [info exists vsd(statusTimeout)] {
    after cancel $vsd(statusTimeout)
    unset vsd(statusTimeout)
  }
  set vsd(status) $msg
  set vsd(statusTimeout) [after 10000 clearStatus]
#  puts $msg
}

proc autoUpdateMode {{f {}}} {
  global vsd
  if {$f == {}} {    
    set f $vsd(statsrc)
  }
  
  if {$f == {}} {
    return
  }
  
  if [info exists vsd(autoUpdateId:$f)] {
    after cancel $vsd(autoUpdateId:$f)
    unset vsd(autoUpdateId:$f)
  }

  set vsd(autoUpdate:$f) $vsd(autoUpdate)


  if {$vsd(autoUpdate)} {
    set interval $vsd(autoUpdateIntervalMs)
    doAutoUpdate $f $interval
  } else {
    showAllInstancesInactiveForFile $f
  }
}

# Called when we click on a cache in the Monitor... dialog.
proc monitorBrowse {e} {
  global vsd
  set w .monitors
  if {![winfo exists $w]} {
    return
  }

  set l [$w.top.monlist subwidget hlist]

  set data [$l info data $e]
  set name [lindex $data 0]
  set version [lindex $data 1]
  set gemstone [lindex $data 3]
  if [info exists vsd(statmonpid:$name)] {
    [$w.box subwidget start] configure -state disabled
    [$w.box subwidget status] configure -state normal
    [$w.box subwidget stop] configure -state normal

    if [info exists vsd(statmonoutput:$name)] {
      $w.statmonfile configure -state normal
      set vsd(wv:statmonfile) $vsd(statmonoutput:$name)
    }
    $w.statmonfile configure -state disabled

    if [info exists vsd(statmonargs:$name)] {
      $w.statmonargs configure -state normal
      set vsd(wv:statmonargs) $vsd(statmonargs:$name)
    }
    $w.statmonargs configure -state disabled
    
    if [info exists vsd(statmoninterval:$name)] {
      $w.ctr.statmoninterval configure -state normal
      set vsd(wv:statmoninterval) $vsd(statmoninterval:$name)
    }
    $w.ctr.statmoninterval configure -state disabled
    if [info exists vsd(statmonflush:$name)] {
      $w.ctr.statmonflush configure -state normal
      set vsd(wv:statmonflush) $vsd(statmonflush:$name)
    }
    $w.ctr.statmonflush configure -state disabled
    # New
    if [info exists vsd(statmonCompressGz:$name)] {
      $w.ctr.compressLabelFrame.gz configure -state normal
      set vsd(wv:statmonCompressGz) $vsd(statmonCompressGz:$name)
    }
    $w.ctr.compressLabelFrame.gz configure -state disabled
    # 45196 - Add lz4 support
    if [info exists vsd(statmonCompressLz4:$name)] {
      $w.ctr.compressLabelFrame.lz4 configure -state normal
      set vsd(wv:statmonCompressLz4) $vsd(statmonCompressLz4:$name)
    }
    $w.ctr.compressLabelFrame.lz4 configure -state disabled
  } else {
    set statmonlist {}
    [$w.box subwidget start] configure -state normal
    [$w.box subwidget status] configure -state disabled
    [$w.box subwidget stop] configure -state disabled
    set vsd(wv:statmonfile) ""
    $w.statmonfile configure -state normal
    #	set vsd(wv:statmonargs) ""
    $w.statmonargs configure -state normal	
    $w.ctr.statmoninterval configure -state normal
    $w.ctr.statmonflush configure -state normal
    $w.ctr.compressLabelFrame.gz configure -state normal
    # 45196 - Add lz4 support
    # Determine whether to enable the lz4 checkbox
    if [hasLz4 $version] {
      $w.ctr.compressLabelFrame.lz4 configure -state normal
    } else {
      $w.ctr.compressLabelFrame.lz4 deselect
      $w.ctr.compressLabelFrame.lz4 configure -state disabled
    }
  }
}

proc updateMonitorState {name updatelist} {
  global vsd
  set w .monitors
  if {![winfo exists $w]} {
    return
  }
  set l [$w.top.monlist subwidget hlist]
  set e [$l info selection]
  if {$e == {}} {
    return
  }
  set data [$l info data $e]
  if {[string equal $name [lindex $data 0]]} {
    monitorBrowse $e
  }
  if {$updatelist} {
    foreach e [$l info children] {
      set data [$l info data $e]
      if {[string equal $name [lindex $data 0]]} {
	if [info exists vsd(statmonpid:$name)] {
	  $l item create $e 1 -itemtype text \
	    -text [lindex $vsd(statmonpid:$name) 0]
	} else {
	  $l item create $e 1 -itemtype text -text "<none>"
	}
      }
    }
  }
}

proc showMonitorStatus {name} {
  global vsd
  set w .monstatus
  set exists [winfo exists $w]
  if $exists {
    raise $w
    wm deiconify $w
    [$w.top.msg subwidget text] delete 1.0 end
  } else {
    toplevel $w
    wm title $w "Monitor Status"
    if {$vsd(geometry:$w) != {}} {
      wm geometry $w $vsd(geometry:$w)
    }
    frame $w.top -relief raised -bd 1
    tixScrolledText $w.top.msg -width 500 -height 100
    [$w.top.msg subwidget text] configure -wrap none -font fixedTextFont
    pack $w.top.msg -expand yes -fill both
    pack $w.top -side top -fill both -expand yes
    tixButtonBox $w.box
    $w.box add dismiss -text Dismiss -width 6 \
      -command "dismissWindow $w"
    pack $w.box -fill x -side bottom
  }

  [$w.top.msg subwidget text] insert end $vsd(statmoncmd:$name)
  [$w.top.msg subwidget text] insert end "\n"
  [$w.top.msg subwidget text] insert end $vsd(statmonstatus:$name)
}

proc readMonitorOutput {f name} {
  global vsd
  if [eof $f] {
    catch {close $f}
    showMonitorStatus $name
    unset vsd(statmoncmd:$name)
    unset vsd(statmonpid:$name)
    unset vsd(statmonoutput:$name)
    unset vsd(statmoninterval:$name)
    unset vsd(statmonflush:$name)
    unset vsd(statmonstatus:$name)
    unset vsd(statmonpids:$name)
    unset vsd(statmonCompressGz:$name)
    unset vsd(statmonCompressLz4:$name)
    updateMonitorState $name true
  } else {
    gets $f line
    append vsd(statmonstatus:$name) $line
    append vsd(statmonstatus:$name) "\n"
    if {$vsd(statmonoutput:$name) == {}} {
      if [regexp {Output file is:[ ]+(.+)$} $line match file] {
	set vsd(statmonoutput:$name) $file
	updateMonitorState $name false
      }
    }
    if {$vsd(statmoninterval:$name) == {}} {
      if [regexp {Sample Interval .+is:[ ]+([0-9.]+)} $line match interval] {
	set vsd(statmoninterval:$name) $interval
	updateMonitorState $name false
      }
    }
    if {$vsd(statmonflush:$name) == {}} {
      if [regexp {Write Interval .+is:[ ]+([0-9.]+)} $line match flushinterval] {
	set vsd(statmonflush:$name) $flushinterval
	updateMonitorState $name false
	if {$flushinterval == 0} {
	  set i $vsd(statmoninterval:$name)
	} else {
	  set i $flushinterval
	}
	after [expr {wide(($i + 3) * 1000)}] [list loadStatmonFile $name]
      }
    }
  }
}

proc loadStatmonFile {name} {
  global vsd
  if [info exists vsd(statmonoutput:$name)] {
    setDataFile $vsd(statmonoutput:$name)
    # file from statmonitors started by vsd default to auto update
    set vsd(autoUpdate) 1
    autoUpdateMode
  }
}

proc statusMonitor {} {
  global vsd
  set w .monitors
  if {![winfo exists $w]} {
    return
  }
  set l [$w.top.monlist subwidget hlist]
  set e [$l info selection]
  if {$e == {}} {
    return
  }
  set data [$l info data $e]
  set name [lindex $data 0]
  showMonitorStatus $name
}

proc stopMonitor {} {
  global vsd
  set w .monitors
  if {![winfo exists $w]} {
    return
  }
  set l [$w.top.monlist subwidget hlist]
  set e [$l info selection]
  if {$e == {}} {
    return
  }
  set data [$l info data $e]
  set name [lindex $data 0]
  if [info exists vsd(statmonpid:$name)] {
    sl_kill $vsd(statmonpid:$name) 2
  }
}

proc stashStatmonUsage {exe} {
  global vsd
  if [info exists vsd(usage:$exe)] {
    return
  }
  catch {exec $exe} vsd(usage:$exe)
}

proc checkStatmonUsage {exe switch} {
  global vsd
  if {![info exists vsd(usage:$exe)]} {
    return 0
  }
  if {[string first " $switch " $vsd(usage:$exe)] == -1} {
    return 0
  } else {
    return 1
  }
}

#
# 45196 - Add lz4 support
# Returns 1 to indicate lz4 is available in statmonitor or
# 0 to indicate it isn't.  lz4 is in GS/64 3.4 and later.
#
proc hasLz4 {version} {
  set firstDigit [string index $version 0]
  if { $firstDigit == 5 || $firstDigit == 6} {
    # probably GS/32 bit 5.x or 6.x.  No lz4 for you.
    return 0
  }

  set w $version
  # first get rid of the dots
  set w [string map {"." ""} $version]
  # now pad with zeros to get length 5
  while {[string length $w] < 5} {
    set w [string cat $w "0"]
  }
  if {$w >= 34000} {
    return 1
  } else {
    return 0
  }
}

proc startMonitor {} {
  global vsd isWindows env
  set w .monitors
  if {![winfo exists $w]} {
    return
  }
  set l [$w.top.monlist subwidget hlist]
  set e [$l info selection]
  if {$e == {}} {
    return
  }
  set data [$l info data $e]
  set name [lindex $data 0]
  set version [lindex $data 1]
  set gemstone [lindex $data 3]

  if [info exists vsd(statmonpid:$name)] {
    showError "StatMon is already running on cache $name."
    return
  }

  if {![string equal $name "none"]} {
    if {$version == ""} {
      showError "No support for starting statmonitor on GemStone/S versions 4.1.3 and earlier."
      return
    }
  }

  if [info exists vsd(statmonexe)] {
    set statmonexe $vsd(statmonexe)
  } else {
    set statmonexe ""
  }
  if {$statmonexe == {} || ![file exists $statmonexe]} {
    set statmonbase "statmonitor"
    if {$isWindows} {
      append statmonbase ".exe"
    }
    set statmonexe [file join $gemstone bin $statmonbase]
    if {$gemstone == {} || ![file exists $statmonexe]} {
      set statmondir [file join [file dirname [file dirname [file dirname [info nameofexecutable]]]] bin]
      set statmonexe [file join $statmondir $statmonbase]
      if {![file exists $statmonexe]} {
	global env
	set loaded 0
	if [info exists env(GEMSTONE)] {
	  set statmonexe [file join $env(GEMSTONE) bin $statmonbase]
	  if [file exists $statmonexe] {
	    set gemstone {}
	    set loaded 1
	  }
	}
	if {! $loaded} {
	  showError "Could not find statmonitor executable."
	  return
	}
      } else {
	set gemstone [file dirname $statmondir]
      }
    }
  }

  $vsd(w:statmoninterval) update
  $vsd(w:statmonflush) update

  stashStatmonUsage $statmonexe
  
  set statmoncmdline "$statmonexe $name"
  if {$vsd(wv:statmonfile) != {}} {
    if {[string equal ".out" [file extension $vsd(wv:statmonfile)]]} {
      append statmoncmdline " -f [file rootname $vsd(wv:statmonfile)]"
    } else {
      append statmoncmdline " -f $vsd(wv:statmonfile)"
    }
  }
  if {$vsd(wv:statmoninterval) != {}} {
    append statmoncmdline " -i $vsd(wv:statmoninterval)"
  }
  if {$vsd(wv:statmonflush) != {}} {
    append statmoncmdline " -u $vsd(wv:statmonflush)"
  } else {
    append statmoncmdline " -u 0"
  }
  if {$vsd(wv:statmonargs) != {}} {
    append statmoncmdline " $vsd(wv:statmonargs)"
  }
  
  if [checkStatmonUsage $statmonexe "-P"] {
    if {$vsd(wv:statmonpids) != {}} {
      foreach id $vsd(wv:statmonpids) {
	append statmoncmdline " -P $id"
      }
    }
  }
  if {$vsd(wv:statmonCompressGz)} {
    append statmoncmdline " -z"
  } elseif {$vsd(wv:statmonCompressLz4)} {
    append statmoncmdline " -Z"
  }
  # Fix 43734 - Comment out this check.
  #   What did statmonitor -v <VSDpid> ever do?  It's not in the statmonitor code 
  #   as far back as GS 5.0!  Maybe it was a GemFire thing?
  #    if [checkStatmonUsage $statmonexe "-v"] {
  #	append statmoncmdline " -v [pid]"
  #    }
  set oldGemstone {}
  if {$gemstone != {}} {
    if [info exists env(GEMSTONE)] {
      set oldGemstone $env(GEMSTONE)
    }
    set env(GEMSTONE) $gemstone
  }
  set cmdstr "|$statmoncmdline 2>@stdout"
  if [catch {open $cmdstr r} result] {
    showError "The command '$statmoncmdline'\nfailed with: $result"
    if {$oldGemstone != {}} {
      set env(GEMSTONE) $oldGemstone
    }
    return
  }
  if {$oldGemstone != {}} {
    set env(GEMSTONE) $oldGemstone
  }
  set vsd(statmoncmd:$name) $statmoncmdline
  set vsd(statmonpid:$name) [lindex [pid $result] 0]
  set vsd(statmonoutput:$name) ""
  set vsd(statmoninterval:$name) ""
  set vsd(statmonflush:$name) ""
  set vsd(statmonstatus:$name) ""
  set vsd(statmonpids:$name) $vsd(wv:statmonpids)
  set vsd(statmonCompressGz:$name)  $vsd(wv:statmonCompressGz)
  set vsd(statmonCompressLz4:$name) $vsd(wv:statmonCompressLz4)

  updateMonitorState $name true
  fileevent $result readable [list readMonitorOutput $result $name]
}

proc sl_caches {} {
  set result {}
  if [catch {exec gslist -x} gslistOutput] {
    # something went wrong. Probably no gslist.
  } else {
    # parse the result
    set matchData [regexp -all -inline {([^\n]+)\s+status=[^\n]+\s+type=\s+([^\n]+)\s+version=\s+([^\n]+)\s+owner=\s+([^\n]+)\s+(?:\S+=[^\n]+\s+){3,7}(GEMSTONE|product)=\s*([^\n]+)\s*} $gslistOutput]
    foreach {junk name type version owner junk2 dir} $matchData {
      if {[string equal $type "SAP"] || [string equal $type "cache"]} {
	lappend result [list $name $version $owner $dir]
      }
    }
  }
  return $result
}

proc getMonitorTargets {} {
  #    global tcl_platform
  set res [sl_caches]
  #
  #    if {[llength $res] > 0} { # Don't display garbage if no gslist or no caches up
  #        lappend res [list none $tcl_platform(osVersion) \
    #             [info hostname] [file dirname [file dirname [info nameofexecutable]]]]
  #    }
  return $res
}
proc refreshMonitors {} {
  global vsd
  set w .monitors
  if {![winfo exists $w]} {
    return
  }
  set l [$w.top.monlist subwidget hlist]
  $l delete all
  [$w.box subwidget start] configure -state disabled
  [$w.box subwidget status] configure -state disabled
  [$w.box subwidget stop] configure -state disabled

  # Fix bug 46187 
  # We need to disable other widgets here until a monitor is selected.
  # A previous monitor selection leaves these enabled.
  $w.statmonfile configure -state disabled
  $w.statmonargs configure -state disabled
  $w.ctr.statmoninterval configure -state disabled
  $w.ctr.statmonflush configure -state disabled
  $w.ctr.compressLabelFrame.gz configure -state disabled
  $w.ctr.compressLabelFrame.lz4 configure -state disabled

  foreach c [getMonitorTargets] {
    set name [lindex $c 0]
    set row [$l addchild "" -data $c -itemtype text -text $name]
    if [info exists vsd(statmonpid:$name)] {
      $l item create $row 1 -itemtype text \
	-text [lindex $vsd(statmonpid:$name) 0]
    } else {
      $l item create $row 1 -itemtype text -text "<none>"
    }
    $l item create $row 2 -itemtype text -text [lindex $c 1]
    $l item create $row 3 -itemtype text -text [lindex $c 2]
  }
  updateMonitorListBgColor
}

proc dismissWindow {w} {
  global vsd
  set vsd(geometry:$w) [wm geometry $w]
  destroy $w
}

proc changeCtrFilter {newFilter} {
  global vsd
  set ctr $vsd(lastctr)
  set curfilter [getStatFilter $ctr]
  if {[string equal $curfilter $newFilter]} {
    return
  } else {
    setStatFilter $ctr $newFilter
  }
}

proc showStatisticInfoHelp {} {
  global vsd
  set topic "$vsd(lastctr) Statistic"
  doHelp $topic
}

proc changeCtrLevel {newLevel} {
  global vsd
  set ctr $vsd(lastctr)
  if {[string equal [getStatLevel $ctr] $newLevel]} {
    return
  } else {
    setStatLevel $ctr $newLevel
  }
}

proc getUsedStatNames {} {
  if {[llength [sl_stat -allfiles]] > 0} {
    return [allCountersForAllTypes]
  } else {
    return [sl_stat -names]
  }
}

proc updateCounterList {} {
  global vsd
  set ctrnames [getUsedStatNames]
  set newStatNameCount [llength $ctrnames]
  if {$vsd(statNameCount) != $newStatNameCount} {
    set vsd(statNameCount) $newStatNameCount
    if {[info exists vsd(ctrbox:w)] && [winfo exists $vsd(ctrbox:w)]} {
      set w $vsd(ctrbox:w)
      [$w subwidget listbox] delete 0 end
      foreach c [lsort -dictionary $ctrnames] {
	$w insert end $c
      }
      set c $vsd(lastctr)
      set vsd(lastctr) {}
      showCounterInfo $c
    }
  }
}

proc destroyCtrInfo {w} {
  global vsd
  set vsd(showCtrInfo) 0
  wm withdraw $w
}

proc createCtrInfo {w} {
  global vsd widgetHelp

  toplevel $w
  wm protocol $w WM_DELETE_WINDOW "destroyCtrInfo $w"

  if {$vsd(geometry:$w) != {}} {
    wm geometry $w $vsd(geometry:$w)
  }
  set widgetHelp($w) {Statistic Information Window}
  wm title $w "Statistic Information"

  set vsd(ctrbox:w) $w.counters
  tixComboBox $w.counters -dropdown true -editable false -value "" \
    -command showCounter
  $vsd(balloon) bind $w.counters -msg \
    "Selects statistic to display/modify information on"
  bind [$w.counters subwidget entry] <KeyPress> \
    [list findByFirstLetter $w.counters %A 1]
  set tmp [list startSearch $w.counters  searchComboBox]
  set tmp2 [$w.counters subwidget entry]
  bind $tmp2 <Control-s> $tmp
  bind $tmp2 <Control-f> $tmp

  set ctrlist [lsort -dictionary [getUsedStatNames]]
  if {$vsd(lastctr) == {}} {
    set vsd(lastctr) [lindex $ctrlist 0]
  }
  set vsd(statNameCount) [llength $ctrlist]
  foreach c $ctrlist {
    $w.counters insert end $c
  }
  pack $w.counters -fill x -padx 3 -pady 3

  frame $w.top
  tixLabelFrame $w.top.type -labelside left -label "Kind:"
  set f [$w.top.type subwidget frame]
  label $f.l -text "none"
  set vsd(ctrtype:w) $f.l
  pack $f.l
  pack $w.top.type -fill x -side left
  $vsd(balloon) bind $w.top.type -msg "Shows the statistic's kind"

  tixLabelFrame $w.top.units -labelside left -label "Units:"
  set f [$w.top.units subwidget frame]
  label $f.l -text "none"
  set vsd(ctrunits:w) $f.l
  pack $f.l
  pack $w.top.units -fill x -side right
  $vsd(balloon) bind $w.top.units -msg "Shows the units that the statistic measures"

  pack $w.top -fill x -padx 3 -pady 3
  
  tixLabelFrame $w.ctrobjs -labelside left -label "Types:"
  set f [$w.ctrobjs subwidget frame]
  label $f.l -text "none" -justify left -wraplength 300
  set vsd(ctrobjs:w) $f.l
  pack $f.l
  pack $w.ctrobjs -padx 3 -pady 3 -anchor w
  $vsd(balloon) bind $w.ctrobjs -msg "Shows all instance types that have this statistic"

  tixOptionMenu $w.filtertype -label "Default Filter:" -variable vsd(ctrfilter) \
    -state disabled -command changeCtrFilter \
    -options {
      label.width 12
    }
  $vsd(balloon) bind $w.filtertype -msg "Click to change statistic's default filter"
  set vsd(ctrfilter:w) $w.filtertype
  $vsd(ctrfilter:w) configure -disablecallback true
  $w.filtertype add command default -label "Default"
  $w.filtertype add command none -label "None"
  $w.filtertype add command persecond -label "PerSecond"
  $w.filtertype add command persample -label "PerSample"
  #    $w.filtertype add command smooth -label "Smooth"
  $w.filtertype add command aggregate -label "Aggregate"
  set vsd(ctrfilter) default
  $vsd(ctrfilter:w) configure -disablecallback false
  pack $w.filtertype -padx 3 -pady 3 -anchor w

  # 45848 - "Statistics Help" button deleted.

  tixLabelFrame $w.fdoc -label "Statistic Description"
  set fdoc [$w.fdoc subwidget frame]
  tixScrolledText $fdoc.doc -scrollbar y -width 250 -height 120
  set vsd(doctext:w) [$fdoc.doc subwidget text]
  $vsd(doctext:w) configure -wrap word -takefocus 0 -state disabled
  $vsd(balloon) bind $w.fdoc -msg "Describes the current statistic"

  pack $fdoc.doc -fill both -expand yes
  pack $w.fdoc -fill both -expand yes
}

proc dumpCtrInfo {} {
  set str {}
  foreach c [lsort -dictionary [sl_stat -names]] {
    append str "\n" $c
    append str "\n  type: " [getStatType $c] \
      "  level: " [getStatLevel $c] \
      "  units: " [getStatLevel $c] \
      "  filter: " [getStatFilter $c] \
      "\n" [getPlainStatDescription $c] \
      "\n  os: " [isOSStat $c] \
      "  objects: " [getStatObjList $c] "\n"
  }
  showData $str 1
}

proc doCtrInfo {} {
  global vsd
  set w .ctrinfo
  if {$vsd(showCtrInfo)} {
    if [winfo exists $w] {
      raise $w
      wm deiconify $w
    } else {
      createCtrInfo $w
    }
    set c $vsd(lastctr)
    set vsd(lastctr) {}
    showCounterInfo $c
  } else {
    if {![winfo exists $w]} {
      createCtrInfo $w
      set vsd(lastctr) {}
    }
    wm withdraw $w
  }
}

proc doCopySelection {} {
  global vsd
  set procs [getSelectedInstances]
  if {$procs == {}} {
    return
  }
  set ctrs [getSelCtr]
  if {$ctrs == {}} {
    return
  }
  set vsd(clipboard) {}
  lappend vsd(clipboard) "selection"
  lappend vsd(clipboard) [list $procs $ctrs]
}

# Added to fix bug 27336
proc doChangeDirectory {} {
  global vsd isWindows
  set adir [pwd]
  if {$isWindows} {
    set newDir [tk_chooseDirectory -parent $vsd(w) -mustexist true \
                  -title "VSD Working Directory" -initialdir $adir]
  } else {
    set newDir [tk_chooseDirectory -parent $vsd(w) -mustexist true \
                  -title "VSD Working Directory" \
                  -background $vsd(currentBgColor) -initialdir $adir]
  }
  if {$newDir != {}} {
    cd $newDir
    # User has changed directory. Remember they did it.
    set vsd(cwd) $newDir
  }
}

proc leaveTimeShifter {} {
  grab release .timeShifter
  dismissWindow .timeShifter
}

# Feature 43200
proc doTimeShift {} {
  global vsd
  #  puts "old_time_shift_hours is $vsd(old_time_shift_hours),  time_shift_hours is $vsd(time_shift_hours)"
  if {$vsd(old_time_shift_hours) != $vsd(time_shift_hours)} {
    setInstanceList
    updateAllTimeAxis
    #    updateGraphWidgetState
    #    updateAllActiveLines
    set vsd(old_time_shift_hours) $vsd(time_shift_hours)
  }
  leaveTimeShifter
}

# Feature 43200
proc doAdjustTimeOffset {} {
  global vsd
  # Save the old value
  set vsd(old_time_shift_hours) $vsd(time_shift_hours)
  set w .timeShifter
  toplevel $w -bd 2
  wm title $w "Change Time Offset"
  if {$vsd(geometry:$w) != {}} {
    wm geometry $w $vsd(geometry:$w)
  }
  wm group $w .
  wm minsize $w 250 100
  wm maxsize $w 250 100
  frame $w.top -relief raised -bd 1
  spinbox $w.top.sbox -bd 1 -relief raised -from -23.0 -to 23.0 -increment 1.0 -format %2.0f \
    -textvariable vsd(time_shift_hours) -width 4
  pack $w.top.sbox -side top
  label $w.top.l -font boldMainWindowFont -width 5 -height 1 -text "Hours"
  pack $w.top.l -side top
  button $w.top.okButton -text "Ok" -command { doTimeShift }
  pack $w.top.okButton -side left
  button $w.top.cancelButton -text "Cancel" -command \
    { set vsd(time_shift_hours) $vsd(old_time_shift_hours) ; leaveTimeShifter }
  pack $w.top.cancelButton -side right
  pack $w.top -side top -fill both -expand yes
  if { $vsd(masterBgColorChanged) == 1 } {
    setMasterBgColorForWindow $w
  }
  # Try to create a modal window here...
  grab $w
  wm transient $w
  wm protocol $w WM_DELETE_WINDOW { leaveTimeShifter }
  focus $w
  raise $w
  tkwait window .timeShifter
}

#############################################
# Feature 46389 - rolling left time trimmer
#############################################

proc createRollingTimeTrimmerValuesForFile {f} {
  global vsd
  if {[info exists vsd($f:new_left_trim_secs)]} {
    return
  }
  set vsd($f:new_left_trim_secs) $vsd(default_left_trim_secs)
  set vsd($f:new_left_trim_mins) $vsd(default_left_trim_mins)
  set vsd($f:new_left_trim_hrs)  $vsd(default_left_trim_hrs)
  set vsd($f:current_left_trim_secs) 0
  set vsd($f:current_left_trim_mins) 0
  set vsd($f:current_left_trim_hrs) 0
}

proc saveRollingTimeTrimmerSettings {} {
  global vsd
  set f $vsd(statsrc)
  set vsd($f:current_left_trim_secs) $vsd($f:new_left_trim_secs)
  set vsd($f:current_left_trim_mins) $vsd($f:new_left_trim_mins)
  set vsd($f:current_left_trim_hrs)  $vsd($f:new_left_trim_hrs)
}

proc revertRollingTimeTrimmerSettings {} {
  global vsd
  set f $vsd(statsrc)
  set vsd($f:new_left_trim_secs) $vsd($f:current_left_trim_secs)
  set vsd($f:new_left_trim_mins) $vsd($f:current_left_trim_mins)
  set vsd($f:new_left_trim_hrs)  $vsd($f:current_left_trim_hrs)
  set vsd(leftTimeTrimmerSaveDefaults) 0
}

proc saveRollingTimeTrimmerDefaults {f} {
  global vsd
  if {$vsd(leftTimeTrimmerSaveDefaults) == 1} {
    set vsd(default_left_trim_secs) $vsd($f:current_left_trim_secs)
    set vsd(default_left_trim_mins) $vsd($f:current_left_trim_mins)
    set vsd(default_left_trim_hrs)  $vsd($f:current_left_trim_hrs)
    set vsd(leftTimeTrimmerSaveDefaults) 0
  }
}

proc applyRollingTimeTrim {} {
  global vsd
  set f $vsd(statsrc)
  set total [expr {$vsd($f:new_left_trim_secs) + ($vsd($f:new_left_trim_mins) * 60) + \
		     ($vsd($f:new_left_trim_hrs) * 3600) }]
  set oldTotal [expr {$vsd($f:current_left_trim_secs) + ($vsd($f:current_left_trim_mins) * 60) + \
			($vsd($f:current_left_trim_hrs) * 3600) }]
  if {[expr {$oldTotal == $total}]} {
    saveRollingTimeTrimmerDefaults $f
    return
  }

  disableAutoUpdates
  if {[expr {$total > 0}]} {
    fileUntrimLeft $f
    fileUntrimRight $f
    $vsd(w:filemenu) entryconfigure "Untrim Left"  -state disabled
    $vsd(w:filemenu) entryconfigure "Untrim Right" -state disabled
  }
  if [ catch { $f -setrollingtrimleft $total } someError] {
    showError "setrollingtrimleft failed for $f because $someError"
    return
  }
  if {[expr {$total == 0}]} {
    set fileInfo [$f -info]
    updateFileTrimMenuItems $fileInfo
  }
  setInstanceList
  updateGraphWidgetState
  updateAllActiveLines
  enableAutoUpdates
  # Save the new settings
  saveRollingTimeTrimmerSettings
  updateAllLineTrimMenuItems
  if {$vsd(leftTimeTrimmerSaveDefaults) == 1} {
    set vsd(default_left_trim_secs) $vsd($f:current_left_trim_secs)
    set vsd(default_left_trim_mins) $vsd($f:current_left_trim_mins)
    set vsd(default_left_trim_hrs)  $vsd($f:current_left_trim_hrs)
    set vsd(leftTimeTrimmerSaveDefaults) 0
  }
}

proc zeroRollingTimeTrimmer {} {
  global vsd
  set f $vsd(statsrc)
  set vsd($f:new_left_trim_secs) 0 
  set vsd($f:new_left_trim_mins) 0
  set vsd($f:new_left_trim_hrs) 0  
}

proc dismissRollingTimeTrimmer {} {
  revertRollingTimeTrimmerSettings
  dismissWindow .rollingTimeTrimmer
}

proc updateRollingTimeTrimmerForFileSelect {} {
  global vsd
  set f $vsd(statsrc)
  createRollingTimeTrimmerValuesForFile $f

  set w .rollingTimeTrimmer
  if {![winfo exists $w]} {
    return
  }
  set f $vsd(statsrc)
  $w.top.lf1.chf.entryH configure -textvariable vsd($f:current_left_trim_hrs)
  $w.top.lf1.cmf.entryM configure -textvariable vsd($f:current_left_trim_mins)
  $w.top.lf1.csf.entryS configure -textvariable vsd($f:current_left_trim_secs)

  $w.top.lf2.fz.nhf.sboxH configure -textvariable vsd($f:new_left_trim_hrs)
  $w.top.lf2.fz.nmf.sboxM configure -textvariable vsd($f:new_left_trim_mins)
  $w.top.lf2.fz.nsf.sboxS configure -textvariable vsd($f:new_left_trim_secs)
  
  $w.top.lf3.fileLabel configure -text [getFileInfoString $f]
}

proc getFileInfoString {f} {
  set id [getFileId $f]
  set fname [getUniqueFileName $f]
  set result [lindex [file split $fname] end ]
  return $result
}


proc openRollingTimeTrimmer {} {
  global vsd
  # Save the old value
  set w .rollingTimeTrimmer
  if [winfo exists $w] {
    # use existing window
    wm deiconify $w
    focus $w
    raise $w
    return
  }

  set f $vsd(statsrc)

  toplevel $w -bd 2
  if {$vsd(geometry:$w) != {}} {
    wm geometry $w $vsd(geometry:$w)
  }
  
  pack propagate $w 1
  wm title $w "Rolling Left Trim Window"
  wm resizable $w 0 0
  wm group $w .

  # Main frame for window
  frame $w.top -relief raised -borderwidth 1
  pack $w.top -side top

  # [label]frames for Current, New, buttons and file name sections
  pack [labelframe $w.top.lf1 -text "Current Settings" -labelanchor nw] -side top -pady 5 -fill x
  pack [labelframe $w.top.lf2 -text "New Settings" -labelanchor nw] -side top  -pady 5 -fill x
  pack [labelframe $w.top.lf3 -text "Apply To File" -font normalMainWindowFont -labelanchor nw] -side top -pady 5 -fill x


  # Current settings
  # Frame for each Hours, Minutes, Seconds
  pack [frame $w.top.lf1.chf] -padx 5 -pady 5 -fill x -side left
  pack [frame $w.top.lf1.cmf] -padx 5 -pady 5 -fill x -side left
  pack [frame $w.top.lf1.csf] -padx 5 -pady 5 -fill x -side left
  
  # Hours
  entry $w.top.lf1.chf.entryH -bd 1 -justify right \
    -textvariable vsd($f:current_left_trim_hrs) -state readonly -width 0 -relief flat
  label $w.top.lf1.chf.labelH -font boldMainWindowFont -height 1 -text "Hours"
  pack $w.top.lf1.chf.entryH -fill x -side left 
  pack $w.top.lf1.chf.labelH -fill x -side left

  # Minutes
  entry  $w.top.lf1.cmf.entryM -bd 1 -justify right \
    -textvariable vsd($f:current_left_trim_mins) -state readonly -width 0 -relief flat
  label $w.top.lf1.cmf.labelM -font boldMainWindowFont -height 1 -text "Minutes"
  pack $w.top.lf1.cmf.entryM -fill x -side left
  pack $w.top.lf1.cmf.labelM -fill x -side left

  # Seconds
  entry $w.top.lf1.csf.entryS -bd 1 -justify right \
    -textvariable vsd($f:current_left_trim_secs) -state readonly -width 0 -relief flat
  label $w.top.lf1.csf.labelS -font boldMainWindowFont -height 1 -text "Seconds"
  pack $w.top.lf1.csf.entryS -fill x -side left
  pack $w.top.lf1.csf.labelS -fill x -side left
  

  # New settings
  # Frame for each Hours, Minutes, Seconds
  pack [frame $w.top.lf2.fz] -side top -pady 5
  pack [frame $w.top.lf2.fz.nhf] -padx 5 -pady 5 -fill x -side left
  pack [frame $w.top.lf2.fz.nmf] -padx 5 -pady 5 -fill x -side left
  pack [frame $w.top.lf2.fz.nsf] -padx 5 -pady 5 -fill x -side left
  pack [frame $w.top.lf2.bf] -side top -pady 5

  pack [frame $w.top.lf2.cbf] -side top  -pady 5 -fill x
  
  set padx 2
  set ipadx 2
  set ipady 2
  spinbox $w.top.lf2.fz.nhf.sboxH -bd 1 -relief raised -from 0 -to 99 -increment 1 \
    -textvariable vsd($f:new_left_trim_hrs) -width 0 -relief solid -justify center
  label $w.top.lf2.fz.nhf.labelH -font boldMainWindowFont -height 1 -text "Hours"
  pack $w.top.lf2.fz.nhf.sboxH  -fill x -side left -padx $padx -ipadx $ipadx -ipady $ipady
  pack $w.top.lf2.fz.nhf.labelH -fill x -side left -padx $padx

  spinbox $w.top.lf2.fz.nmf.sboxM -bd 1 -relief raised -from 0 -to 59 -increment 1 \
    -textvariable vsd($f:new_left_trim_mins) -width 0 -relief solid -justify center
  label $w.top.lf2.fz.nmf.labelM -font boldMainWindowFont -height 1 -text "Minutes"
  pack $w.top.lf2.fz.nmf.sboxM  -fill x -side left -padx $padx -ipadx $ipadx -ipady $ipady
  pack $w.top.lf2.fz.nmf.labelM -fill x -side left -padx $padx

  spinbox $w.top.lf2.fz.nsf.sboxS -bd 1 -relief raised -from 0 -to 59 -increment 1 \
    -textvariable vsd($f:new_left_trim_secs) -width 0  -relief solid -justify center
  label $w.top.lf2.fz.nsf.labelS -font boldMainWindowFont -height 1 -text "Seconds"
  pack  $w.top.lf2.fz.nsf.sboxS  -fill x -side left -padx $padx -ipadx $ipadx -ipady $ipady
  pack  $w.top.lf2.fz.nsf.labelS -fill x -side left -padx $padx

  # Action buttons
  button $w.top.lf2.bf.zeroButton    -text "Zero"    -command { zeroRollingTimeTrimmer }
  button $w.top.lf2.bf.dismissButton -text "Dismiss" -command { dismissRollingTimeTrimmer }
  button $w.top.lf2.bf.applyButton   -text "Apply"   -command { applyRollingTimeTrim }
  button $w.top.lf2.bf.revertButton  -text "Revert"  -command { revertRollingTimeTrimmerSettings }
  
  pack $w.top.lf2.bf.applyButton   -side left -padx 5
  pack $w.top.lf2.bf.revertButton  -side left -padx 5
  pack $w.top.lf2.bf.zeroButton    -side left -padx 5
  pack $w.top.lf2.bf.dismissButton -side left -padx 5

  # Save As Default checkbutton
  checkbutton $w.top.lf2.cbf.cb -text "Save As Defaults To Startup File" \
    -variable vsd(leftTimeTrimmerSaveDefaults)
  pack $w.top.lf2.cbf.cb -side left -fill x
  
  # File name frame
  label $w.top.lf3.fileLabel -font normalMainWindowFont -height 1 -text [getFileInfoString $f]
  pack $w.top.lf3.fileLabel -side left -fill x

  if { $vsd(masterBgColorChanged) == 1 } {
    setMasterBgColorForWindow $w
  }
  #   grid columnconfigure $w.top "all" -uniform allTheSame
  wm protocol $w WM_DELETE_WINDOW { dismissRollingTimeTrimmer }

  #     grid columnconfigure $w.top "all" -uniform allTheSame
  #    grid columnconfigure $w.top "all" -weight 1
  #   grid rowconfigure $w.top 1 -weight 1

}

# Code for 44951
proc doSetColor {w color} {
  catch { $w config -background $color }
  catch { $w config -highlightbackground $color }
}

# Update the background color for all active graphs.
# Ignore any windows that no longer exist.
proc updateAllGraphBgColor {} {
  global vsd
  set i 0
  while { $i < $vsd(graphcreates) } {
    set w ".chart$i"
    if [winfo exists $w] {
      set g $w.graph
      $g xaxis configure -background $vsd(currentBgColor)
      $g yaxis configure -background $vsd(currentBgColor)
      $g y2axis configure -background $vsd(currentBgColor)
      $g configure -plotbackground $vsd(currentBgColor) \
        -background $vsd(currentBgColor)
      $g legend configure -background $vsd(currentBgColor)
    }
    incr i
  }
}

# Update the background color for the Monitor list window
proc updateMonitorListBgColor {} {
  global vsd
  set w .monitors
  if {! [winfo exists $w] } {
    return
  }
  setMasterBgColorForWindow $w
  set l [$w.top.monlist subwidget hlist]
  set i 0
  while { $i < 4 } {
    $l header configure $i -headerbackground $vsd(currentBgColor)
    incr i
  }
}

# Set the background color for window $w to $color.
# Then do the same for all children of $w by 
# recursively calling this proc.
proc doSetMasterBgColorForWindow {w color} {
  doSetColor $w $color
  foreach child [winfo children $w] {
    doSetMasterBgColorForWindow $child $color
  }
}

# Set the background color for window $w to the current
# background color (stored in $vsd(currentBgColor) )
proc setMasterBgColorForWindow {w} {
  global vsd
  if { $vsd(masterBgColorChanged) == 1 } {
    doSetMasterBgColorForWindow $w $vsd(currentBgColor)
  }
}

# Open the color chooser dialog to select a new master background color.
proc doChooseColor {title parent} {
  global vsd isWindows
  if { $vsd(masterBgColorChanged) == 1 && $isWindows == 0 } {
    # we have been here before.  Make the dialog match the current BG color
    set color [tk_chooseColor -title $title -parent $parent \
		 -background $vsd(currentBgColor)]
  } else {
    # first time here.  Use default color for the dialog.
    set color [tk_chooseColor -title $title -parent $parent]
  }
  return $color
}

proc setMasterBackgroundColor {color} {
  global vsd
  set vsd(masterBgColorChanged) 1
  set vsd(currentBgColor) $color
  set vsd(currentPlotBgColor) $color
  #
  # The following code has been tried but does not do what we want.
  #    tk_setPalette $color
  #    tix resetoptions TK TK
  #    tk_setPalette background $color highlightBackground $color

  # Update colors in the VSD styles.  Not sure if this is required.
  catch { doSetMasterBgColorForWindow $vsd(name:s) $color }
  catch { doSetMasterBgColorForWindow $vsd(time:s) $color }
  catch { doSetMasterBgColorForWindow $vsd(samples0:s) $color }
  catch { doSetMasterBgColorForWindow $vsd(samples1:s) $color }
  catch { doSetMasterBgColorForWindow $vsd(ctr0:s) $color }
  catch { doSetMasterBgColorForWindow $vsd(ctr1:s) $color }
  catch { doSetMasterBgColorForWindow $vsd(header:s) $color }
  catch { doSetMasterBgColorForWindow $vsd(buthdr:s) $color }

  set i 0
  while { $i < 7 } {
    $vsd(instanceList) header configure $i -headerbackground $color
    incr i
  }
  updateAllGraphBgColor
  updateMonitorListBgColor
  if {[string compare $color ""]} {
    doSetMasterBgColorForWindow . $color
  }
}

proc pickMasterBackgroundColor {} {
  global vsd
  set w $vsd(w)
  set initialColor [$w cget -background]
  set title "Choose a background color"
  set color [doChooseColor $title $w]

  if { $color == "" || $color == $initialColor } {
    return
  }
  setMasterBackgroundColor $color
}


# 45196 - Add lz4 support
proc selectGzip { } {
  set w .monitors
  $w.ctr.compressLabelFrame.lz4 deselect
}

proc selectLz4 { } {
  set w .monitors
  $w.ctr.compressLabelFrame.gz deselect
}

proc doMonitor {} {
  global vsd widgetHelp
  set w .monitors
  if [winfo exists $w] {
    wm deiconify $w
    focus $w
    raise $w
    refreshMonitors
    return
  }

  set widgetHelp($w) {Monitor Window}

  toplevel $w
  wm protocol $w WM_DELETE_WINDOW "wm withdraw $w"
  wm title $w "Monitor"
  if {$vsd(geometry:$w) != {}} {
    wm geometry $w $vsd(geometry:$w)
  }
  frame $w.top -relief raised -bd 1

  tixScrolledHList $w.top.monlist -width 300 -options {
    hlist.columns 4
    hlist.header true
    hlist.height 3
    hlist.selectMode single
    hlist.BrowseCmd monitorBrowse
  }
  set l [$w.top.monlist subwidget hlist]
  # First some styles for the headers
  set vsd(header:s) [tixDisplayStyle text -fg black \
		       -anchor c -padx 8 -pady 2 \
		       -font boldMainWindowFont]

  $l header create 0 -itemtype text -text "Cache Name" \
    -style $vsd(header:s)
  $l header create 1 -itemtype text -text "StatMon" \
    -style $vsd(header:s)
  $l header create 2 -itemtype text -text "Version" \
    -style $vsd(header:s)
  $l header create 3 -itemtype text -text "Creator" \
    -style $vsd(header:s)

  pack $w.top.monlist -expand yes -fill both
  $vsd(balloon) bind $w.top.monlist -msg \
    "Lists/Selects statistic sources and monitor processes"

  set vsd(wv:statmonfile) ""
  tixLabelEntry $w.statmonfile -label "StatMon Output:" -state disabled \
    -options {
      entry.textVariable vsd(wv:statmonfile)
    }
  $w.statmonfile subwidget label configure -background $vsd(currentBgColor)
  $vsd(balloon) bind $w.statmonfile -msg "Click to enter file name that monitor will write to"
  
  #    set vsd(wv:statmonargs) ""
  tixLabelEntry $w.statmonargs -label "StatMon Arguments:" -state disabled \
    -options {
      entry.textVariable vsd(wv:statmonargs)
    }
  $w.statmonargs subwidget label configure -background $vsd(currentBgColor)


  frame $w.ctr -relief flat -bd 0
  set vsd(w:statmoninterval) $w.ctr.statmoninterval
  tixControl $w.ctr.statmoninterval -label "Sample Interval:" -state disabled \
    -integer 1 -min 1 -autorepeat false -variable vsd(wv:statmoninterval) \
    -value $vsd(wv:statmoninterval) \
    -options {
      entry.width 3
    }
  $vsd(balloon) bind $w.ctr.statmoninterval -msg "How often, in seconds, monitor should sample"

  set vsd(w:statmonflush) $w.ctr.statmonflush
  tixControl $w.ctr.statmonflush -label "Write Interval:" -state disabled \
    -integer 1 -min -1 -autorepeat false -variable vsd(wv:statmonflush) \
    -value $vsd(wv:statmonflush) \
    -options {
      entry.width 3
    }
  $vsd(balloon) bind $w.ctr.statmonflush -msg "How often, in seconds, monitor should flush collected data to disk"

  
  # 45196 - Add lz4 support
  # *** NEW checkbox for compression: none, gzip, or lz4 ***
  labelframe $w.ctr.compressLabelFrame -text "Compression:"
  checkbutton $w.ctr.compressLabelFrame.gz -text gzip \
    -variable vsd(wv:statmonCompressGz) -command selectGzip -state disabled
  checkbutton $w.ctr.compressLabelFrame.lz4 -text lz4 \
    -variable vsd(wv:statmonCompressLz4) -command selectLz4 -state disabled
  pack $w.ctr.compressLabelFrame.gz -side left -fill x \
    -in $w.ctr.compressLabelFrame
  pack $w.ctr.compressLabelFrame.lz4 -side left -fill x \
    -in $w.ctr.compressLabelFrame
  
  pack $w.ctr.statmoninterval $w.ctr.statmonflush $w.ctr.compressLabelFrame -side left -padx 3 -pady 3
  #   pack $w.ctr.compressLabelFrame -in $w.ctr -after $w.ctr.statmonflush
  #   pack $w.ctr -expand no -fill x -side bottom -padx 3 -pady 2 -after $w.ctr.statmonflush

  tixButtonBox $w.box
  $w.box add refresh -text Refresh -width 6 \
    -command refreshMonitors
  $vsd(balloon) bind [$w.box subwidget refresh] -msg "Update display of statistic sources that can be monitored"
  $w.box add start -text Start -width 6 -state disabled \
    -command startMonitor
  $vsd(balloon) bind [$w.box subwidget start] -msg "Start monitoring the select statistic source"
  $w.box add status -text Status -width 6 -state disabled \
    -command statusMonitor
  $vsd(balloon) bind [$w.box subwidget status] -msg "Show the status of the selected monitor task"
  $w.box add stop -text Stop -width 6 -state disabled \
    -command stopMonitor
  $vsd(balloon) bind [$w.box subwidget stop] -msg "Stop the selected monitor task"
  $w.box add dismiss -text Dismiss -width 6 \
    -command "wm withdraw $w"
  set but [$w.box subwidget dismiss]
  $vsd(balloon) bind $but -msg "Close the monitor window"


  pack $w.box -fill x -side bottom
  pack $w.statmonargs -expand no -fill x -side bottom
  pack $w.ctr -fill x -side bottom
  pack $w.top -side top -fill both -expand yes
  pack $w.statmonfile -expand no -fill x -side bottom    
  refreshMonitors
}

proc createVsdConfig {fname} {
  global vsd

  if [catch {open $fname w+} f] {
    showError "Notice: could not write $fname because:\n$f"
    return
  }
  puts $f "\# $vsd(vsdConfigFn) is never changed by vsd. If the user deletes it then
\# the next time vsd is started it will be recreated with default values.
\# Users can configure vsd by setting default values in this file.
\# Any value set in $vsd(vsdConfigFn) will override the same definition in $vsd(vsdRcFn).
\# Make sure and put braces around the value if it contains whitespace.
\# For example: set vsd(exec:lpr) {lpr -h}
"

  puts $f "\# vsd(exec:lpr) is the name of the command used to print.
\# It is called with one argument; the name of the file to be printed."
  puts $f "set vsd(exec:lpr) [list $vsd(exec:lpr)]"

  puts $f "\n\# vsd(exec:mail) is the name of the command used to send mail.
\# It is called with one argument; the name of the user to send mail to.
\# It also expects the message to mail from stdin."
  puts $f "set vsd(exec:mail) [list $vsd(exec:mail)]"

  puts $f "\n\# vsd(exec:date) is the name of the command used to get the date.
\# It is called with no arguments."
  puts $f "set vsd(exec:date) [list $vsd(exec:date)]"

  puts $f "\n\# vsd(exec:uname) is the name of the command used to get system info.
\# It is called with no arguments."
  puts $f "set vsd(exec:uname) [list $vsd(exec:uname)]"

  puts $f "\n\# Uncomment the next line if you do not want the cursor to blink.
\# set vsd(cursorblink) 0"

  puts $f {
    # See the vsd help section "Statistic Definitions" for a guide to adding
    # your own definitions to this file.
    #
    # See the vsd help section "Statistic Aliases" for a guide to adding
    # your own aliases to this file.
  }
  catch {close $f}
}

proc chooseChart {title} {
  global vsd
  if [info exists vsd(ct:$title)] {
    set w $vsd(ct:$title)
    if [winfo exists $w] {
      raise $w
      wm deiconify $w
    }
  }
}

proc changeNoFlatlines {} {
  global vsd

  if {$vsd(noFlatlines) && $vsd(noAllZeros)} {
    set vsd(noAllZeros) 0
  } 
  
  foreach idx [array names vsd {*:ctrlistid}] {
    set vsd($idx) {}
  }
  fillCounters 1 [getSelectedInstances]
}

# 51109
proc changeNoAllZeros {} {
  global vsd

  if {$vsd(noFlatlines) && $vsd(noAllZeros)} {
    set vsd(noFlatlines) 0
  }  
  
  foreach idx [array names vsd {*:ctrlistid}] {
    set vsd($idx) {}
  }
  fillCounters 1 [getSelectedInstances]
}

# changeLevel deleted for 49883

proc traceAPICalls {} {
  global vsd
  sl_stat -trace $vsd(traceAPICalls)
}

proc ctrHlistBrowse {junk} {
  global vsd
  set te [tixEvent type]
  if {![string equal $te "<ButtonRelease-1>"]} {
    set lb $vsd(w:counterHList)
    if {![string equal $te "<Up>"] &&
	![string equal $te "<Down>"] &&
	![string equal $te "<Application>"]} {
      set i [$lb nearest [tixEvent flag y]]
    } else {
      set i [$lb info anchor]
    }
    if {$i != {} && [$lb selection includes $i]} {
      set itext [$lb entrycget $i -text]
      showCounterInfo $itext
    }
    updateDisabledItems
  }
}

proc ctrHlistCommand {e} {
  doChart 0
}

proc setActiveColor {} {
  global vsd isWindows
  set title "Choose Selected Line Color"
  if { $vsd(masterBgColorChanged) == 1 && $isWindows == 0 } {
    set color [tk_chooseColor -initialcolor $vsd(activecolor) \
		 -parent $vsd(w) -title $title -background $vsd(currentBgColor)]
  } else {
    set color [tk_chooseColor -initialcolor $vsd(activecolor) \
		 -parent $vsd(w) -title $title]
  }
  if {$color != ""} {
    set vsd(activecolor) $color
    foreach name [array names vsd  ct:*] {
      set g $vsd($name).graph
      $g pen configure "activeLine" -color $vsd(activecolor)
      $g marker configure triStart -fg $vsd(activecolor)
      $g marker configure triEnd -fg $vsd(activecolor)
    }
  }
}

proc setChartFont {font args} {
  global vsd
  set vsd(smallfont) [font actual $font]
  font delete vsdsf
  eval font create vsdsf $vsd(smallfont)
}

proc createTextFonts {} {
  global vsd
  eval font create normalTextFont $vsd(textfont)
  eval font create boldTextFont $vsd(textfont) -weight bold
  eval font create italicTextFont $vsd(textfont) -slant italic
  eval font create boldItalicTextFont $vsd(textfont) -weight bold -slant italic
  eval font create fixedTextFont $vsd(textfont) -family Courier -weight bold
}

# 46261
proc setMainWindowFont {font args} {
  global vsd
  set vsd(mainWindowFont) [font actual $font]
  font delete normalMainWindowFont boldMainWindowFont italicMainWindowFont
  createMainWindowFonts
  setInstanceList
  fillCounters 1 [getSelectedInstances]
}

# 46261
proc createMainWindowFonts {} {
  global vsd
  eval font create normalMainWindowFont $vsd(mainWindowFont) -weight normal
  eval font create boldMainWindowFont   $vsd(mainWindowFont) -weight bold
  eval font create italicMainWindowFont $vsd(mainWindowFont) -slant italic -weight normal
}

proc setDataLogFont {font args} {
  global vsd
  set vsd(dataLogFont) [font actual $font]
  font delete dataLogFont
  createDataLogFont
}

proc createDataLogFont {} {
  global vsd
  eval font create dataLogFont $vsd(dataLogFont)
}

proc chooseVsdFont {vsdKey setFontCmdName nameString} {
  global vsd
  if {[tk fontchooser configure -visible]} {
    tk fontchooser hide
  }

  set aFont [font actual $vsd($vsdKey)]
  tk fontchooser configure -command $setFontCmdName -font $aFont \
    -title "Choose $nameString Font"
  tk fontchooser show
}

proc isCombineTemplate {tl} {
  string match {*+} [lindex $tl 0]
}

proc isOperatorTemplate {tl} {
  expr {[lsearch -exact {"plus" "diff" "divide"} [lindex $tl 0]] != -1}
}

proc fileMatches {mf df fids} {
  if {[llength $fids] > 0} {
    expr {[lsearch -exact $fids [getFileId $mf]] != -1}
  } else {
    expr {$df == {} || [string equal $df $mf]}
  }
}

proc findTemplateMatch {tl {df {}}} {
  global vsd
  set instList {}
  set typeList [string trimright [lindex $tl 0] "+"]
  set objs {}
  set fids {}
  foreach t $typeList {
    if {[string is integer -strict $t]} {
      lappend fids $t
    } else {
      lappend objs $t
    }
  }
  set excludeMode 0
  set name [lindex $tl 1]
  if {[string range $name 0 0] eq "!"} {
    # we want to include all instances except instances that match the string after '!'
    set excludeMode 1
    # shave off the '!' character
    set excludeName [string range $name 1 end]
    # include all other names
    set name {*}
  }
  set ctr [lindex $tl 2]

  if [catch {getStatObjList $ctr} statObjList] {
    setStatus "Unknown statistic \"$ctr\"."
    bell
    return {}
  }

  if {$objs != {}} {
    set ctrobjs {}
    foreach co $statObjList {
      set noverco [file rootname $co]
      if {[lsearch -exact $objs $noverco] != -1} {
	lappend ctrobjs $co
      }
    }
  } else {
    set ctrobjs $statObjList
  }
  if {$ctrobjs == {}} {
    # We don't have any instances of the target objects
    return {}
  }

  if {$vsd(templatesUseSelection)} {
    set procs [getSelectedInstances]
    if {$procs != {}} {
      # if some procs are selected restrict the template to them
      foreach i $procs {
	set data [$vsd(instanceList) info data $i]
	set iobj [lindex $data 1]
	if {[lsearch -exact $ctrobjs $iobj] != -1} {
	  set fullname [lindex [lindex $data 0] 0]
	  if {[string match $name $fullname]} {
	    if {! $excludeMode || ![string match $fullname $excludeName]} {
	      if {[fileMatches [lindex $data 5] $df $fids]} {
		lappend instList [lindex $data 6]
	      }
	    }
	  }
	}
      }
      # Only return the matched selected instances if at least
      # one of the selected types was the kind this template
      # was trying to match.
      if {$instList != {}} {
	return $instList
      }
    }
  }

  set instList {}
  foreach i [instSort [sl_stat -enabledobjinstances $ctrobjs]] {
    set fullname [lindex [lindex $i 0] 0]
    if {[string match $name $fullname]} {
      if {! $excludeMode || ![string match $fullname $excludeName]} {
	if {[fileMatches [lindex $i 5] $df $fids]} {
	  lappend instList [lindex $i 6]
	}
      }
    }
  }

  return $instList
}

proc createLinesFromTemplate {tl {df {}}} {
  global vsd
  set result {}
  set scale [lindex $tl 3]
  set filter [lindex $tl 4]
  set infoArgs [llength $tl]
  if {$infoArgs <= 7} {
    # old style with default divider, offset, and normalize
    set divider 1
    set offset 0
    set normalized 0
    set statFilter [lindex $tl 6]
  } else {
    set divider [lindex $tl 6]
    set offset [lindex $tl 7]
    set normalized [lindex $tl 8]
    set statFilter [lindex $tl 9]
  }
  if {[isOperatorTemplate $tl]} {
    set childLines {}
    foreach childSpec [lrange $tl 1 2] {
      set isChildOp [isOperatorTemplate $childSpec]
      lappend childLines [createLinesFromTemplate $childSpec $df]
    }
    set lines1 [lindex $childLines 0]
    set lines2 [lindex $childLines 1]
    if {[llength $lines1] > 0 && [llength $lines2] > 0} {
      set opcode [lindex $tl 0]
      switch -exact $opcode {
	"plus" {set operator "+"}
	"diff" {set operator "-"}
	"divide" {set operator "/"}
      }
      while {[llength $lines1] < [llength $lines2]} {
	lappend lines1 [lindex $lines1 end]
      }
      while {[llength $lines2] < [llength $lines1]} {
	lappend lines2 [lindex $lines2 end]
      }
      foreach l1 $lines1 l2 $lines2 {
	set line [createOperatorLine [list $l1 $l2] $operator 0 $filter $scale $divider $offset $normalized $statFilter]
	if {$line != {}} {
	  lappend result $line
	}
      }
    }
    
    foreach tmp $childLines {
      foreach l $tmp {
	$l -free
      }
    }
  } else {
    set instList [findTemplateMatch $tl $df]
    if {$instList == {}} {
      return {}
    }
    set counter [lindex $tl 2]
    if {[isCombineTemplate $tl]} {
      if {$vsd(combineFiles) || [llength $instList] == 1} {
	set line [createSimpleLine $instList $counter $filter $scale $divider \
		    $offset $normalized $statFilter]
	if {$line != {}} {
	  lappend result $line
	}
      } else {
	foreach fIt [getSortedFiles] {
	  set dfInstList {}
	  foreach inst $instList {
	    set instData [sl_stat -instinfo $inst]
	    if {[string equal $fIt [lindex $instData 5]]} {
	      lappend dfInstList $inst
	    }
	  }
	  if {$dfInstList != {}} {
	    set line [createSimpleLine $dfInstList $counter $filter $scale $divider \
			$offset $normalized $statFilter]
	    if {$line != {}} {
	      lappend result $line
	    }
	  }
	}
      }
    } else {
      foreach fn $instList {
	set line [createSimpleLine [list $fn] $counter $filter $scale $divider \
		    $offset $normalized $statFilter]
	if {$line != {}} {
	  lappend result $line
	}
      }
    }
  }
  return $result
}

proc startLongOp {w} {
  global vsd
  set w [winfo toplevel $w]
  set vsd(longOpW) $w
  set vsd(longOpCancel) 0
  myBusyOn $w
  set busyWin ${w}._Busy
  focus $busyWin
  bind $busyWin <Escape> {cancelLongOp}
  bind $busyWin <Control-g> {cancelLongOp}
}

proc cancelLongOp {} {
  global vsd
  set vsd(longOpCancel) 1
  setStatus "Cancelled operation"
  bell
}

proc endLongOp {} {
  global vsd
  if {$vsd(longOpW) != ""} {
    myBusyOff $vsd(longOpW)
    set vsd(longOpW) ""
  }
  set vsd(longOpCancel) 0
}

proc newFromTemplate {t} {
  global vsd vsdtemplates
  if {[info exists vsdtemplates($t)]} {
    if {[string equal "@" [lindex $vsdtemplates($t) 0]]} {
      foreach tn [lrange $vsdtemplates($t) 1 end] {
	newFromTemplate $tn
      }
      return
    }
    disableAutoUpdates
    foreach f [getSortedFiles] {
      set new 1
      set w ""
      foreach tl $vsdtemplates($t) {
	set lineList [createLinesFromTemplate $tl $f]
	if {$lineList != {}} {
	  set mapaxis [lindex $tl 5]
	  if {$new} {
	    set vsd(curchart) "$t $vsd(graphcreates)"
	    set w [createGraphWindow $vsd(curchart)]
	    startLongOp $w
	    set new 0
	  }
	  foreach line $lineList {
	    if {$w != "" && ![winfo exists $w]} {set vsd(longOpCancel) 1; break}
	    addLineToChart $line 0 0 $mapaxis "none"
	    update
	    if {$vsd(longOpCancel)} {break}
	  }
	}
	if {$vsd(longOpCancel)} {break}
      }
      if {$vsd(longOpCancel)} {break}
      endLongOp
    }
    enableAutoUpdates
    endLongOp
  }
}

proc addFromTemplate {t title} {
  global vsd vsdtemplates
  if {[info exists vsdtemplates($t)]} {
    if {[string equal "@" [lindex $vsdtemplates($t) 0]]} {
      foreach tn [lrange $vsdtemplates($t) 1 end] {
	addFromTemplate $tn $title
      }
      return
    }
    set w $vsd(ct:$title)
    startLongOp $w
    set vsd(curchart) $title
    disableAutoUpdates
    foreach tl $vsdtemplates($t) {
      set lineList [createLinesFromTemplate $tl]
      set mapaxis [lindex $tl 5]
      foreach line $lineList {
	if {![winfo exists $w]} {set vsd(longOpCancel) 1; break}
	addLineToChart $line 0 0 $mapaxis "none"
	update
	if {$vsd(longOpCancel)} {break}
      }
      if {$vsd(longOpCancel)} {break}
    }
    enableAutoUpdates
    endLongOp
  }
}

proc createGraphTemplateMenu {m title} {
  global vsd vsdtemplates
  foreach t [lsort [array names vsdtemplates]] {
    $m add command -label $t -command [list addFromTemplate $t $title]
  }
}

proc reloadTemplates {m} {
  global vsd vsdtemplates
  set fn "$vsd(VSDHOME)/$vsd(vsdTemplatesFn)"
  # 47637 - use VSDHOME instead of ~
  if [file exists $fn] {
    if [catch {source $fn} msg] {
      showError "Notice: could not read '$fn' because:\n$msg"
    } else {
      $m delete 0 end
      createTemplateMenu $m
      lappend vsd(sourcedFiles) $fn
      createAboutHelp 1
    }
  } else {
    showError "Notice: could not read '$fn' because:\nfile does not exist"
  }
}

proc createTemplateMenu {m} {
  global vsd vsdtemplates
  foreach t [lsort [array names vsdtemplates]] {
    addTemplateMenu $m $t
  }
}

proc addTemplateMenu {m t} {
  $m add command -label $t -command [list newFromTemplate $t]
}

proc defaultTemplates {} {
  global vsdtemplates
  set vsdtemplates(CacheMix) {
    {Shrpc {*} FreeFrameCount 1 none y2}
    {Shrpc {*} LocalDirtyPageCount 1 none y}
    {Shrpc {*} FreeFrameLimit 1 none y}
    {Shrpc {*} BitlistPagesWrittenByGem 1 none y}
    {Shrpc {*} BitlistPagesWrittenByStone 1 none y}
    {Shrpc {*} BmInternalPagesWrittenByGem 1 none y}
    {Shrpc {*} BmInternalPagesWrittenByStone 1 none y}
    {Shrpc {*} BmLeafPagesWrittenByGem 1 none y}
    {Shrpc {*} BmLeafPagesWrittenByStone 1 none y}
    {Shrpc {*} CommitRecordPagesWrittenByGem 1 none y}
    {Shrpc {*} CommitRecordPagesWrittenByStone 1 none y}
    {Shrpc {*} DataPagesWrittenByGem 1 none y}
    {Shrpc {*} OtInternalPagesWrittenByGem 1 none y}
    {Shrpc {*} OtInternalPagesWrittenByStone 1 none y}
    {Shrpc {*} OtLeafPagesWrittenByGem 1 none y}
    {Shrpc {*} OtLeafPagesWrittenByStone 1 none y}
  }
  set vsdtemplates(CacheTooSmall) {
    {Shrpc ShrPcMonitor FreeFrameCount 1 none y2}
    {+ {*} FramesFromFreeList 1 persecond y}
    {+ {*} FramesFromFindFree 1 persecond y}
  }
  set vsdtemplates(CommitInfo) {
    {Stn {!pagemgrThread} TotalCommits 1 persecond y}
    {Stn {!pagemgrThread} CommitRecordCount 1 none y2}
    {Stn {!pagemgrThread} CommitQueueSize 1 none y2}
  }
  # Fix 43319
  set vsdtemplates(CPU) {
    {+ {*} PercentCpuActive 1 none y}
    {+ {*} PercentCpuWaiting 1 none y}
    {+ {*} PercentCpuIdle 1 none y}
    {+ {*} PercentCpuUser 1 none y}
    {+ {*} PercentCpuSystem 1 none y}
    {+ {*} PercentCpuIOWait 1 none y}
    {+ {*} PercentCpuSwapWait 1 none y}
    {NtSystem {*} TotalProcessorTime 1 persecond y}
    {NtSystem {*} ProcessorQueueLength 1 none y2}
  }
  set vsdtemplates(CRBacklog) {
    {Stn {!pagemgrThread} TotalCommits 1 persecond y2}
    {Stn {!pagemgrThread} CommitRecordCount 1 none y2}
    {Stn {!pagemgrThread} CommitQueueSize 1 none y2}
    {Stn {!pagemgrThread} OldestCrSession 1 none y}
  }
  set vsdtemplates(EpochSweeps) {
    {Stn {!pagemgrThread} EpochGcCount 1 persecond y}
    {Stn {!pagemgrThread} EpochScannedObjs 1 none y2}
    {Stn {!pagemgrThread} EpochNewObjsSize 1 none y2}
    {Stn {!pagemgrThread} EpochPossibleDeadSize 1 none y2}
    {Stn {!pagemgrThread} PossibleDeadSize 1 none y2}
  }
  set vsdtemplates(Garbage) {
    {Stn {!pagemgrThread} PagesNeedReclaimSize 1 none y2}
    {Stn {!pagemgrThread} ReclaimedPagesCount 1 none y2}
    {Stn {!pagemgrThread} EpochGcCount 1 persecond y}
    {Stn {!pagemgrThread} ReclaimCount 1 persecond y}
    {Gem+ {*Gc*} CommitCount 1 persecond y}
  }
  set vsdtemplates(PageServer) {
    {Pgsvr+ {*} AioDirtyCount 1 persecond y}
    {Pgsvr+ {*} AioCkptCount 1 persecond y}
    {Shrpc {*} LocalDirtyPageCount 1 none y}
  }
  set vsdtemplates(SpaceFree) {
    {Shrpc {*} FreeFrameCount 1 none y}
    {Stn {!pagemgrThread} FreePages 1 none y2}
  }
}
proc getStatType {s} {
  return [lindex [sl_stat -info $s] 0]
}
proc getStatLevel {s} {
  return [lindex [sl_stat -info $s] 1]
}
proc getStatFilter {s} {
  return [lindex [sl_stat -info $s] 2]
}
proc setStatFilter {s f} {
  global vsd
  sl_stat -info $s [list {} {} $f]
  set vsd(filter:$s) $f
  if {[string equal $f "Default"]} {
    unset vsd(filter:$s)
  }
}
proc setStatLevel {s l} {
  global vsd
  sl_stat -info $s [list {} $l]
  set vsd(level:$s) $l
}
proc getStatObjList {s} {
  return [lsort -dictionary [lindex [sl_stat -info $s] 3]]
}
proc getPlainStatDescription {s} {
  set txt [getStatDescription $s]
  regsub -all {\[(\w+) > \w+ Statistic\]} $txt {\1} res
  return $res
}

proc getStatDescription {s} {
  global statDocs
  
  if {[string equal -length 17 $s "PersistentCounter"]} {
    return $statDocs(PersistentCounter)
  }

  if [info exists statDocs($s)] {
    return $statDocs($s)
  } else {
    if {[string equal -length 13 $s "sharedCounter"]} {
      return $statDocs(sharedCounter)
    } else {
      if [fetchStatDef $s] {
	return $statDocs($s)
      } else {
	return ""
      }
    }
  }
}

proc isOSStat {s} {
  global statDefinitions
  if [info exists statDefinitions($s)] {
    return [lindex $statDefinitions($s) 3]
  } else {
    if [fetchStatDef $s] {
      return [lindex $statDefinitions($s) 3]
    } else {
      return false
    }
  }
}

proc getStatUnits {s} {
  global statDefinitions
  if [info exists statDefinitions($s)] {
    return [lindex $statDefinitions($s) 2]
  } else {
    if [fetchStatDef $s] {
      return [lindex $statDefinitions($s) 2]
    } else {
      return none
    }
  }
}

proc fetchStatDef {s} {
  if [catch {sl_stat -info $s} statDef] {
    return 0
  } else {
    set kind [lindex $statDef 0]
    set level [lindex $statDef 1]
    # 2 is filter
    # 3 is object list
    set isOSStat [lindex $statDef 4]
    set units [lindex $statDef 5]
    set docs [lindex $statDef 6]

    global statDefinitions statDocs
    set statDefinitions($s) [list $kind $level $units $isOSStat]
    set statDocs($s) $docs
    return 1
  }
}

proc selectByStatistic {} {
  global vsd
  set selCtr [getSelCtr]
  if {$selCtr != {}} {
    foreach ctr $selCtr {
      set objlist [getStatObjList $ctr]
      foreach obj $objlist {
	set objset($obj) 1
      }
    }
    set objnames [lsort [array names objset]]
    if {$objnames != {}} {
      foreach i [$vsd(instanceList) info children] {
	set iobj [lindex [$vsd(instanceList) info data $i] 1]
	if {[lsearch -exact $objnames $iobj] != -1} {
	  $vsd(instanceList) selection set $i
	}
      }
    }
  }
  updateGraphWidgetState
}

proc selectAllInstances {} {
  global vsd
  foreach i [$vsd(instanceList) info children] {
    $vsd(instanceList) selection set $i
  }
  updateGraphWidgetState
}

proc selectByType {} {
  global vsd
  set typeList {}
  foreach i [getSelectedInstances] {
    set data [$vsd(instanceList) info data $i]
    set iobj [lindex $data 1]
    if {[lsearch -exact $typeList $iobj] == -1} {
      lappend typeList $iobj
    }
  }
  if {$typeList != {}} {
    foreach i [$vsd(instanceList) info children] {
      set iobj [lindex [$vsd(instanceList) info data $i] 1]
      if {[lsearch -exact $typeList $iobj] != -1} {
	$vsd(instanceList) selection set $i
      }
    }
    updateGraphWidgetState
  }
}

# Start 47790

proc dismissSearchWindow {w} { 
  pack forget $w.msg
  dismissWindow $w
}

proc openGetSearchIdWindow {nameStr fieldWidth {showCaseCheckBox 0}} {
  global vsd
  set w .search${nameStr}Window
  toplevel $w -bd 2
  wm title $w "Enter a $nameStr"
  if {[info exists vsd(geometry:$w)] && $vsd(geometry:$w) != {}} {
    wm geometry $w $vsd(geometry:$w)
  }
  wm resizable $w 0 0
  wm group $w .
  frame $w.f -bd 3 -relief raised
  pack $w.f -fill both -expand yes -pady 2
  tixLabelEntry $w.f.entry -label "$nameStr: "
  set entry [$w.f.entry subwidget entry]
  $entry configure -width $fieldWidth
  pack $w.f.entry -fill x -expand yes -side left -padx 5 -pady 2
  $entry config -textvariable vsd(search${nameStr})
  button $w.f.search -text "Search"  -command selectBy${nameStr}
  button $w.f.cancel -text "Cancel"  -command [list dismissSearchWindow $w]
  pack $w.f.cancel $w.f.search -padx 5 -pady 2 -side right -expand no -fill none
  if {$showCaseCheckBox != 0} {
    label $w.wildMsg -font normalTextFont -text "Wildcards * and ? may be used" -state normal
    pack $w.wildMsg -side top -expand no -fill none -pady 5
    checkbutton $w.casebutton -text "Case Sensitive" -variable vsd(searchNameCaseSensitive)
    pack $w.casebutton -side bottom -expand no -fill none -pady 2
  }
  label $w.msg -font boldTextFont -text "$nameStr not found!" -state normal
  bind $w <Return> selectBy${nameStr}
  bind $w <KP_Enter> selectBy${nameStr}
  bind $w <Escape> [list dismissSearchWindow $w]
  focus $entry
}

proc selectByIntegerArg {nameStr instListIdex} {
  global vsd
  set found 0

  # ProcessId or SessionId
  set name "${nameStr}"
  set varName "search${name}"
  set id $vsd($varName)
  set w .${varName}Window

  # ProcessId: 4
  # SessionId: 5
  set sortInstIdx $vsd(ilColumnIdx:$name)
  
  if {$id eq "" || $id <= 0} {
    bell
    $w.msg configure -text "Invalid $nameStr"
    pack $w.msg -side bottom -pady 2
    return
  }

  foreach i [$vsd(instanceList) info children] {
    set item [lindex [lindex [$vsd(instanceList) info data $i] 0] $instListIdex]
    if {$item eq $id} {
      if {$found == 0} {
	$vsd(instanceList) selection clear
	$vsd(instanceList) anchor set $i
      }
      $vsd(instanceList) selection set $i
      incr found
    }
  }
  if {$found > 0} {
    # sort so the selected instances appear together
    sortInstancesIfNeeded $name $sortInstIdx
    foreach idx [$vsd(instanceList) info selection] {
      $vsd(instanceList) see $idx
    }    
    updateGraphWidgetState
    dismissSearchWindow $w
  } else {
    $w.msg configure -text "$nameStr not found!"
    pack $w.msg -side bottom
    bell
  }
}

proc selectByProcessId {} {
  selectByIntegerArg "ProcessId" 1
}

proc openSearchSessionIdWindow {} {
  openGetSearchIdWindow "SessionId" 4
}

proc selectBySessionId {} {
  selectByIntegerArg "SessionId" 2
}

proc openSearchProcessIdWindow {} {
  openGetSearchIdWindow "ProcessId" 10
}

proc selectByName {} {
  global vsd
  set found 0

  # ProcessId or SessionId
  set name "Name"
  set varName "search${name}"
  set searchStr $vsd($varName)
  set w .${varName}Window
  set sortInstIdx $vsd(ilColumnIdx:$name)

  if {$searchStr eq ""} {
    bell
    $w.msg configure -text "Invalid $nameStr"
    pack $w.msg -side bottom
    return
  }

  foreach i [$vsd(instanceList) info children] {
    set item [lindex [lindex [$vsd(instanceList) info data $i] 0] 0]
    if { $vsd(searchNameCaseSensitive) == 0} {
      set match [string match -nocase $searchStr $item]
    } else {
      set match [string match  $searchStr $item]
    }
    
    if {$match == 1} {
      if {$found == 0} {
	$vsd(instanceList) selection clear
	$vsd(instanceList) anchor set $i
      }
      $vsd(instanceList) selection set $i
      incr found
    }
  }
  if {$found > 0} {
    # sort so the selected instances appear together
    sortInstancesIfNeeded $name $sortInstIdx
    foreach idx [$vsd(instanceList) info selection] {
      $vsd(instanceList) see $idx
    }
    updateGraphWidgetState
    dismissSearchWindow $w
  } else {
    $w.msg configure -text "$searchStr not found!"
    pack $w.msg -side bottom
    bell
  }   
}

proc openSearchNameWindow {} {
  openGetSearchIdWindow "Name" 30 1
}
# End  47790

proc doPopup {w m x y} {
  global vsd
  if {[string equal $w [winfo containing $x $y]]} {
    tk_popup $m $x $y
  }
}

proc setHdrSortHighlight {} {
  global vsd
  set type $vsd(def:sortbytype)
  if {$vsd(def:sortdecreasing)} {
    set theImage downbm 
  } else {
    set theImage upbm
  }
  # Workaround bug 44084

  # apply up/down error to the primary sort column
  set w $vsd(instanceList).hdrBut$type
  if [winfo exists $w] {
    $w configure -image $theImage -font boldMainWindowFont
  }

  # If the new primary sort column is the current secondary sort column,
  # choose a new secondary sort key from the list of keys.
  # Primary sort key takes precedence!
  if {$vsd(secondarySortKeyIdx) == [getPrimarySortKeyIndex]} {
    setDefaultSecondarySortKey
  }
}

proc sortInstancesIfNeeded {type idx} {
  global vsd
  set oldtype $vsd(def:sortbytype)
  if {[string equal $type $oldtype]} {
    return
  }
  sortInstances $type $idx
}

proc sortInstances {type idx} {
  global vsd
  set oldtype $vsd(def:sortbytype)
  if {[string equal $type $oldtype]} {
    set vsd(def:sortdecreasing) [expr {!$vsd(def:sortdecreasing)}]
  } else {
    set oldw $vsd(instanceList).hdrBut$oldtype
    if {[winfo exists $oldw]} {
      $oldw configure -image "" -font boldMainWindowFont
    }
    set vsd(def:sortbytype) $type
    set vsd(def:sortdecreasing) 1
  }
  setHdrSortHighlight
  setInstanceList
}

proc getScriptName {} {
  global vsd
  set exe [info nameofexecutable]
  set result [file join [file dirname $exe] "vsd"]

  return $result
}

proc restartVsd {} {
  global vsd
  set vsd(restarting) 1
  uplevel #0 {source [getScriptName]}
}

proc tixItemStyle {args} {
  return [eval tixDisplayStyle $args]
}

# Return 1 if $GEMSTONE is set, or 0 otherwise
proc haveGemStone {} {
  if ![info exists ::env(GEMSTONE)] {
    return 0
  } else {
    return 1
  }
}

# set the secondary sort key to be the column under the right-clicked button
proc button3SortProc {w newIdx} {
  global vsd
  if {$newIdx == $vsd(secondarySortKeyIdx)} {
    # no change in secondary sort key, so do nothing.
    return
  }

  if {$newIdx == [getPrimarySortKeyIndex]} {
    # selected column is already the primary sort key.  Do nothing.
    return
  }

  primSetSecondarySortKey $newIdx

  # resort the instance list
  setInstanceList
}

# Source a file with name $fn located in the $VSDHOME directory
# in the global context.
# On error, prints a message and exits 
proc sourceVsdHomeFile {fn  {mustExist 0}} {
  global vsd fileToSource
  # fileToSource must be in the global context since that's where we're sourcing
  set fileToSource "$vsd(VSDHOME)/$fn"
  if {[file exists $fileToSource]} {
    if {[catch {uplevel #0 {source $fileToSource}} msg]} {
      set errMsg "Fatal error: could not read '$fileToSource' because:\n$msg"
      puts stderr $errMsg
      fatalError $errMsg
    } else {
      #      puts "sourced file $fileToSource"
      lappend vsd(sourcedFiles) $fileToSource
      return 1
    }
  } elseif {$mustExist} {
    fatalError "sourceVsdHomeFile: Required file $fileToSource does not exist"
  }
  return 0
}

# Source a file with name $fn located in the vsd bin directory
# in the global context.
# On error, prints a message and exits 
proc sourceVsdBinFile {fn {mustExist 1} } {
  global fileToSource vsd
  # fileToSource must be in the global context since that's where we're sourcing
  set fileToSource [buildVsdFilePathFor $fn]
  if {[file exists $fileToSource]} {
    if {[catch {uplevel #0 {source $fileToSource}} msg]} {
      set errMsg "Fatal error: could not read '$fileToSource' because:\n$msg"
      puts stderr $errMsg
      fatalError $errMsg
    } else {
      #      puts "sourced file $fileToSource"
      lappend vsd(sourcedFiles) $fileToSource
      return 1
    }
  } elseif {$mustExist} {
    fatalError "sourceVsdBinFile: Required file $fileToSource does not exist"
  }
  return 0
}

proc printDefaultFonts {} {
  puts "Default Tk Fonts (configure, actual):"
  foreach aFont {TkDefaultFont TkTextFont TkFixedFont TkMenuFont TkHeadingFont \
		   TkCaptionFont TkSmallCaptionFont TkIconFont TkTooltipFont} {
    puts "  $aFont [font configure $aFont]"
    puts "  font actual $aFont [font actual $aFont]\n"
  }

#  foreach family [lsort -dictionary [font families]] {
#    puts "$family"
#  }
  
}

proc enableClearHidden {} {
  global vsd
  set m $vsd(w:mainmenu).hideInst
  $m entryconfigure "Clear All Hidden" -state normal
  set m $vsd(processMenu).hideInst
  $m entryconfigure "Clear All Hidden" -state normal
}

proc disableClearHidden {} {
  global vsd
  set m $vsd(w:mainmenu).hideInst
  $m entryconfigure "Clear All Hidden" -state disabled
  set m $vsd(processMenu).hideInst
  $m entryconfigure "Clear All Hidden" -state disabled  
}


proc clearHiddenInstances {} {
  global vsd
  foreach key [dict keys $vsd(hideInstDict)] {
    set vsd($key) 0
  }  
  set vsd(hiddenInstanceTypes) {}
  disableClearHidden
  setInstanceList
}

proc updateHiddenInstances {} {
  global vsd
  set newTypes {}
  dict for {key value} $vsd(hideInstDict) {
    if {$vsd($key)} {
      lappend newTypes $value
    }
  }
  set vsd(hiddenInstanceTypes) $newTypes
  if {[llength $newTypes]} {
    enableClearHidden
  } else {
    disableClearHidden
  }
  setInstanceList
}


proc setTextFont {font args} {
  global vsd
  set saved $vsd(textfont)
  set vsd(textfont) [font actual $font]
  font delete normalTextFont boldTextFont italicTextFont boldItalicTextFont fixedTextFont
  createTextFonts
}

proc setFileInactiveTime {w accept} {
  global vsd
  if {$accept} {
    set entry [$w.ctrl subwidget entry]
    # Unbind return key to prevent infinite loop
    bind $entry <Return> ""
    # generate <Return> event to ensure modified value is remembered.
    # otherwise we forget the value if user typed in a new value but
    # didn't press <Return> before clicking OK
    event generate $entry <Return>
    set vsd(fileInactiveTime) $vsd(fileInactiveTime:tmp)
  }
  unset vsd(fileInactiveTime:tmp)
  destroy $w
}

proc changeInactiveFileTimeout {} {
  global vsd
  set name .fileInactiveWin
  set vsd(fileInactiveTime:tmp) $vsd(fileInactiveTime)
  toplevel $name -bd 2
  wm title $name "Inactive File Timeout"
  wm group $name .
  wm minsize $name 300 75
  wm maxsize $name 300 75
  tixControl $name.ctrl -label "Seconds" -min 1 -max 3600 -integer true \
    -variable vsd(fileInactiveTime:tmp)
  pack $name.ctrl -side top
  button $name.cancel -text "Cancel" -command  [list setFileInactiveTime $name 0]
  button $name.ok -text "OK" -command [list setFileInactiveTime $name 1]
  pack $name.ok -side left
  pack $name.cancel -side right
  set entry [$name.ctrl subwidget entry]
  bind $entry <Return> [list setFileInactiveTime $name 1]
  bind $name  <Escape> [list setFileInactiveTime $name 0]
  focus $entry
}

proc RunVSD {w} {
  global vsd vsdtemplates isWindows widgetHelp env sortKeyIntToStringMap
  global sortKeyStringToIntMap sortkeyIntToProcNameMap 
  set vsd(time_shift_hours) 0
  set widgetHelp($w) {Main Window Features}
  
  if {[info exists env(GEMSTONE_LANG)]} {
    unset env(GEMSTONE_LANG)
  }

  # 47637 - use VSDHOME instead of ~
  if {[info exists env(VSDHOME)] && [file isdirectory $env(VSDHOME)] && \
	[file writable $env(VSDHOME)]} {
    set vsd(VSDHOME) $env(VSDHOME)
  } else {
    set vsd(VSDHOME) ~
  }

  set vsd(hideInstDict) [dict create hideShrPcMon Shrpc hideStone Stn hidePgsvr Pgsvr hideGem Gem \
			   hideAppStat AppStat hideStatMon Statmon hideSystem system hideNetldi Netldi \
			   hideGemThr GemThr]
  
  foreach key [dict keys $vsd(hideInstDict)] {
    set vsd($key) 0
  }

  # 49836
  set vsd(helpGeo) {}
  set vsd(hiddenInstanceTypes) {}
  
  set vsd(leftTimeTrimmerSaveDefaults) 0
  set vsd(default_left_trim_secs) 0
  set vsd(default_left_trim_mins) 0
  set vsd(default_left_trim_hrs)  0
  
  set vsd(maintainer) "vsd@gemtalksystems.com"
  set vsd(helpIdx) 0
  set vsd(bulletImage) [image create photo -data \
			  "R0lGODlhEAAFAID/AMDAwAAAACH5BAEAAAAALAAAAAAQAAUAAAINhA+hyKff2FvRzYogKwA7"]
  
  set vsd(exec:lpr) "lpr"
  set vsd(exec:mail) "/usr/sbin/sendmail"
  set vsd(exec:date) "date"
  set vsd(exec:uname) "uname -a"
  set vsd(symbollist) {none scross splus square triangle circle diamond plus cross}

  bind Toplevel <FocusIn> {+ chartFocus %W}
  set vsd(w) $w

  set vsd(initialBgColor) [$w cget -background]
  set vsd(currentBgColor) $vsd(initialBgColor)
  set vsd(currentPlotBgColor) white
  set vsd(masterBgColorChanged) 0

  set vsd(searchProcessId) ""
  set vsd(searchSessionId) ""
  set vsd(searchName) ""
  set vsd(searchNameCaseSensitive) 1
  set vsd(confirmonexit) 1
  set vsd(traceAPICalls) 0
  set vsd(warningGiven) 0
  set vsd(sdtrace) 0
  set vsd(xcount) 0
  set vsd(ycount) 0
  set vsd(lastdatafile) {}
  set appendedFileMap {}
  set vsd(lastctr) {}
  set vsd(counterType) {}
  set vsd(selectedInstIds) {}
  set vsd(selectedProcs) {}
  set vsd(zeroCtrs) {}
  set vsd(autoUpdatesDisabled) 0
  if {$isWindows} {
    set vsd(graphcursor) arrow
  } else {
    set vsd(graphcursor) top_left_arrow
  }
  set vsd(xaxis:defaultTitle) "Time Axis"
  set vsd(yaxis:defaultTitle) "Left  Axis"
  set vsd(y2axis:defaultTitle) "Right  Axis"
  set vsd(obsoleteCounters) "StatTypeNum Time ProcessName ProcessNumber ProcessId SessionId Offset DeviceNumber"
  if {[winfo depth .] == 1} {
    set vsd(colorlist) {black}
    set vsd(activecolor) black
  } else {
    set vsd(colorlist) {blue cyan brown green \
			  purple pink magenta black \
			  PeachPuff SeaGreen DodgerBlue orchid }
    set vsd(activecolor) red
  }

  set vsd(inactiveDataFiles) [dict create]
  set vsd(dirContentsDict) [dict create]
  set vsd(uniqueNameDict) [dict create]
  set vsd(appendedFileNamesDict) [dict create]
  set vsd(instNameToProcKindDict) [dict create]
  set vsd(allCounters) [dict create]
  set vsd(listOfCachePatterns) {}
  set vsd(listOfVersionPatterns) {}
  set vsd(brokenLinkColor) red
  set vsd(normalLinkColor) blue
  set vsd(visitedLinkColor) brown
  
  set vsd(freeGeometryIds) {}
  set vsd(graphcount) 0
  set vsd(graphcreates) 0

  # Fix 41952 - add .fileselector to the list
  set vsd(winnames) {.monitors .monstatus .ctrinfo .datawin \
                       .timeShifter .fileselector .rollingTimeTrimmer .searchProcessIdWindow
    .searchSessionIdWindow .searchNameWindow }
  foreach cw $vsd(winnames) {
    set vsd(geometry:$cw) {}
  }
  # Use this to remember if the user explicitly changed directory
  set vsd(cwd) {}
  set vsd(lastdatadir) {}
  set vsd(snapshotDirName) [file normalize [getVsdDataDir]]
  set vsd(snapshotFileName) "chart_snapshot.gif"
  set vsd(prevGraphWindowList) {}
  set vsd(prevGraphWindowGeom) ""
  set vsd(rollingTimeWindowMenuText) "Set Rolling Time Window..."
  set vsd(def:geometry) {}
  set vsd(def:xformat) "time"
  set vsd(def:linestyle) "linear"
  set vsd(def:xaxis:showtitle) 1
  set vsd(def:yaxis:showtitle) 1
  set vsd(def:y2axis:showtitle) 1
  set vsd(def:showcurxy) 1
  set vsd(def:showminmax) 0
  set vsd(def:showlinestats) 1
  set vsd(def:showcrosshairs) 0
  set vsd(def:showlegend) 1
  set vsd(def:showgridlines) 0
  set vsd(def:sortbytype) Type
  set vsd(def:sortdecreasing) 1
  set vsd(filelist) {}
  set vsd(statsrc) {}
  set vsd(appendMode) 0
  set vsd(autoUpdate) 0
  set vsd(autoAppend) 0
  set vsd(autoUpdateIntervalMs) 1000
  set vsd(fileInactiveTime) 90
  set vsd(showCtrInfo) 0
  set vsd(absoluteTSMode) 1
  set vsd(showCommas) 1
  # 46239
  set vsd(autoSaveConfig) 1
  set vsd(copyChildren) 1
  set vsd(singleFileMode) 1
  set vsd(lastEnabledFiles) {}
  set vsd(templatesUseSelection) 0
  set vsd(file:isEnabled) 1
  set vsd(cursorblink) 1
  auto_load tk_chooseColor    
  auto_load tk_messageBox    
  auto_load tk_getOpenFile
  auto_load tk_getSaveFile    
  auto_load tixMeter
  set vsd(hasMeter) [expr {[info commands tixMeter] != ""}]

  set vsd(wv:statmoninterval) 20
  set vsd(wv:statmonflush) 0
  set vsd(wv:statmonlevel) 1
  set vsd(wv:statmonpids) ""
  set vsd(combineInstances) 0
  set vsd(combineFiles) 0
  set vsd(noFlatlines) 0
  set vsd(noAllZeros) 0
  set vsd(clipboard) {}
  set vsd(ruleCount) 0
  set vsd(longOpCancel) 0
  set vsd(longOpW) ""

  set vsd(instancePaneSize) 115
  set vsd(statisticPaneSize) 50
  set vsd(fileOpenGeometry) {}

  # Translate int index (0 based) to column name
  set sortKeyIntToStringMap(0) "StartTime"
  set sortKeyIntToStringMap(1) "EndTime"
  set sortKeyIntToStringMap(2) "File"
  set sortKeyIntToStringMap(3) "Samples"
  set sortKeyIntToStringMap(4) "ProcessId"
  set sortKeyIntToStringMap(5) "SessionId"
  set sortKeyIntToStringMap(6) "Type"
  set sortKeyIntToStringMap(7) "Name"

  # Translate column name to int index
  set sortKeyStringToIntMap(StartTime) 0
  set sortKeyStringToIntMap(EndTime) 1
  set sortKeyStringToIntMap(File) 2
  set sortKeyStringToIntMap(Samples) 3
  set sortKeyStringToIntMap(ProcessId) 4
  set sortKeyStringToIntMap(SessionId) 5
  set sortKeyStringToIntMap(Type) 6
  set sortKeyStringToIntMap(Name) 7

  # translate column index to sort proc
  set sortkeyIntToProcNameMap(0) "getPrimStartTimeSortKey"
  set sortkeyIntToProcNameMap(1) "getPrimEndTimeSortKey"
  set sortkeyIntToProcNameMap(2) "getPrimFileSortKey"
  set sortkeyIntToProcNameMap(3) "getPrimSamplesSortKey"
  set sortkeyIntToProcNameMap(4) "getPrimPidSortKey"
  set sortkeyIntToProcNameMap(5) "getPrimSidSortKey"
  set sortkeyIntToProcNameMap(6) "getPrimTypeSortKey"
  set sortkeyIntToProcNameMap(7) "getPrimNameSortKey"

  set vsd(numberOfSortKeys) 8
  set vsd(defaultSortOrder) [list 6 7 2 0 1 5 4 3]

  set vsd(wv:statmonCompressGz) 0
  set vsd(wv:statmonCompressLz4) 1

  set vsd(wv:statmonargs) "" 

  defaultTemplates

  makeErrorDialog $w

  loadStatisticAliases
  sl_stat -level "wizard"
  
  set vsd(vsdRcFn) ".vsdrc"
  set vsd(vsdTemplatesFn) ".vsdtemplates"
  set vsd(vsdConfigFn) ".vsdconfig"
  # 47637 - use VSDHOME instead of ~
  sourceVsdHomeFile $vsd(vsdRcFn)
  
  if {[string equal $vsd(def:sortbytype) "Start Time"]} {
    set vsd(def:sortbytype) "StartTime"
  }
  
  if {![sourceVsdHomeFile $vsd(vsdTemplatesFn)]} {
    writeTemplateFile
  }

  if {![sourceVsdHomeFile $vsd(vsdConfigFn)]} {
    createVsdConfig "$vsd(VSDHOME)/$vsd(vsdConfigFn)"
  }
  
  # 46260 - hard code to false
  set vsd(combineFiles) 0

  loadStatisticDefinitions ; # do this after reading $vsd(vsdConfigFn) and .vsdrc

  setBuiltinCtrAliases

#  printDefaultFonts
  
  if {![info exists vsd(smallfont)]} {
    if {$isWindows} {
      #        set vsd(smallfont) [lreplace [tix option get font] 1 1 "8"]
      #        set vsd(smallfont) "-Adobe-Helvetica-Bold-R-Normal--*-120-*"
      # bug 45377 - make font size 10 on Windows
      set vsd(smallfont) {-family Helvetica -size 10}
    } else {
      set vsd(smallfont) {-family lucida -size 8}
    }
  }
  eval font create vsdsf $vsd(smallfont)

  if {![info exists vsd(textfont)]} {
    # does a better way of getting default text font exist?
    # Why yes it does!  Use TkDefaultFont here to fix 45174  
    if {$isWindows} {
      # 45377 - make size 12 on Windows
      eval font configure TkDefaultFont -size 12
    }
    set vsd(textfont) [font configure TkDefaultFont]
  }
  createTextFonts

  if {![info exists vsd(dataLogFont)]} {
    set vsd(dataLogFont) [font actual fixedTextFont]
  }
  createDataLogFont
  
  # 46261
  if {![info exists vsd(mainWindowFont)]} {
    set vsd(mainWindowFont) {-family Helvetica -size 11}
  }

  createMainWindowFonts
  if {! $vsd(cursorblink)} {
    option add *insertOffTime 0
  }
  
  # Sometimes the geometry gets saved with a strange value
  if {[string match "1x1+*" $vsd(def:geometry)]} {
    set vsd(def:geometry) ""
  }
  foreach cw $vsd(winnames) {
    if {[string match "1x1+*" $vsd(geometry:$cw)]} {
      set vsd(geometry:$cw) ""
    }
  }

  if {$vsd(def:geometry) != {}} {
    wm geometry $vsd(w) $vsd(def:geometry)
  }

  set vsdExeName [info nameofexecutable]

  set mf [frame $w.mf -bd 2 -relief raised]
  #  set mainBoldFont boldMainWindowFont
#  set mainFont [tix option get font]
#  set mainBoldFont [tix option get bold_font]

  set mainFont normalMainWindowFont 
  set mainBoldFont boldMainWindowFont

  menubutton $mf.main -menu $mf.main.m -text "Main" -underline 0 \
    -takefocus 0 -font $mainBoldFont
  menu $mf.main.m -tearoff 0
  set vsd(w:mainmenu) $mf.main.m
  $mf.main.m add command -label "Load Data File(s)..." -underline 0 \
    -command browseDataFile -font $mainBoldFont
  $mf.main.m add command -label "Append Data File(s)..." -underline 0 \
    -command browseDataFileAppend -font $mainBoldFont

  set monitorState "normal"
  if {$isWindows || ![haveGemStone]} {
    # fix for bug 27746
    set monitorState "disabled"
  } 
  $mf.main.m add command -label "Monitor..." -command {doMonitor} \
    -state $monitorState -accelerator "Ctrl+m" -font $mainBoldFont

  $mf.main.m add command -label "Change Directory..." -command {doChangeDirectory} \
    -font $mainBoldFont
  $mf.main.m add command -label "Change Time Offset..." -command {doAdjustTimeOffset} \
    -font $mainBoldFont
  $mf.main.m add command -label "Set Master Background Color..." -command {pickMasterBackgroundColor} \
    -accelerator "Ctrl+b" -font $mainBoldFont

  $mf.main.m add command -label "Copy Selection" -command {doCopySelection} \
    -accelerator "Ctrl+c" -font $mainBoldFont
  bind $vsd(w) <Control-m> [list $mf.main.m invoke "Monitor..."]
  bind $vsd(w) <Control-b> [list $mf.main.m invoke "Set Master Background Color..."]
  bind $vsd(w) <<Copy>> [list $mf.main.m invoke "Copy Selection"]
  $mf.main.m add check -label "Show Statistic Info" -underline 0 \
    -variable vsd(showCtrInfo) \
    -command {doCtrInfo} -font $mainBoldFont

  # 47790
  $mf.main.m add cascade -label "Select Instances By..." -menu $mf.main.m.selInstBy \
     -font $mainBoldFont
  menu $mf.main.m.selInstBy -tearoff 0
   $mf.main.m.selInstBy add command -label "All" \
    -command {selectAllInstances} -font $mainBoldFont  
  $mf.main.m.selInstBy add command -label "Clear" \
    -command {$vsd(instanceList) selection clear; updateGraphWidgetState} \
    -font $mainBoldFont
  $mf.main.m.selInstBy add command -label "Name..." \
    -command openSearchNameWindow -font $mainBoldFont  
  $mf.main.m.selInstBy add command -label "Process ID..." \
    -command openSearchProcessIdWindow -font $mainBoldFont
  $mf.main.m.selInstBy add command -label "Session ID..." \
    -command openSearchSessionIdWindow -font $mainBoldFont
  $mf.main.m.selInstBy add command -label "Statistic" \
    -command {selectByStatistic} -font $mainBoldFont  
  $mf.main.m.selInstBy add command -label "Type" \
    -command {selectByType} -font $mainBoldFont  
  
  $mf.main.m add cascade -label "Hide Instances By Type..." -menu $mf.main.m.hideInst \
     -font $mainBoldFont
  menu $mf.main.m.hideInst -tearoff 0
  $mf.main.m.hideInst add command -label "Clear All Hidden" \
    -command clearHiddenInstances -font $mainBoldFont -state disabled
  $mf.main.m.hideInst add separator
  $mf.main.m.hideInst add check -label "AppStat" -variable vsd(hideAppStat) \
    -command updateHiddenInstances -font $mainBoldFont
  $mf.main.m.hideInst add check -label "Gem" -variable vsd(hideGem) \
    -command updateHiddenInstances -font $mainBoldFont
  $mf.main.m.hideInst add check -label "GemThr" -variable vsd(hideGemThr) \
    -command updateHiddenInstances -font $mainBoldFont  
  $mf.main.m.hideInst add check -label "Host System" -variable vsd(hideSystem) \
    -command updateHiddenInstances -font $mainBoldFont
  # 49252
  $mf.main.m.hideInst add check -label "Netldi" -variable vsd(hideNetldi) \
    -command updateHiddenInstances -font $mainBoldFont    
  $mf.main.m.hideInst add check -label "Page Server" -variable vsd(hidePgsvr) \
    -command updateHiddenInstances -font $mainBoldFont  
  $mf.main.m.hideInst add check -label "StatMon" -variable vsd(hideStatMon) \
    -command updateHiddenInstances -font $mainBoldFont
  $mf.main.m.hideInst add check -label "ShrPcMonitor" -variable vsd(hideShrPcMon) \
    -command updateHiddenInstances -font $mainBoldFont  
  $mf.main.m.hideInst add check -label "Stone" -variable vsd(hideStone) \
    -command updateHiddenInstances -font $mainBoldFont

  
  $mf.main.m add check -label "Combine" \
    -variable vsd(combineInstances) -font $mainBoldFont
  # 46260 - remove Combine Across Files
  $mf.main.m add check -label "No Flatlines" \
    -variable vsd(noFlatlines) -command changeNoFlatlines \
    -font $mainBoldFont
  $mf.main.m add check -label "No All Zeros" \
    -variable vsd(noAllZeros) -command changeNoAllZeros \
    -font $mainBoldFont  
  $mf.main.m add check -label "Single File Mode" \
    -variable vsd(singleFileMode) -command singleFileMode \
    -font $mainBoldFont
  $mf.main.m add check -label "Absolute TimeStamps" \
    -variable vsd(absoluteTSMode) -font $mainBoldFont
  $mf.main.m add check -label "Copy Referenced Lines" \
    -variable vsd(copyChildren) -font $mainBoldFont

  set prefMenu $mf.main.m.prefs
  menu $prefMenu -tearoff 0

  # 49055
  set fontprefs $prefMenu.fontprefs
  menu $fontprefs -tearoff 0

  $fontprefs add command -label "Main Window" \
    -command [list chooseVsdFont mainWindowFont setMainWindowFont {Main}] \
    -font $mainBoldFont

  $fontprefs add command -label "Charts" \
    -command [list chooseVsdFont smallfont setChartFont Chart] \
    -font $mainBoldFont

  $fontprefs add command -label "Chart Data Logs" \
    -command [list chooseVsdFont dataLogFont setDataLogFont {Data Log}] \
    -font $mainBoldFont

  $fontprefs add command -label "Help Text" \
    -command [list chooseVsdFont textfont setTextFont Text] \
    -font $mainBoldFont      

  $prefMenu add command -label "Change Inactive File Timeout" \
    -command changeInactiveFileTimeout -font $mainBoldFont
  
  $prefMenu add cascade -label "Choose Fonts..." -menu $fontprefs -font $mainBoldFont

  $prefMenu add check -label "Confirm Exit" \
    -variable vsd(confirmonexit) -font $mainBoldFont
  
  # 46239
  $prefMenu add check -label "Save Settings on Exit" -variable vsd(autoSaveConfig) \
    -font $mainBoldFont

  $prefMenu add check -label "Shows Commas In Numbers" -variable vsd(showCommas) \
    -font $mainBoldFont -command updateAllActiveLines

  $mf.main.m add cascade -label "Settings" -menu $prefMenu \
    -font $mainBoldFont

  $mf.main.m add command -label "Save Settings" -command writeStartupFile \
    -font $mainBoldFont

  if [wizardMode] {
    $mf.main.m add command -label "Dump Statistic Info"  -command dumpCtrInfo \
      -font $mainBoldFont
    $mf.main.m add check -label "Trace API Calls" \
      -variable vsd(traceAPICalls) -command traceAPICalls \
      -font $mainBoldFont
    $mf.main.m add command -label "ForceError"  -command {forcedError } \
      -font $mainBoldFont
    set aliasMenu $mf.main.m.aliases
    menu $aliasMenu -tearoff 0
    $aliasMenu add command -label "Dump listOfCachePatterns"  -command dumpCachePatterns \
      -font $mainBoldFont       
    $aliasMenu add command -label "Dump cacheIdAliasDict"  -command dumpCacheIdAliasDict \
      -font $mainBoldFont
    $aliasMenu add command -label "Dump allCounters keys"  -command dumpAllCtrsKeys \
      -font $mainBoldFont
    $aliasMenu add command -label "Dump alias template types"  -command dumpAliasTemplateTypes \
       -font $mainBoldFont    
    $aliasMenu add command -label "Dump ctrAliases (LARGE!)"  -command dumpCtrAliases \
      -font $mainBoldFont
    $aliasMenu add command -label "Dump instNameToProcKind" -command dumpInstNameToProcKindDict \
       -font $mainBoldFont
     $mf.main.m add cascade -label "Aliases" -menu $aliasMenu -font $mainBoldFont        
  }

  $mf.main.m add separator
  $mf.main.m add command -label "Exit     " -underline 1 -command ExitVSD \
    -font $mainBoldFont
  pack $mf.main -side left

  set widgetHelp($mf.main) {Main Window Main Menu}

  menubutton $mf.file -menu $mf.file.m -text "File" -underline 0 \
    -takefocus 0 -font $mainBoldFont
  menu $mf.file.m -tearoff 0
  set vsd(w:filemenu) $mf.file.m
  $mf.file.m add command -label "Info..." -underline 0 -state disabled \
    -command fileInfo -font $mainBoldFont
  $mf.file.m add command -label "Update" -state disabled \
    -command {fileUpdate $vsd(statsrc)} \
    -accelerator "Ctrl+u" -font $mainBoldFont
  $mf.file.m add command -label "Update All" \
    -command {fileUpdateAll} \
    -accelerator "Ctrl+U" -font $mainBoldFont
  bind $vsd(w) <Control-u> [list $mf.file.m invoke "Update"]
  bind $vsd(w) <Control-U> [list $mf.file.m invoke "Update All"]
  $mf.file.m add check -label "Auto Update" -state disabled \
    -command autoUpdateMode -variable vsd(autoUpdate) \
    -font $mainBoldFont -accelerator "Ctrl+A"
  # 47581
  bind $vsd(w) <Control-A> [list $mf.file.m invoke "Auto Update"]
  if {! $isWindows} {
    $mf.file.m add check -label "Auto Append Next File" -state disabled \
      -variable vsd(autoAppend) -command autoAppendMode \
      -font $mainBoldFont
  }
  $mf.file.m add check -label "Enabled" -state disabled \
    -command fileEnableMode -variable vsd(file:isEnabled) \
    -font $mainBoldFont
  $mf.file.m add command -label "Untrim Left" -state disabled \
    -command {fileUntrimLeft $vsd(statsrc)} \
    -font $mainBoldFont
  $mf.file.m add command -label "Untrim Right" -state disabled \
    -command {fileUntrimRight $vsd(statsrc)} \
    -font $mainBoldFont
  $mf.file.m add command -label $vsd(rollingTimeWindowMenuText) -accelerator "Ctrl+R" \
    -state disabled -command {openRollingTimeTrimmer } -font $mainBoldFont
  bind $vsd(w) <Control-R> [list $mf.file.m invoke $vsd(rollingTimeWindowMenuText) ]
  if [wizardMode] {
    $mf.file.m add command -label "Dump" -state disabled -command fileDump \
      -font $mainBoldFont
  }
  pack $mf.file -side left
  set widgetHelp($mf.file) {Main Window File Menu}

  menubutton $mf.chart -menu $mf.chart.m -text "Chart" -underline 0 \
    -takefocus 0 -font $mainBoldFont
  menu $mf.chart.m -tearoff 0
  set vsd(w:chartmenu) $mf.chart.m
  $mf.chart.m add command -label "Add To Chart" \
    -command {doChart 0} -accelerator "Ctrl+a" -state disabled \
    -font $mainBoldFont
  bind $vsd(w) <Control-a> [list $vsd(w:chartmenu) invoke "Add To Chart"]
  $mf.chart.m add command -label "New Chart..." \
    -command {doChart 1} -accelerator "Ctrl+n" -state disabled \
    -font $mainBoldFont
  bind $vsd(w) <Control-n> [list $vsd(w:chartmenu) invoke "New Chart..."]

  $mf.chart.m add separator
  $mf.chart.m add check -label "Show Legend" \
    -variable vsd(def:showlegend) -font $mainBoldFont

  set mtime $mf.chart.m.time
  set widgetHelp($mtime) {Main Chart Menu Time Format SubMenu}
  menu $mtime -tearoff 0
  $mtime add radio -label "Seconds" -value "elapsed" \
    -variable vsd(def:xformat) -font $mainBoldFont
  $mtime add radio -label "Hour:Minute:Second" -value "time" \
    -variable vsd(def:xformat) -font $mainBoldFont
  $mtime add radio -label "Month/Day Hour:Minute:Second" -value "date" \
    -variable vsd(def:xformat) -font $mainBoldFont

  $mf.chart.m add cascade -label "Time Format" -menu $mtime -underline 0 \
    -font $mainBoldFont

  set mstyle $mf.chart.m.styles
  set widgetHelp($mstyle) {Main Chart Menu Default Line Style SubMenu}
  menu $mstyle -tearoff 0
  foreach style {linear step natural quadratic} {
    $mstyle add radio -label $style -variable vsd(def:linestyle) \
      -font $mainBoldFont
  }
  $mf.chart.m add cascade -label "Default Line Style" -menu $mstyle \
    -font $mainBoldFont

  $mf.chart.m add check -label "Show Time Axis Title" \
    -variable vsd(def:xaxis:showtitle) -font $mainBoldFont
  $mf.chart.m add check -label "Show Left Axis Title" \
    -variable vsd(def:yaxis:showtitle) -font $mainBoldFont
  $mf.chart.m add check -label "Show Right Axis Title" \
    -variable vsd(def:y2axis:showtitle) -font $mainBoldFont
  $mf.chart.m add check -label "Show Current Values" \
    -variable vsd(def:showcurxy) -font $mainBoldFont
  $mf.chart.m add check -label "Show Min and Max" \
    -variable vsd(def:showminmax) -font $mainBoldFont
  $mf.chart.m add check -label "Show Line Stats" \
    -variable vsd(def:showlinestats) -font $mainBoldFont
  $mf.chart.m add check -label "Show CrossHairs" \
    -variable vsd(def:showcrosshairs) -font $mainBoldFont
  $mf.chart.m add check -label "Show Grid Lines" \
    -variable vsd(def:showgridlines) -font $mainBoldFont
  if {[info commands tk_chooseColor] != ""} {
    $mf.chart.m add command -label "Selected Line Color..." \
      -command setActiveColor -font $mainBoldFont
  }
  
  $mf.chart.m add separator
  $mf.chart.m add command -label "Close All Charts" -command {closeAllCharts} \
    -font $mainBoldFont
  pack $mf.chart -side left
  set widgetHelp($mf.chart) {Main Window Chart Menu}

  menubutton $mf.template -menu $mf.template.m -text "Template" -underline 0 \
    -takefocus 0 -font $mainBoldFont
  menu $mf.template.m -tearoff 0
  set vsd(w:templatemenu) $mf.template.m
  set vsd(w:templatecascade) $mf.template.m.templates

  $mf.template.m add command -label "Reload Template File" \
    -command "reloadTemplates $vsd(w:templatecascade)" -underline 0 -font $mainBoldFont

  set widgetHelp($vsd(w:templatecascade)) {Template Menu New Template Chart SubMenu}
  menu $vsd(w:templatecascade) -tearoff 0
  createTemplateMenu $vsd(w:templatecascade)
  $mf.template.m add cascade -label "New Template Chart" \
    -menu $vsd(w:templatecascade) -underline 0 -state disabled -font $mainBoldFont
  $mf.template.m add check -label "Templates Use Selection" \
    -variable vsd(templatesUseSelection) -font $mainBoldFont

  pack $mf.template -side left
  set widgetHelp($mf.template) {Main Window Template Menu}

  menubutton $mf.help -menu $mf.help.m -text "Help" -underline 0 \
    -takefocus 0 -font $mainBoldFont
  menu $mf.help.m -tearoff 0
  $mf.help.m add command -label "How to..." -underline 0 -command HowToHelp -font $mainBoldFont
  $mf.help.m add command -label "Main Window..." -command {showMainHelp} -font $mainBoldFont
  $mf.help.m add command -label "Chart Window..." -command {showChartHelp} -font $mainBoldFont
  $mf.help.m add command -label "All Topics..." -command {allHelp} -font $mainBoldFont
  $mf.help.m add command -label "All Help Text..." -command {allFlatHelp} -font $mainBoldFont
  $mf.help.m add separator

  tk fontchooser configure -parent .vsd
  
  $mf.help.m add command -label "About VSD..." -underline 0 -command {showAboutHelp} -font $mainBoldFont
  pack $mf.help -side right
  set widgetHelp($mf.help) {Main Window Help Menu}

  pack $mf -side top -fill x

  # We create the frame and the ScrolledHList widget
  # at the top of the dialog box
  #
  # Fix 45853 - set anchor to w, not e
  tixComboBox $w.filelist -label "File:" -anchor w \
    -editable true -command chooseDataFile -options {
      listbox.height 1
    }
  set vsd(w:filelist) $w.filelist
  $vsd(w:filelist) appendhistory "Browse For File..."
  # 45851 - don't add previously opened files to the list

  [$vsd(w:filelist) subwidget label] configure -font $mainBoldFont
  [$vsd(w:filelist) subwidget listbox] configure -font $mainFont
  [$vsd(w:filelist) subwidget entry] configure -font $mainFont
  
  set xpad 3
  set ypad 3

  set widgetHelp($w.filelist) {Main Window File ComboBox}

  pack $vsd(w:filelist) -side top -fill x -padx $xpad -pady $ypad
  update ;# so that winfo height will work

  set filelistHeight [winfo height $vsd(w:filelist)]
  if {$filelistHeight == 1} {
    set filelistHeight [winfo reqheight $vsd(w:filelist)]
    if {$filelistHeight == 1} {
      set filelistHeight 24
    }
  }
  incr filelistHeight $ypad
  incr filelistHeight $ypad
  incr filelistHeight $ypad
  set vsd(w:fileselector) ""

  tixPanedWindow $w.mainPane -orient vertical
  set p1 [$w.mainPane add instances -min 115 -size $vsd(instancePaneSize) -expand 3]
  set p2 [$w.mainPane add statistics -min 50 -size $vsd(statisticPaneSize) -expand 1]

  tixScrolledHList $p1.instanceList -options {
    hlist.columns 8
    hlist.header true
    hlist.selectMode extended
    hlist.command instanceCommand
    hlist.BrowseCmd instanceBrowse
  }
  set vsd(instanceList) [$p1.instanceList subwidget hlist]
  
  # Up Arrow bitmap image   
  image create bitmap upbm -data {
    #define up_width 11
    #define up_height 11
    static char up_bits = {
      0x00, 0x00, 0x20, 0x00, 0x70, 0x00, 0xf8, 0x00, 0xfc, 0x01, 0xfe,
      0x03, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00
    }
  }

  # Down arrow bitmap image
  image create bitmap downbm -data {
    #define down_width 11
    #define down_height 11
    static char down_bits = {
      0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0xfe,
      0x03, 0xfc, 0x01, 0xf8, 0x00, 0x70, 0x00, 0x20, 0x00, 0x00, 0x00
    }
  }

  set vsd(buthdr:s) [tixDisplayStyle window -padx 0 -pady 0]
  set i 0
  set instList {StartTime EndTime File Samples ProcessId SessionId Type Name}
  foreach n $instList {
    set buttonName "$vsd(instanceList).hdrBut$n"
    set vsd(ilColumnIdx:$n) $i
    button $buttonName -text $n -relief flat \
      -font $mainBoldFont -takefocus 0 -padx 0 -pady 0 \
      -command [list sortInstances $n $i] -anchor w -compound right
    $vsd(instanceList) header create $i -itemtype window \
      -window $buttonName -style $vsd(buthdr:s)
    bind $buttonName <Button-3> [list button3SortProc %W $i]
    incr i
  }

  if {![info exists vsd(secondarySortKeyIdx)]} {
    # no secondary sort key set by .vsdrc. Use the default instead.
    setDefaultSecondarySortKey
  } else {
    primSetSecondarySortKey $vsd(secondarySortKeyIdx)
  }
  setHdrSortHighlight

  set sfg [$vsd(instanceList) cget -selectforeground]
  set vsd(name:s) [tixItemStyle text -refwindow $vsd(instanceList) \
		     -font $mainBoldFont \
		     -selectforeground $sfg]
  set vsd(time:s) [tixItemStyle text -refwindow $vsd(instanceList) \
		     -font $mainFont -padx 8 \
		     -selectforeground $sfg]
  set vsd(samples0:s) [tixItemStyle text -refwindow $vsd(instanceList) \
			 -font $mainFont \
			 -selectforeground $sfg]
  # 45000 - use LimeGreen instead of Green here
  set vsd(samples1:s) [tixItemStyle text -refwindow $vsd(instanceList) \
			 -fg LimeGreen \
			 -selectforeground $sfg \
			 -font $mainBoldFont]

  pack $p1.instanceList -expand yes -fill both
  set widgetHelp($p1.instanceList) {Main Window Instance Table}

  set tmp [list startSearch $vsd(instanceList) searchHList $filelistHeight]
  bind $vsd(instanceList) <Control-s> $tmp
  bind $vsd(instanceList) <Control-f> $tmp

  bind all <Button-4> \
    {event generate [focus -displayof %W] <MouseWheel> -delta  120}

  bind all <Button-5> \
    {event generate [focus -displayof %W] <MouseWheel> -delta -120}

  set m [menu $w.processmenu -tearoff 0]
  set bindcmd [list doPopup %W $m %X %Y]
  bind $vsd(instanceList) <ButtonRelease-3> $bindcmd
  bind $vsd(instanceList).tixsw:header <ButtonRelease-3> $bindcmd
  set widgetHelp($w.processmenu) {Instance List PopupMenu}

  
  $m add command -label "Search..." -accelerator "Ctrl+s" \
    -command [list startSearch $vsd(instanceList) searchHList $filelistHeight] 

  # set m $w.processmenu
  # $w.processmenu.applyAlias
  global ctrAliasTemplateTypes
  $m add cascade -label "Apply Alias Template for Type" -menu $m.applyAlias -state disabled
  menu $m.applyAlias -tearoff 0
  set vsd(processMenu) $m
  
  foreach aType [array names ctrAliasTemplateTypes] {
    # Create a proc for each alias template type
    namespace eval :: "proc applyAliasTemplateForType$aType \{\} \{ applyAliasTemplateForType $aType\}"
  }

  $m add command -label "Remove Aliases" -state disabled -command removeAliases
  $m add cascade -label "Hide Instances By Type..." -menu $m.hideInst
  menu $m.hideInst -tearoff 0
 $m.hideInst add command -label "Clear All Hidden" \
    -command clearHiddenInstances -state disabled
  $m.hideInst add separator
  $m.hideInst add check -label "AppStat" -variable vsd(hideAppStat) \
    -command updateHiddenInstances
  $m.hideInst add check -label "Gem" -variable vsd(hideGem) \
    -command updateHiddenInstances
  $m.hideInst add check -label "GemThr" -variable vsd(hideGemThr) \
    -command updateHiddenInstances  
  $m.hideInst add check -label "Host System" -variable vsd(hideSystem) \
    -command updateHiddenInstances
  # 49252
  $m.hideInst add check -label "Netldi" -variable vsd(hideNetldi) \
    -command updateHiddenInstances    
  $m.hideInst add check -label "Page Server" -variable vsd(hidePgsvr) \
    -command updateHiddenInstances  
  $m.hideInst add check -label "StatMon" -variable vsd(hideStatMon) \
    -command updateHiddenInstances
  $m.hideInst add check -label "ShrPcMonitor" -variable vsd(hideShrPcMon) \
    -command updateHiddenInstances  
  $m.hideInst add check -label "Stone" -variable vsd(hideStone) \
    -command updateHiddenInstances

  # 47790
  $m add cascade -label "Select Instances By..." -menu $m.selInstBy
  menu $m.selInstBy -tearoff 0
   $m.selInstBy add command -label "All" \
    -command {selectAllInstances}
  $m.selInstBy add command -label "Clear" \
    -command {$vsd(instanceList) selection clear; updateGraphWidgetState}
  $m.selInstBy add command -label "Name..."  \
    -command openSearchNameWindow
  $m.selInstBy add command -label "Process ID..." \
    -command openSearchProcessIdWindow
  $m.selInstBy add command -label "Session ID..." \
    -command openSearchSessionIdWindow  
  $m.selInstBy add command -label "Statistic" \
    -command {selectByStatistic}
  $m.selInstBy add command -label "Type" \
    -command {selectByType}
  
  $m add check -label "Combine" \
    -variable vsd(combineInstances)
  # 46260 - remove Combine Across Files
  $m add check -label "No Flatlines" \
  -variable vsd(noFlatlines) -command changeNoFlatlines
  $m add check -label "No All Zeros" \
    -variable vsd(noAllZeros) -command changeNoAllZeros
  $m add check -label "Single File Mode" \
    -variable vsd(singleFileMode) -command singleFileMode
  $m add check -label "Absolute TimeStamps" \
    -variable vsd(absoluteTSMode)

  
  set vsd(selectedCtr) ""

  tixScrolledHList $p2.counterHList -options {
    hlist.columns 1
    hlist.header false
    hlist.selectMode extended
    hlist.command ctrHlistCommand
    hlist.BrowseCmd ctrHlistBrowse
    hlist.exportSelection 0
    hlist.takeFocus 1
  }
  set vsd(w:counterHList) [$p2.counterHList subwidget hlist]
  set m [menu $w.countermenu -tearoff 0]
  set searchCmd [list startSearch $vsd(w:counterHList) searchCtrHList] 
  $m add command -label "Search For Counter..." -command $searchCmd
  $m add command -label "Show Search Help..." -command showSearchHelp
  bind $vsd(w:counterHList) <ButtonRelease-3> [list doPopup %W $m %X %Y]
  bind $vsd(w:counterHList) <KeyPress> \
    [list HListPrefixSearch $vsd(w:counterHList) %A]
  bind $vsd(w:counterHList) <Control-f> $searchCmd
  bind $vsd(w:counterHList) <Control-s> $searchCmd
  event add <<ClearSearch>> <Up> <Down> <Shift-Up> <Shift-Down> <Left> <Right> \
    <Prior> <Next> <Return> <space> <Double-1> <1> <Shift-1> <Control-1> \
    <FocusOut>
  bind $vsd(w:counterHList) <<ClearSearch>> "+[list HListPrefixClear $vsd(w:counterHList)]"
  set sfg [$vsd(w:counterHList) cget -selectforeground]
  # the font for normal stats
  set vsd(ctr0:s) [tixItemStyle text -refwindow $vsd(w:counterHList) \
		     -font $mainBoldFont \
		     -selectforeground $sfg]
  # the font for zero stats
  set vsd(ctr1:s) [tixItemStyle text -refwindow $vsd(w:counterHList) \
		     -font $mainFont \
		     -selectforeground $sfg]

  pack $p2.counterHList -fill both -expand yes
  set widgetHelp($p2.counterHList) {Main Window Statistic List}

  set vsd(curchart) {}
  tixComboBox $w.chartlist -label "Chart:" -dropdown true \
    -command chooseChart -history true -prunehistory true \
    -editable false -variable vsd(curchart) -options {
      listbox.height 4
    }

  set vsd(w:chartlist) $w.chartlist
  [$vsd(w:chartlist) subwidget label] configure -font $mainBoldFont
  [$vsd(w:chartlist) subwidget listbox] configure -font $mainFont
  [$vsd(w:chartlist) subwidget entry] configure -font $mainFont
  
  set widgetHelp($w.chartlist) {Main Window Chart ComboBox}

  tixButtonBox $w.box -padx $xpad -pady $ypad
  $w.box add additem -text "Add Line" -command {doChart 0} -state disabled
  set vsd(w:addline) [$w.box subwidget additem]
  $w.box add newchart -text "New Chart" -command {doChart 1} -state disabled
  set vsd(w:newchart) [$w.box subwidget newchart]
  set widgetHelp($vsd(w:addline)) {Main Window Add Line Button}
  set widgetHelp($vsd(w:newchart)) {Main Window New Chart Button}

  set sf [frame $w.statusFrame -relief raised -bd 1]
  set vsd(status) ""
  label $sf.status -relief sunken -bd 1 -textvariable vsd(status) -padx 2
  pack $sf.status -fill x
  pack $sf -fill x -side bottom -padx $xpad -pady $ypad

  pack $w.box -fill x -side bottom -padx $xpad -pady $ypad

  pack $w.chartlist -side bottom -padx $xpad -pady $ypad -fill x

  pack $w.mainPane -side top -expand yes -fill both -padx $xpad -pady $ypad

  set widgetHelp($w.box) {Main Window Status Display}

  set vsd(balloon) [tixBalloon .vsd_balloon -statusbar $sf.status -state status]
  $vsd(balloon) bind $vsd(w:filelist) -msg "Show/Select/Load current statmonitor file"
  foreach n $instList {
    $vsd(balloon) bind "$vsd(instanceList).hdrBut$n" -msg "Click to sort by \"$n\""
  }
  $vsd(balloon) bind $vsd(w:chartlist) -msg "Show/Select/Name current chart"
  $vsd(balloon) bind $vsd(w:addline) -msg "Add selected stats to current chart"
  $vsd(balloon) bind $vsd(w:newchart) -msg "Creates a new chart window and add selected stats to it"

  wm protocol $w WM_DELETE_WINDOW ExitVSD

  patchTix

  createSearch
  createTemplateName
  createHelpSearch
  createAboutHelp
  #UsageVSD $w
  doCtrInfo

  #    traceAPICalls
  set color -1
  set appendIt 0
  set autoUpdate 0
  set autoAppend 0
  global argv argc
  set i 0
  set argWasDir 0
  while { $i < $argc } {
    set f [lindex $argv $i]
    switch -exact -- $f {
      "-a" {set appendIt 1}
      "-A" {
	if {! $isWindows} {
	  set appendIt 1
	  set autoUpdate 1
	  set autoAppend 1
	}
      }
      "-b" {incr i ; set color [lindex $argv $i]}
      "-u" {set autoUpdate 1}
      default {
	if {$appendIt} {
	  if {$vsd(statsrc) != {}} {
	    set vsd(appendMode) 1
	  } else {
	    set vsd(appendMode) 0
	  }
	}
	# 47618
	if {[file isdirectory $f] && [file exists $f]} {
	  if {$argWasDir == 1} {
	    showError "Only one directory argument may be specified"
	    exit
	  }
	  cd $f
	  set vsd(cwd)
	  set vsd(lastdatadir) $f
	  set argWasDir 1
	} else {
	  # handle wildcards
	  if {[string first * $f] != -1 || \
		[string first ? $f] != -1} {
	    # substitute forward slash for backslash
	    # sort and load in order of oldest to newest
	    set files [glob -nocomplain [string map {\\ /} $f]]
            set fileAndMTime [lmap name $files {list $name [file mtime $name]}]
	    set sortedFiles [lmap item [lsort -integer -index 1 $fileAndMTime] {lindex $item 0}]
	    foreach fn $sortedFiles {
	      set vsd(appendMode) $appendIt
	      setDataFile $fn
	    }
	  } else {
	    set vsd(appendMode) $appendIt
	    setDataFile $f
	  }
	}
      }
    }
    incr i
  }
  set vsd(appendMode) 0

  # 45211 - Setup autoupdate last, after a stats file has hopefully been specified on the command line.
  #         Otherwise the autoUpdate proc does nothing.
  set vsd(autoUpdate) $autoUpdate
  if {! $isWindows} {
    set vsd(autoAppend) $autoAppend
  }
  if {$autoUpdate} {
    foreach df [allDataFileIds] {
      set vsd(appendMode) $appendIt
      autoUpdateMode $df
    }
  }

  if { $color != -1 } {
    setMasterBackgroundColor $color
  } else {
    if [info exists env(VSD_MASTER_BG_COLOR)] {
      set color $env(VSD_MASTER_BG_COLOR)
      setMasterBackgroundColor $color
    } 
  }
  if { $argWasDir == 1} {
    browseDataFile
  }
}

proc loadStatisticDefinitions {} {
  global statDefinitions vsd
  foreach s [array names statDefinitions] {
    set statAttributes $statDefinitions($s)
    set type [lindex $statAttributes 0]
    set level [lindex $statAttributes 1]
    set filter {}
    if [info exists vsd(filter:$s)] {
      set filter $vsd(filter:$s)
    }
    if [info exists vsd(level:$s)] {
      if {[string equal $level $vsd(level:$s)]} {
	unset vsd(level:$s)
      } else {
	set level $vsd(level:$s)
      }
    }
    sl_stat -info $s [list $type $level]
  }
  # convert old vsd(ctr:*) definitions
  foreach ctr [array names vsd ctr:*] {
    set statname [string range $ctr 4 end]
    set ctrlist $vsd($ctr)
    set level [lindex $ctrlist 1]
    set filter [lindex $ctrlist 3]
    if {$level != {}} {
      setStatLevel $statname $level
    }
    if {$filter != {}} {
      setStatFilter $statname $filter
    }
    unset vsd($ctr)
  }
  processUserStatDefs
  # 45220 - generateAllStatHelp deleted
}

proc processUserStatDefs {} {
  global statdef statDefinitions statDocs vsd
  foreach name [array names statdef] {
    set attributes $statdef($name)
    if {[llength $attributes] != 6} {
      set msg "The statistic definition for $name does not have six attributes. "
      set msg [string cat $msg "Check $vsd(VSDHOME)/$vsd(vsdConfigFn) for a line that sets "]
      set msg [string cat $msg "statdef($name) and correct it. To " ]
      set msg [string cat $msg "learn more about statistic definitions see the "]
      set msg [string cat $msg "vsd help section entitled \"Statistic Definitions\"."]
      fatalError $msg
    }
    set kind [lindex $attributes 0]
    set level [lindex $attributes 1]
    set units [lindex $attributes 2]
    set isOSStat [lindex $attributes 3]
    set filter [lindex $attributes 4]
    set docs [lindex $attributes 5]
    if {$kind != {}} {
      if [catch {sl_stat -info $name [list $kind]} err] {
	set msg "The statistic definition for $name does not have a valid value for "
	set msg [string cat $msg "the kind attribute. The illegal value found is: "]
	set msg [string cat $msg "\"$kind\". The problem is: \"$err\". Check "]
	set msg [string cat $msg "$vsd(VSDHOME)/$vsd(vsdConfigFn) for a line that sets "]
	set msg [string cat $msg "statdef($name) and correct it. To learn more "]
	set msg [string cat $msg "about statistic definitions see the vsd help "]
	set msg [string cat $msg "section entitled \"Statistic Definitions\"."]
	fatalError $msg
      }
    } else {
      set kind "uvalue"
    }
    if {$level != {}} {
      if [catch {sl_stat -info $name [list {} $level]} err] {
	set msg "The statistic definition for $name does not have a valid value "
	set msg [string cat $msg "for the level attribute. The illegal value "]
	set msg [string cat $msg "found is: \"$level\". The problem is: \"$err\". "]
	set msg [string cat $msg "Check $vsd(VSDHOME)/$vsd(vsdConfigFn) for a line that "]
	set msg [string cat $msg "sets statdef($name) and correct it. To learn more "]
	set msg [string cat $msg "about statistic definitions see the vsd help" ]
        set msg [string cat $msg "section entitled \"Statistic Definitions\"."]
	fatalError $msg
      }
    } else {
      set level "common"
    }
    if {$filter != {}} {
      if [catch {sl_stat -info $name [list {} {} $filter]} err] {
	set msg "The statistic definition for $name does not have a valid value "
	set msg [string cat "for the filter attribute. The illegal value found "]
	set msg [string cat "is: \"$filter\". The problem is: \"$err\". Check "]
	set msg [string cat "$vsd(VSDHOME)/$vsd(vsdConfigFn) for a line that sets "]
	set msg [string cat "statdef($name) and correct it. To learn more about "]
	set msg [string cat "statistic definitions see the vsd help section "]
	set msg [string cat "entitled \"Statistic Definitions\"."]
	fatalError $msg
      }
    } else {
      set filter "default"
    }
    if {$isOSStat != {}} {
      if {![string is boolean $isOSStat]} {
	set msg "The statistic definition for $name does not have a valid "
	set msg [string cat "boolean value for the isOSStat attribute. The "]
	set msg [string cat "illegal value found is: \"$isOSStat\". Check "]
	set msg [string cat "$vsd(VSDHOME)/$vsd(vsdConfigFn) for a line that sets "]
	set msg [string cat "statdef($name) and correct it. To learn more "]
	set msg [string cat "about statistic definitions see the vsd help "]
	set msg [string cat "section entitled \"Statistic Definitions\"."]
	fatalError $msg
      }
    } else {
      set isOSStat false
    }
    if {$units == {}} {
      set units "none"
    }
    sl_stat -info $name {} ;# make sure it gets pushed to lower level
    set statDefinitions($name) [list $kind $level $units $isOSStat]
    if {$docs != {}} {
      set statDocs($name) $docs
    }
  }
}

proc debugvar {varname} {
  upvar $varname var
  puts "$varname is: '$var'"
  return
}

# initStatHelp deleted
# generateStatHelp deleted

proc loadStatisticAliases {} {
  sl_stat -alias "numberattached" "AttachedCount"
  sl_stat -alias "numberoffreeframes" "FreeFrameCount"
  sl_stat -alias "numberofcommitrecords" "CommitRecordCount"
  sl_stat -alias "asynchwritesinprogress" "AsyncWritesInProgress"
  sl_stat -alias "aynschwritesiocount" "AsyncWritesCount"
  sl_stat -alias "messagestostn" "MessagesToStone"
  sl_stat -alias "numincommitqueue" "CommitQueueSize"
  sl_stat -alias "numinlockreqqueue" "LockReqQueueSize"
  sl_stat -alias "numinnotifyqueue" "NotifyQueueSize"
  sl_stat -alias "numinloginwaitqueue" "LoginQueueSize"
  sl_stat -alias "numinrunqueue" "RunQueueSize"
  sl_stat -alias "numinsymbolcreatequeue" "SymbolCreationQueueSize"
  sl_stat -alias "numberofscavenges" "ScavengeCount"
  sl_stat -alias "numberofdeadobjs" "DeadObjCount"
  sl_stat -alias "numberofmakeroominoldspace" "MakeRoomInOldSpaceCount"
  sl_stat -alias "numberofgcnotconnected" "GcNotConnectedCount"
  sl_stat -alias "numberofgcnotconnecteddead" "GcNotConnectedDeadCount"
  sl_stat -alias "norollbacksize" "NoRollbackSetSize"
  sl_stat -alias "notconnectedobjssize" "NotConnectedObjsSetSize"
  sl_stat -alias "numberofsigabort" "SigAbortCount"
  sl_stat -alias "numberofsiglostot" "SigLostOtCount"
  sl_stat -alias "numberofreads" "ReadCount"
  sl_stat -alias "numberofwrites" "WriteCount"
  sl_stat -alias "numberofseeks" "SeekCount"
  sl_stat -alias "numberoftransfers" "TransferCount"
  sl_stat -alias "numswaps" "SwapCount"
  sl_stat -alias "epochgccount" "EpochGcCount"
  sl_stat -alias "localdirtypagecount" "LocalDirtyPageCount"
  sl_stat -alias "globaldirtypagecount" "GlobalDirtyPageCount"
  sl_stat -alias "pagereads" "PageReads"
  sl_stat -alias "pagewrites" "PageWrites"
  sl_stat -alias "deadobjscount" "DeadObjsCount"
  sl_stat -alias "reclaimcount" "ReclaimCount"
  sl_stat -alias "reclaimedpagescount" "ReclaimedPagesCount"
  sl_stat -alias "checkpointcount" "CheckpointCount"
  sl_stat -alias "pagesneedreclaimsize" "PagesNeedReclaimSize"
  sl_stat -alias "possdeadsize" "PossibleDeadSize"
  sl_stat -alias "deadnotreclaimedsize" "DeadNotReclaimedSize"
  sl_stat -alias "pgsvraiodirtycount" "AioDirtyCount"
  sl_stat -alias "pgsvraiockptcount" "AioCkptCount"
  sl_stat -alias "numberofcommits" "CommitCount"
  sl_stat -alias "numberoffailedcommits" "FailedCommitCount"
  sl_stat -alias "numberofaborts" "AbortCount"
  # The following old names only existed for a 5.0 alpha
  sl_stat -alias "vccachenumscavenges" "VcCacheScavengesCount"
  sl_stat -alias "vccachesizebytes" "VcCacheSizeBytes"
  sl_stat -alias "codecachesizebytes" "CodeCacheSizeKBytes"
  sl_stat -alias "codecachenumentries" "CodeCacheEntries"
  sl_stat -alias "codecachenumstaleentries" "CodeCacheStaleEntries"
  sl_stat -alias "codecachenumscavenges" "CodeCacheScavengesCount"
  sl_stat -alias "datepageswrittenbygem" "DataPagesWrittenByGem"
  sl_stat -alias "datepageswrittenbystone" "DataPagesWrittenByStone"
  set i 0
  while {$i < 10} {
    sl_stat -alias "sessionstat0$i" "SessionStat0$i"
    sl_stat -alias "sessionStat0$i" "SessionStat0$i"
    sl_stat -alias "sessionStat$i" "SessionStat0$i"
    incr i
  }

  set i 10
  while {$i < 48} {
    sl_stat -alias "sessionstat$i" "SessionStat$i"
    sl_stat -alias "sessionStat$i" "SessionStat$i"
    incr i
  }
  # Names changed in gsj fiji (4.0)
  sl_stat -alias "sweepcount" "RGCSweepCount"
  sl_stat -alias "possibledeadwsunionsize" "RGCPossibleDeadWsUnionSize"
  sl_stat -alias "pagesneedreclaiming" "RGCPagesNeedReclaiming"
  sl_stat -alias "reclaimnewdatapagescount" "RGCReclaimNewDataPagesCount"
  # Names changed in gsj guatamala (4.6)
  sl_stat -alias "jdbcconnectionsreturned" "JDBCConnectionsAcquired"
  sl_stat -alias "webrequestcount" "WebRequestsReceived"
}

# generateTypeHelp deleted
# 45220 - generateAllStatHelp deleted

proc createAboutHelp {{force 0}} {
  global vsd  env tix_version isWindows vsdhelp
  set topic {About VSD}
  if {$force != 0} {
    unset vsdhelp($topic)
  }
  if [info exists vsdhelp($topic)] {
    return
  }

  # Get the executable architecture (32 or 64 bit)
  if [catch {sl_arch} resultArch] {
    set msg "Could not get architecture from sl_arch because: "
    append msg $resultArch
    showError $msg
    return
  }

  set vsdhelp($topic) \
    "<b>VSD:</b>      Visual Statistics Display
<b>Versions:</b>  vsd=$vsd(version), statistics=$vsd(stats_version)
<b>Build ID:</b>  $vsd(git_sha)
<b>Build URL:</b> $vsd(git_url)
<b>Build Date:</b> $vsd(build_date)
<b>Build Kind:</b> $vsd(buildKind)
<b>Architecture:</b> $resultArch bit
<b>Creator:</b>    Darrel Schneider
<b>Maintainer:</b>  Norm Green
<b>Support:</b>  send mail to $vsd(maintainer)
<b>Home Page:</b> http://www.gemtalksystems.com/vsd
<b>Purpose:</b>   To display GemStone statistics. The statistics must come from a \[statmonitor] output file or from GBS.
<b>Credits:</b>  Tcl[info patchlevel],  Tk[info patchlevel],  Tix$tix_version, Rbc 0.1, zlib 1.2.12, lz4 1.9.4
<b>Platforms:</b>    Linux x86_64, Oracle Solaris x86_64, IBM AIX, Apple Macintosh and Microsoft Windows.
\[<b>Files:</b> > VSD Files]
   * <tt>[info nameofexecutable]</tt> contains VSD's binary virtual machine.
   * <tt>[getScriptName]</tt> contains VSD's \[Tcl > Tcl Language] source code implementation."

# 48317 - list files actually read by VSD, in order of reading
  foreach fn $vsd(sourcedFiles) {
    set vsdhelp($topic) [string cat $vsdhelp($topic) "\n   * <tt><b>$fn</b></tt>"]
  }
set vsdhelp($topic) [string cat $vsdhelp($topic)   \
      "\nSee https://gemtalksystems.com/products/vsd/history/ to learn about what's new in this version.\n"]
}

proc buildVsdFilePathFor {name} {
  # Build a full file name with path for a file
  # located in the same directory as vsd.tcl.

  # expand the filename of this script
  set fullName [file normalize $::argv0]
  # get the directory name
  set fullName [file dirname $fullName]
  # Build the full file name and path
  set fullName [file join $fullName $name]

  return "$fullName"
}

# import stat definitions
sourceVsdBinFile "vsd.stats.tcl"

# vsdhelp and stathelp moved to vsd.help.tcl
sourceVsdBinFile "vsd.help.tcl"

global vsd
if [catch {sl_isSlow} bool] {
  set msg "Could not determine slow or fast from  sl_isSlow because: "
  append msg $bool
  showError $msg
} else {
  if {[string equal $bool "true"]} {
    set vsd(isSlow) 1
    set vsd(buildKind) slow
  } else {
    set vsd(isSlow) 0
    set vsd(buildKind) fast
  }
}

# proc auditStats in vsd.stats.tcl
if {$vsd(isSlow)} {
  auditStats
}

# Setup indexed stats that use the same description
# moved to vsd.stats.tcl

#!Vector Operations
#<em>not yet documented</em>
#!Instance Operations
#<em>not yet documented</em>
  
############################# Start the program #########################
if {![info exists vsdIsRunning]} {
  global vsd
  set vsdIsRunning 1
  tix initstyle
  wm withdraw .
  set w .vsd
  toplevel $w
  set vsd(version) "5.6.3"
  set vsd(git_sha)  "befef0e6cd7f20e24240f34746456339c0b66cfa"
  set vsd(git_url) "git@git.gemtalksystems.com:vsd.git"
  set vsd(build_date) "Thu Jul 10 14:56:51 PDT 2025"
  set vsd(short_sha) [string range $vsd(git_sha) 0 7]
  set vsd(sourcedFiles) {}

  # Request 45849 - put version in window title
  wm title $w "VSD $vsd(version) (build $vsd(short_sha))"
  # Request 44952 - Make F12 a hot-key to find and show the main window.
  bind all <F12> {
    global vsd
    set w $vsd(w)
    focus $w
    raise $w
    wm deiconify $w
  }

  # Bug 43692 - Close current sub-window with Alt-F4 key sequence
  if {$isWindows} {
    bind all <Alt-F4> { 
      global vsd
      set xxx [winfo toplevel %W]
      if { $xxx == $vsd(w) } {
	# Alt-F4 on main window: call exit routine
	ExitVSD 
      } else {
	# Alt-F4 NOT on main window: destroy the window
	destroy $xxx 
      }     
    }
  }
  RunVSD $w
}
