#!/usr/bin/sh
# \
exec oagwish "$0" "$@"

# $Log: not supported by cvs2svn $
# Revision 1.24  2004/08/23 17:01:34  borland
# Moved the command button to the left side of the output file entry box.
#
# Revision 1.23  2004/08/11 21:52:14  borland
# Removed file select button from output file widget, since the command button
# has this feature.
#
# Revision 1.22  2004/07/30 18:54:58  borland
# Added file selection and command buttons to file entry boxes.
#
# Revision 1.21  2004/01/03 17:51:42  borland
# Fixed bug in previous change.
#
# Revision 1.20  2004/01/03 17:35:09  borland
# Added commandline option -fileName for loading configuration data.
#
# Revision 1.19  2002/01/04 14:19:49  borland
# Replaced /home/helios4 with /home/helios .
#
# Revision 1.18  2000/06/14 14:27:03  soliday
# Now longer runs with oagwishBeta
#
# Revision 1.17  1999/11/03 21:02:05  borland
# Now uses CA to search for PVs rather than the ioc database files.
# Also gives the user the option to go ahead in spite of PVs not
# found.
#
# Revision 1.16  1999/09/28 16:28:17  borland
# Per D. Blachowicz:
# Allows specification of readback names for PVs.
# Also,
# 1. Procedure "SaveMonitorList" used "return" command. Because "return"
#     command works only for one level the application was going back to
#     "proc RunMonitor" and program was running displaying a system error.
#     I fixed that.
#  2. A name for PV entered by the user was never checked for its existence
#     in IOC database. It caused strange behavior of the window running a
#     quickMonitor subprocess.
#     Now, each PV name is checked against IOC database prior to any process
#     in the application. If PV name is not found the message for the user
#     is displayed and the run is stopped.
#  3. Old version of the quickMonitor used direct input into the file
#     using "open", "puts", "close" commands.
#     I exchange that with the dataArray structure and "sdds save" function.
#  4. In old version each next "Run" overwrote output log file used by the
#     previous one.
#     In my version each "Run" has to use separate output files. When the user
#     forget to do it the message will be displayed and the user will be allowed
#     to change output file.
#
# Revision 1.15  1997/07/25 21:40:51  borland
# Improve save/load config features.
#
# Revision 1.14  1996/09/23 19:34:18  borland
# Now somewhat smarter about user-supplied input file names.  Won't try
# to overwrite user-entered input file, but will just use it.
#
# Revision 1.13  1996/09/23 18:07:38  borland
# Now somewhat smarter about user-supplied input file names.  Won't try
# to overwrite user-entered input file, but will just use it.
#
# Revision 1.12  1996/09/19 21:51:34  borland
# Changed from using "exec wish" to "exec oagwish".
#
# Revision 1.11  1996/06/05 21:00:21  borland
# Added button to bring up name capture utility.
#
# Revision 1.10  1996/03/04  20:14:21  saunders
# Added revision/author to Version menu.
#
# Revision 1.9  1996/02/28 22:28:17  borland
# Added -getUnits option to sddsmonitor command.  sddsplot command now uses
# Time variable and -ticks=xtime option.
#
# Revision 1.8  1996/02/27  23:44:32  borland
# Redid indexing of PVnames for monitor list, to get rid of problems
# with having an empty PVname at the top of the list when "LOAD INPUT..."
# was used.  Also improved the context help for a number of widgets.
#
# Revision 1.7  1996/01/18  00:55:45  saunders
# Changed PV entry frame to use APSScroll. Now allows arbitrary number
# of entries.
#
# Revision 1.6  1996/01/17  16:59:34  saunders
# Added /usr/local/oag/lib_patch to front to auto_path.
#
# Revision 1.5  1996/01/16  21:12:14  saunders
# Fixed log entry problem, so now comments and version numbers
# appear properly. Also, renames X.tcl files to just X.
#
# Revision 1.4  1996/01/12 22:36:48  saunders
# Removing dependence on ~borland/tcl/rel auto_path entry. There are still
# references to personal directories. Also fixed minor bugs as encountered.
#
# Revision 1.3  1995/12/15  20:12:37  saunders
# Added proper installation Makefile and Log
#

set auto_path [linsert $auto_path 0  /usr/local/oag/apps/lib/$env(HOST_ARCH)]
set auto_path [linsert $auto_path 0 /usr/local/oag/lib_patch/$env(HOST_ARCH)]
APSDebugPath

set CVSRevisionAuthor "\$Revision: 1.25 $ \$Author: borland $"

APSApplication . -name quickMonitor -version $CVSRevisionAuthor \
  -overview {This is the quickMonitor utility.  It provides a simple interface to the sddsmonitor program, which allows logging EPICS data to an SDDS file.  The data can be plotted after it is collected, or during collection.  You can also manipulate and display the data offline using the SDDS toolkit programs.}

set monitors 0
set monitorLines 0

set mainStatus "Press ADD to enter more PV names for monitoring"

proc SetMainStatus {text} {
    global mainStatus
    set mainStatus $text
    update
}

APSScrolledStatus .status -parent .userFrame -textVariable mainStatus -width 90

frame .userFrame.lines  -bd 4  -relief raised
pack .userFrame.lines -side top -fill x

set pvScroll [APSScroll .sw -parent .userFrame.lines -name "PV List"]
set pvScrollFrame .userFrame.lines.sw

APSButton .pvScrollAdd -parent .userFrame.lines -text ADD -command \
  "MakeNewMonitorLine $pvScroll" \
  -contextHelp "Press to add PV name entry slots."
 
frame .userFrame.settings -bd 4 -relief raised
pack .userFrame.settings -side top -fill x
set Interval 1
set Steps 100
set outputFile ""
APSLabeledEntry .settings.interval -parent .userFrame -label "Interval (s): " \
  -textVariable Interval -width 80 \
  -contextHelp "Enter the time between samples in seconds in this field."
APSLabeledEntry .settings.steps -parent .userFrame -label "Steps: " \
  -textVariable Steps -width 80 \
  -contextHelp "Enter the number of steps (or samples) in this field.  The total time to run is approximately the \
number of steps times the interval."

APSLabeledEntry .settings.output -parent .userFrame -label "Output file: " \
  -textVariable outputFile -width 80 -commandButton 1 -buttonsOnLeft 1 \
  -contextHelp "Enter a name for the output file to hold the data.  This file is not deleted by quickMonitor, so you can use the data for other purposes.  For example, more complicated plots can be done with quickSDDSplot."


frame .userFrame.ops -bd 4 -relief raised
pack .userFrame.ops -side top -fill x
APSButton .ops.run -parent .userFrame -text RUN -command RunMonitor \
  -contextHelp "Launches a monitoring subprocess to collect the data."
APSButton .ops.new -parent .userFrame -text "NEW..." -command "exec quickMonitor &" \
  -contextHelp "Launches a new instance of the quickMonitor screen"
APSButton .ops.clear -parent .userFrame -text "CLEAR ALL" -command "ClearAllSettings $pvScroll" \
  -contextHelp "Clears all of the settings for quickMonitor, including PV names and filenames."
APSButton .ops.save -parent .userFrame -text "SAVE CONFIG..." -command SaveMonitorList \
  -contextHelp "Allows you to save the configuration, which includes the list of PVs, the interval, and the number of steps"
APSButton .ops.load -parent .userFrame -text "LOAD CONFIG..." -command LoadExistingInput \
  -contextHelp "Loads PV names and run parameters from a previously saved configuration file." 
APSButton .ops.namecap -parent .userFrame -text "NAME CAPTURE..." -command \
  "catch {exec namecapture &}" -contextHelp \
  "Launches an instance of the name capture utility, which is useful in making lists of process variable names for use with quickMonitor.  Use the center mouse button on any MEDM screen to get the process variable name of whatever you point at.  Hold the mouse button down and drag the name into the name capture window.  You can drop the name from there into a quickMonitor entry box, or have name capture put names in a file that you can load into quickMonitor using LOAD INPUT..."

frame .userFrame.review -bd 4 -relief raised
pack .userFrame.review -side top -fill x

set fileSelListDir $env(HOME)
proc LoadExistingInput {args} {
    set filename ""
    if {[APSStrictParseArguments {filename}]} {
        return -code error "LoadExistingInput: invalid arguments"
    }
    global pvScroll fileSelListDir
    global monitorLines PVname Steps Interval
    if ![string length $filename] {
        set filename [APSFileSelectDialog .chooseInputFile \
                        -listDir $fileSelListDir]
        if {$filename==""} { 
            return 
        }
    }
    set fileSelListDir [file dirname $filename]
    if {![APSCheckSDDSFile -fileName $filename]} {
        SetMainStatus "$filename is not an SDDS file."
        return
    }
    set names [APSGetSDDSNames -fileName $filename -class column]
    if {[llength $names]<1} {
        SetMainStatus "$filename is not a valid sddsmonitor input file."
        return 
    }
    if {[lsearch -exact $names "ControlName"]!=-1} {
        set columnName ControlName
    } elseif {[lsearch -exact $names "Device"]!=-1} {
        set columnName Device
    } else {
        SetMainStatus "$filename is not a valid sddsmonitor input file."
        return
    }
    if {[lsearch -exact $names "ReadbackName"]!=-1} {
         set columnNameRB ReadbackName
    } else {
         set columnNameRB $columnName
    }
    
    set names [APSGetSDDSColumn -fileName $filename -column $columnName -page 0]
    set namesRB [APSGetSDDSColumn -fileName $filename -column $columnNameRB -page 0]
    
    if {[llength $names]==0} {
        SetMainStatus "$filename has no process variable names."
        return
    }
    if {$monitorLines==1 && [string length $PVname(0.PV)]==0} {
        incr monitorLines -1
    }
    foreach namePV $names nameRB $namesRB {
        MakeNewMonitorLine $pvScroll -pvNamePV $namePV -pvNameRB $nameRB
        update
    }
    if ![catch {APSGetSDDSParameter -fileName $filename -parameter Interval -page 1} result] {
        set Interval $result
    }
    if ![catch {APSGetSDDSParameter -fileName $filename -parameter Steps -page 1} result] {
        set Steps $result
    }
    SetMainStatus "Loaded configuration from file $filename"
}


proc ClearAllSettings {widget0} {
    global monitorLines PVname outputFile logplot
    if {![APSYesNoPopUp "Really? Clear all settings?"]} { return }
    for {set i 0} {$i<$monitorLines} {incr i} {
        if {[winfo exists $widget0.m$i]} { destroy $widget0.m$i}
        set PVname($i.PV) ""
        set PVname($i.RB) ""
    }
    set monitorLines 0
    set outputFile ""
    set logplot 0
    SetMainStatus "Press ADD to enter PV names for monitoring"
}

proc MakeNewMonitorLine {widget0 args} {
    global pvScrollFrame
    global monitorLines PVname
    set pvNamePV ""
    set pvNameRB ""
    APSParseArguments {pvNamePV pvNameRB}

    set widget $widget0.m$monitorLines
    if ![winfo exists $widget] {
        frame $widget -bd 1
        pack $widget -fill x
        frame $widget.op 
        frame $widget.data
        pack $widget.op $widget.data -side left

        APSButton .delete -parent $widget.op -text "DELETE" \
          -command "DeleteMonitorLine $widget0 $monitorLines" \
          -contextHelp "Press to delete the corresponding PV name entry line."
        $widget.op.delete configure -bd 0
        $widget.op.delete.button configure -font -adobe-courier-medium-r-normal-*-12-*-*-*-*-*-*-*
        APSButton .clear -parent $widget.op -text "CLEAR" \
          -command "ClearMonitorLine $widget0 $monitorLines" \
          -contextHelp "Press to clear the corresponding PV name entry line."
        $widget.op.clear configure -bd 0
        $widget.op.clear.button configure -font -adobe-courier-medium-r-normal-*-12-*-*-*-*-*-*-*
        APSLabeledEntry .x -parent $widget.data -label " PV name:" \
          -textVariable PVname($monitorLines.PV) -width 30 -packOption "-side left" \
          -contextHelp "Enter the name of a process variable (PV) to be monitored in this field."
        APSLabeledEntry .y -parent $widget.data -label " PV readback:" \
          -textVariable PVname($monitorLines.RB) -width 30 -packOption "-side right" \
          -contextHelp "Enter the name of the column in the output file for this PV.  Defaults to the PV name."
    }

    set PVname($monitorLines.PV) $pvNamePV
    if [string length $pvNameRB] {
        set PVname($monitorLines.RB) $pvNameRB
    } else { 
        set PVname($monitorLines.RB) $pvNamePV
    }
    incr monitorLines
    #tkwait visibility $widget.data.x
    APSScrollAdjust $pvScrollFrame -numVisible 10
    #tkwait visibility $widget.data.x
}

proc DeleteMonitorLine {widget0 number} {
    global monitorLines PVname 
    destroy $widget0.m$number
    set PVname($number.PV) ""
    set PVname($number.RB) ""
}

proc ClearMonitorLine {widget0 number} {
    global monitorLines PVname 
    set PVname($number.PV) ""
    set PVname($number.RB) ""
}

set outputListLength 0
proc SaveMonitorList {args} {
    set filename ""
    APSStrictParseArguments {filename}
    global monitorLines PVname 
    global Interval Steps 
    global outputList outputListLength

    set count 0
    for {set i 0} {$i<$monitorLines} {incr i} {
        if {[string length $PVname($i.PV)]} {
            incr count
        }
    }
    if {!$count} {
        return -code error "No PV names given."	
    }
  
    set userMode 0
    while {![string length $filename]} {
        set userMode 1
        set filename [APSInfoDialog [APSUniqueName .] -name FileDialog \
                        -width 60 \
                        -infoMessage "Supply the name of a file to which to save the data." ]
        set filename [string trim $filename]
        if ![string length $filename] {
            return -code error "No filename given."
        }
        if {[file exists $filename] && \
              ![APSMultipleChoice [APSUniqueName .] -question "$filename exists.  Overwrite it?" \
                  -labelList {Yes No} -returnList {1 0}]} {
            set filename ""
        }
    }


    set tmpfile /tmp/[APSTmpString]
    set dataArray(ParameterNames) "Interval Steps"
    set dataArray(ParameterInfo.Interval) "type SDDS_DOUBLE units s"
    set dataArray(ParameterInfo.Steps) "type SDDS_LONG"
    set dataArray(Parameter.Interval) $Interval
    set dataArray(Parameter.Steps) $Steps
    set dataArray(ColumnNames) "ControlName ReadbackName"
    set dataArray(ColumnInfo.ControlName) "type SDDS_STRING"
    set dataArray(ColumnInfo.ReadbackName) "type SDDS_STRING"
    set origNameList ""
    set readbackNameList ""

    global plotList
    set plotList ""
    for {set i 0} {$i<$monitorLines} {incr i} {
        if {[string length $PVname($i.PV)]} {
	    set origName [string trim $PVname($i.PV)]
	    if {[string length $PVname($i.RB)]} {
		 set readbackName [string trim $PVname($i.RB)]
	    } else {
                 set readbackName $origName
	    }
	    foreach var [list origName readbackName] {
               if {[llength [set $var]]==0} {
 		    return -code error "No name specified in PVname [set $var]."
	       }
               if {[llength [set $var]]>1} {
 		    return -code error "White space detected in PVname [set $var]."
	       }
	    }

            lappend origNameList $origName
            lappend readbackNameList $readbackName
            if {[string length $plotList]==0} {
                set plotList $readbackName
            } else {
                set plotList "$plotList,$readbackName"
            }
        }
    }
    SetMainStatus "Searching..."
    if [catch {SearchPVName -useCA 1 -pvnameList $origNameList} result] {
        if ![APSQueryToProceed \
               -message "$result\nThis may indicate an IOC malfunction, an input error, or a database problem."] {
                   return -code error "$result"
               }
    }
    set dataArray(Column.ControlName) [list $origNameList]
    set dataArray(Column.ReadbackName) [list $readbackNameList]

    if [catch {sdds save $tmpfile dataArray} result] {
        return -code error "Unable to save $tmpfile: $result"
    }    
    if [catch {exec sddssort $tmpfile $filename -column=ControlName -unique} result] {
        return -code error "Problem in sddssort: $result"
    }
    file delete -force $tmpfile
    if $userMode {
        SetMainStatus "Configuration saved to $filename"
    }
}

proc SearchPVName {args} {
     set pvnameList ""
     set useCA 0
     if [APSStrictParseArguments {pvnameList useCA}] {
         return -code error "SearchPVName: bad arguments"
     }
     if $useCA {
         if [catch {exec cavget -list=[join $pvnameList ,]} dataList] {
             return -code error "SearchPVName: $result"
         }
         set pvNotFoundList ""
         foreach pv $pvnameList value $dataList {
             if [string compare $value ?]==0 {
                 lappend pvNotFoundList $pv
             }
         }
         if [llength $pvNotFoundList] {
             return -code error "PVs not found: [join $pvNotFoundList]"
         }
     } else {
         set pvdata_path "/home/helios/iocinfo/pvdata"
         set origDir [pwd]
         if {[file isdirectory $pvdata_path] != 1} {
             return -code error "Cannot open or locate directory $pvdata_path"
         }

         cd $pvdata_path
         set pvdata_file_list [glob -nocomplain *]
         set findFlag 0
         foreach variable $pvnameList {
             set findFlag 0
             set variable [string trim $variable]
             set cmd "grep \"$variable\" $pvdata_file_list /dev/null"
             set catchlist [list open |$cmd r]
             catch $catchlist fp
             while {[gets $fp line] >= 1} {
                 set breakpoint [string first : $line]
                 set ematch [string range $line [expr $breakpoint+1] end]
                 
                 if {[string compare $ematch $variable] == 0} {
                     set findFlag 1
                     break
                 }
             }
             if !$findFlag {
                 cd $origDir
                 return -code error "Unable to find PV $variable."
             }
         }
         cd $origDir
     }
}

proc RunMonitor {} {
    global monitorLines outputFile
    global Interval Steps logplot
    global outputList plotComList plotLogScale outputListLength
    global plotList

    if {[string length $outputFile]==0} {
        SetMainStatus "Supply an output filename."
        bell
        return
    }
    if {[string first " " $outputFile]!=-1} {
        SetMainStatus "Spaces are not allowed in the filename"
        bell
        return
    }
    if [array size outputList] {
        for {set index 0} {$index < $outputListLength} {incr index} {
	     if ![string compare $outputList($index) $outputFile] {
		SetMainStatus "Output file name $outputFile\n is already used for run# [expr $index + 1]."
		bell
		return
	     }
	 }
    }
    if {[file exists $outputFile]} {
        bell
        set ok [APSYesNoPopUp "Delete existing $outputFile?"]
        if {!$ok} {
            SetMainStatus "Supply a new output filename"
            return
        }
    }

    set inputFile /tmp/[APSTmpString]
    if [catch {SaveMonitorList -filename $inputFile} result] {
	bell
        APSAlertBox [APSUniqueName .] -errorMessage $result
        SetMainStatus "Searching is stopped."
        return
    }

    set date [exec date]
    SetMainStatus "Running.... (started $date)."
    set outputList($outputListLength) $outputFile
    set plotComList($outputListLength) "sddsplot -ticks=xtime -column=Time,($plotList) -graph=line,vary -legend $outputFile"
    APSExecLog .job$outputListLength -unixCommand "sddsmonitor $inputFile $outputFile \
                -erase -interval=$Interval,s -steps=$Steps -verbose -getunits=force" \
      -contextHelp "This window is running a quickMonitor subprocess."  \
      -callback "AddPlotCommandToFile $outputFile $plotComList($outputListLength)"
    AddReplotButton .job$outputListLength $outputListLength
    incr outputListLength
}


proc AddPlotCommandToFile {outputFile args} {
    set command [APSMakeSafeQualifierString [join $args]]
    set tmpFile [file dirname $outputFile]/[APSTmpString]
    exec sddsprocess $outputFile $tmpFile -nowarning \
      "-print=param,sddsplotCommand01,$command" 
    exec mv $tmpFile $outputFile
}

proc AddReplotButton {parent index} {
    global outputList plotComList plotLogScale plotSeparately plotSameScale
    set w $parent.rprow$index
    pack [frame $w -relief raised -bd 2] -side bottom

    set plotLogScale($index) 0
    set plotSeparately($index) 0
    set plotSameScale($index) 0
    APSButton .plot -parent $w \
      -text "Plot $outputList($index)" \
      -command "ReplotData $index" \
      -contextHelp "This button replots the data in file $outputList($index)"
    checkbutton $w.log -text "Log plot" \
      -variable plotLogScale($index) -relief ridge
    checkbutton $w.separ -text "Separate plots" \
      -variable plotSeparately($index) -relief ridge
    checkbutton $w.same -text "Same scale" \
      -variable plotSameScale($index) -relief ridge
    pack $w.log  $w.separ $w.same -side left
}

proc ReplotData {index} {
    global outputList plotComList plotLogScale plotSeparately plotSameScale
    SetMainStatus "Plotting  $outputList($index)"
    set extraArgs ""
    if $plotSameScale($index) {
        set extraArgs "$extraArgs -samescale=y"
    }
    if $plotSeparately($index) {
        set extraArgs "$extraArgs -separate"
    }
    if {$plotLogScale($index)} {
        eval exec "$plotComList($index)  -mode=y=log \"-ylabel=edit=i/Log\\\$b10\\\$n /\" $extraArgs &"
    } else {
        eval exec "$plotComList($index) $extraArgs &"
    }                
}

MakeNewMonitorLine $pvScroll

if [llength $argv] {
    set args $argv
    set fileName ""
    if {[APSStrictParseArguments {fileName}] || ![string length $fileName]} {
        return -code error {usage: quickMonitor [-fileName <string>]}
    }
    if ![file exists $fileName] {
        return -code error "not found: $fileName"
    }
    LoadExistingInput -filename $fileName
}
