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

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)]
wm withdraw .

set args $argv
set fileName ""
set start 0
set lockdown 0
set repeatCmd 0
set deltaMode 0
set group 0
APSStrictParseArguments {fileName deltaMode start lockdown repeatCmd group}

if {[string length $fileName] && [file exists $fileName]} { 
    set columnList [exec sddsquery -column $fileName]
    if [lsearch -exact $columnList ReferenceControlName]!=-1 {
        set deltaMode 1
    }
}

proc launchRunControlMEDM {} {
    global runControlPV
    if [string length $runControlPV] {
        exec medm -x -attach -macro RCPV=$runControlPV ./sr/psApp/APSRunControlSingle.adl & 
    }
}

proc ScrollAdjust {} {
    global row
    set rows 0
    for {set i 1} {$i <= $row} {incr i} {
        if {[winfo exists .userFrame.scrollWin.frame.canvas.frame.pv${i}]} {
            incr rows
        }
    }
    .userFrame.scrollWin.frame.canvas config -scrollregion "0 0 1000 [expr 22 * ($rows)]"
}

proc LockdownMessage {} {
    APSSetVarAndUpdate status "Action not possible in lockdown mode"
}

# AddPV creates an additional frame to hold a process variable name,
# lower and upper limits, a tolerance value, an enable/disable button,
# and a "use tolerance" button.
proc AddPV {} {
    global row column lockdown deltaMode enableDisableButton group
    set w .userFrame.scrollWin.frame.canvas.frame
    incr row
    set enableDisableButton($row) "Enable "
    if !$lockdown {
        if {!$group} {
            APSButton .enableDisable$row -parent $w -textvariable enableDisableButton($row) \
              -command "EnableDisableMonitor -row $row" \
              -contextHelp "Enable or Disable monitoring the corrisponding process varibale" \
              -size small -noPack 1
        }
        APSButton .delete$row -parent $w -text "Delete" \
          -command "DeleteMonitor -row $row" \
          -contextHelp "Delete corrisponding sub-window" \
          -size small -noPack 1
    } else {
        if {!$group} {
            APSButton .enableDisable$row -parent $w -textvariable enableDisableButton($row) \
              -command LockdownMessage \
              -contextHelp "Enable or Disable monitoring the corrisponding process varibale" \
              -size small -noPack 1 
        }
        APSButton .delete$row -parent $w -text "Delete" \
          -command LockdownMessage \
          -contextHelp "Delete corrisponding sub-window" \
          -size small -noPack 1
    }
    if {!$group} {
        grid $w.enableDisable$row -column 0 -row $row
    }
    grid $w.delete$row -column 1 -row $row
    
    set column 1
    if !$lockdown {
        grid [entry $w.pv$row -textvariable PV(name$row)] -column [incr column] -row $row -sticky ew
        if $deltaMode {
            grid [entry $w.ref$row -textvariable RefPV(name$row)] -column [incr column] -row $row -sticky ew
        }
        grid [entry $w.min$row -textvariable min${row} -width 14] -column [incr column] -row $row -sticky ew
        grid [entry $w.max$row -textvariable max${row} -width 14] -column [incr column] -row $row -sticky ew
        if !$deltaMode {
            grid [entry $w.tol$row -textvariable tol${row} -width 14] -column [incr column] -row $row -sticky ew
        }
        grid [entry $w.maxRead$row -textvariable maxReading${row} -width 14] -column [incr column] -row $row -sticky ew
        grid [entry $w.countReq$row -textvariable countReq${row} -width 14] -column [incr column] -row $row -sticky ew
        grid [entry $w.countTimeFrame$row -textvariable countTimeFrame${row} -width 14] -column [incr column] -row $row -sticky ew
        if $deltaMode {
            grid [entry $w.mask$row -textvariable MaskPV(name$row)] -column [incr column] -row $row -sticky ew
            grid [entry $w.maskneq$row -textvariable MaskNotEqTo(value$row)] -column [incr column] -row $row -stick ew
        }
    } else {
        grid [label $w.pv$row -textvariable PV(name$row)] -column [incr column] -row $row -sticky ew
        if $deltaMode {
            grid [label $w.ref$row -textvariable RefPV(name$row)] -column [incr column] -row $row -sticky ew
        }
        grid [label $w.min$row -textvariable min${row} -width 14] -column [incr column] -row $row -sticky ew
        grid [label $w.max$row -textvariable max${row} -width 14] -column [incr column] -row $row -sticky ew
        if !$deltaMode {
            grid [label $w.tol$row -textvariable tol${row} -width 14] -column [incr column] -row $row -sticky ew
        }
        grid [label $w.maxRead$row -textvariable maxReading${row} -width 14] -column [incr column] -row $row -sticky ew
        grid [label $w.countReq$row -textvariable countReq${row} -width 14] -column [incr column] -row $row -sticky ew
        grid [label $w.countTimeFrame$row -textvariable countTimeFrame${row} -width 14] -column [incr column] -row $row -sticky ew
        if $deltaMode {
            grid [label $w.mask$row -textvariable MaskPV(name$row)] -column [incr column] -row $row -sticky ew
            grid [label $w.maskneq$row -textvariable MaskNotEqTo(value$row)] -column [incr column] -row $row -stick ew
        }
    }
    
    global countSeen$row countReq$row countTimeFrame$row countTimeSeen$row
    set countSeen$row 0
    set countReq$row 1
    set countTimeFrame$row 0
    set countTimeSeen$row ""
    if !$deltaMode {
        if !$lockdown {
            APSButton .calcTolerance$row -parent $w -text "Use Tolerance" -command "UseTolerance -row $row" \
              -contextHelp "Set limits to the current value plus or minus the tolerance value" \
              -size small -noPack 1
        } else {
            APSButton .calcTolerance$row -parent $w -text "Use Tolerance" -command LockdownMessage \
              -contextHelp "Set limits to the current value plus or minus the tolerance value" \
              -size small -noPack 1
        }
        grid $w.calcTolerance$row -column 9 -row $row
    }
    #ScrollAdjust   
}

# The UseTolerance procedure calculates the limits from the current
# value +- the tolerance, where the tolerance can either be a 
# number or a percentage. 
proc UseTolerance {args} {
    APSStrictParseArguments {row}
    global PV min${row} max${row} tol${row}  monitor
    if {![llength [set tol${row}]]} {
        APSSetVarAndUpdate status "Please enter a tolerance value"
        return
    }
    if {[lsearch -exact $monitor $row]==-1} {
        if {[pv linkw PV(value$row) $PV(name$row)]!=0} {
            APSSetVarAndUpdate status "Problem connecting to $PV(name$row)"
            APSAlertBox [APSUniqueName .] -beep once -errorMessage "Problem connecting to $PV(name$row)"
            return
        }
    }
    if {[pv getw PV(value$row)]!=0} {
        APSSetVarAndUpdate status "Problem connecting to $PV(name$row)"
        set beep 1
    } else {
        set pvvalue $PV(value$row)
        if {![string is double $pvvalue]} {
            set choices [pv info PV(value$row) choices]
            set pvvalue [lsearch -exact [lindex [lindex $choices 0] 1] $pvvalue]
        }
        if {[string index [set tol${row}] [expr [string length [set tol${row}]] - 1]] == {%}} {
            set value [string trimright  [set tol${row}] {%}]
            if {$pvvalue < 0} {
                set max${row} [format %.6g [expr $pvvalue * (1.0 - ($value / 100.0))]]
                set min${row} [format %.6g [expr $pvvalue * (1.0 + ($value / 100.0))]]
            } else {	      
                set min${row} [format %.6g [expr $pvvalue * (1.0 - ($value / 100.0))]]
                set max${row} [format %.6g [expr $pvvalue * (1.0 + ($value / 100.0))]]
            }	
        } else {
            set min${row} [format %.6g [expr $pvvalue - [set tol${row}]]]
            set max${row} [format %.6g [expr $pvvalue + [set tol${row}]]]
        }
    }
}

# DeleteMonitor calls EnableDisableMonitor to remove
# the selected process variable from the monitoring list
# and then destroys the frame containing the process 
# variable and related widgets.
proc DeleteMonitor {args} {
    global enableDisableButton group
    APSStrictParseArguments {row}
    set w .userFrame.scrollWin.frame.canvas.frame
    if {$enableDisableButton($row) == "Disable"} {
        EnableDisableMonitor -row $row
    }
    global deltaMode
    if {!$group} {
        destroy $w.enableDisable$row
    }
    destroy $w.ref$row
    destroy $w.mask$row
    destroy $w.maskneq$row
    destroy $w.delete$row 
    destroy $w.pv$row 
    destroy $w.min$row  
    destroy $w.max$row  
    destroy $w.tol$row 
    destroy $w.maxRead$row
    destroy $w.countReq$row
    destroy $w.countTimeFrame$row
    destroy $w.calcTolerance$row
    ScrollAdjust
}

# EnableDisableMonitor acts as a toggle button by adding or
# removing the selected process variable from the monitoring list.
proc EnableDisableMonitor {args} {
    global monitor lockdown deltaMode enableDisableButton
    APSStrictParseArguments {row}
    set w .userFrame.scrollWin.frame.canvas.frame
    if {$enableDisableButton($row) == "Enable "} {
        global PV RefPV MaskPV
        if {![llength $PV(name$row)]} {
            APSSetVarAndUpdate status "No value of Process Variable"
            bell
            return
        }
        global  countSeen$row countTimeSeen$row
        set countSeen$row 0
        set countTimeSeen$row ""
        if {[pv linkw PV(value$row) $PV(name$row)]!=0} {
            APSSetVarAndUpdate status "Problem connecting to $PV(name$row)"
            APSAlertBox [APSUniqueName .] -beep once -errorMessage "Problem connecting to $PV(name$row)"
            return
        }
        if {$deltaMode} {
            set RefPV(name$row) [string trim $RefPV(name$row)]
            set MaskPV(name$row) [string trim $MaskPV(name$row)]
            if {[string length $RefPV(name$row)]!=0} {
                # Need catch in case the RefPV is used several times, I think
                if {[pv linkw RefPV(value$row) $RefPV(name$row)]!=0} {
                    APSSetVarAndUpdate status "Problem connecting to $RefPV(name$row)"
                    APSAlertBox [APSUniqueName .] -beep once -errorMessage "Problem connecting to $RefPV(name$row)"
                    return
                }
            }
            if {[string length $MaskPV(name$row)]!=0} {
                # Need catch in case the MaskPV is used several times, I think
                if {[pv linkw MaskPV(value$row) $MaskPV(name$row)]!=0} {
                    APSSetVarAndUpdate status "Problem connecting to $MaskPV(name$row)"
                    APSAlertBox [APSUniqueName .] -beep once -errorMessage "Problem connecting to $MaskPV(name$row)"
                    return
                }
            }
        }
        lappend monitor "${row}"
        if !$lockdown {
            set enableDisableButton($row) "Disable"
            #$w.pv$row configure -state -bg White -readonlybackground White
        }
        APSSetVarAndUpdate status "$PV(name$row) added to monitoring list"
        if {[llength $monitor] == 1} {
            StartMonitor
        }
    } else {
        global countSeen$row countTimeSeen$row
        set countSeen$row 0
        set countTimeSeen$row ""
        set index [lsearch $monitor "${row}"]
        set monitor [concat [lrange $monitor 0 [expr $index - 1]] [lrange $monitor [expr $index + 1] end]] 
        set enableDisableButton($row) "Enable "
        if !$lockdown {
            #$w.pv$row configure -state normal -bg White -readonlybackground White
        }
        APSSetVarAndUpdate status "PV removed from monitoring list"
        if {![llength $monitor]} {
            APSSetVarAndUpdate abortMonitor 1
        }
    }		
}


proc CheckRunControlPV {args} {
    global runControlPV 
    catch {exec cavget -list=$runControlPV.RUN -pend=1} result
    if  {$result=="?"} {
        return -code error "Channel Access error in getting runcontrol PV $runControlPV"
    } elseif {$result == 1} {
        return -code error "RunControl PV already in use."
    }
}
# StartMonitor is the procedure that actively checks the value
# of the process variables compared to the limits.  When the 
# program is in this loop the interface does not respond very
# quickly.  Because of this it is only run when there are
# process variables in the monitoring list.
proc StartMonitor {} {
    global abortMonitor monitor silent silent15 NumOutOfLimits executeOnAlarmStart executeOnAlarmStop
    global commandDone appendErrorPVs repeatCmd deltaMode
    global runControlPV PV RefPV MaskPV MaskNotEqTo
    global apsScriptHost updateInterval tcl_platform lockdown
    set w .userFrame.scrollWin.frame.canvas.frame
    set u .userFrame.buttonFrame.outOfLimits.entry
    set beepCount 0
    set soundDir /usr/local/oag/apps/sounds
    set abortMonitor 0
    set useRC 0
    set RCFail 0
    if { [llength  $runControlPV] } {
        if [ catch { CheckRunControlPV } result ] {
            APSSetVarAndUpdate status $result
            EnableDisableAll -caption "Disable"
            APSSetVarAndUpdate status "Monitor not started. Check the runcontrol PV input"
            if {[file exists $soundDir/$apsScriptHost.wav]} {
                catch {exec aplay $soundDir/$apsScriptHost.wav &}
            }
            bell
            return
        }
        set useRC 1
    }
    if { $useRC } {
        catch {APSRunControlExit}
        
        exec cavput -list=${runControlPV}.CLR=1
        after 100
        if {[catch {APSRunControlInit -pv $runControlPV \
                      -description "PVmonitor Script" \
                      -timeout 60000} result]} {
            set rcStarted 0
        } else {
            set rcStarted 1
        }
        if {$rcStarted == 0} {
            if {[file exists $soundDir/$apsScriptHost.wav]} {
                catch {exec aplay $soundDir/$apsScriptHost.wav &}
            }
            bell 
            EnableDisableAll -caption "Disable"
            APSSetVarAndUpdate status "Unable to get runcontrol access: $result"
            APSSetVarAndUpdate status "Check medm window to see if other process uses $runControlPV"     
            exec medm -x -attach -macro RCPV=$runControlPV ./sr/psApp/APSRunControlSingle.adl & 
            return
        }
    }   

    APSDisableWidget .userFrame.entryFrame.e4
    while 1 {
        set beep 0
        set errorList ""
        set NumOutOfLimitsCount 0
        update
        if {$abortMonitor} {
            set NumOutOfLimits 0
            $u configure -bg White -readonlybackground White
            if { $useRC } {
                catch { APSRunControlExit }
            }		
            break
        }
        set pvList ""
        set connectionStatus ""
        foreach item $monitor {
            global min$item max$item maxReading$item countSeen$item countReq$item 
            global countTimeFrame$item countTimeSeen$item
            lappend pvList PV(value$item)
            lappend connectionStatus [lindex [lindex [pv info PV(value$item) state] 0] 1]
            set hasRefPV($item) 0
            set hasMaskPV($item) 0
            if $deltaMode {
                if [string length $RefPV(name$item)] {
                    lappend pvList RefPV(value$item)
                    set refConnectionStatus($item) [lindex [lindex [pv info RefPV(value$item) state] 0] 1]
                    set hasRefPV($item) 1
                }
                if [string length $MaskPV(name$item)] {
                    lappend pvList MaskPV(value$item)
                    set maskConnectionStatus($item) [lindex [lindex [pv info MaskPV(value$item) state] 0] 1]
                    set hasMaskPV($item) 1
                }
            }
        }
        if {[pv getw $pvList 30]!=0} {
            APSSetVarAndUpdate status "Problem connecting to a Process Variable"
            APSSetVarAndUpdate status "PV list: [join $pvList ,]"
            set NumOutOfLimitsCount [llength $pvList]
            set beep 1
        } else {
            foreach item $monitor cs $connectionStatus {
                if {$cs != "OK"} {
                    APSSetVarAndUpdate status "Warning: $PV(name$item) not connected"
                }
                set pvvalue $PV(value$item)
                if {![string is double $pvvalue]} {
                    set choices [pv info PV(value$item) choices]
                    set pvvalue [lsearch -exact [lindex [lindex $choices 0] 1] $pvvalue]
                }
                if {$hasRefPV($item)} {
                    if {$refConnectionStatus($item) != "OK"} {
                        APSSetVarAndUpdate status "Warning: $RefPV(name$item) not connected"
                    }
                    set refValue $RefPV(value$item)
                    if {![string is double $refValue]} {
                        set choices [pv info RefPV(value$item) choices]
                        set refValue [lsearch -exact [lindex [lindex $choices 0] 1] $refValue]
                    }
                    set pvvalue [expr $pvvalue-$refValue]
                }
                # default is unmasked
                set masked 0
                if {$hasMaskPV($item)} {
                    if {$maskConnectionStatus($item) != "OK"} {
                        APSSetVarAndUpdate status "Warning: $MaskPV(name$item) not connected"
                        set masked 0
                    }
                    set maskValue $MaskPV(value$item)
                    set choices [pv info MaskPV(value$item) choices]
                    set maskValue [lsearch -exact [lindex [lindex $choices 0] 1] $maskValue]
                    if [expr $maskValue != $MaskNotEqTo(value$item)] {
                        set masked 1
                    }
                }
                if {![regexp {^[-+]?[0-9]*\.?[0-9]*([0-9]\.?e[-+]?[0-9]*)?$} [set maxReading$item]]}  {
                    APSSetVarAndUpdate status "Warning: overwriting bogus value ([set maxReading$item]) for max reading of $PV(name$item)"
                    bell
                    set maxReading$item 1e300
                }
                if {![regexp {^[-+]?[0-9]*\.?[0-9]*([0-9]\.?e[-+]?[0-9]*)?$} [set max$item]]}  {
                    APSSetVarAndUpdate status "Warning: overwriting bogus value ([set max$item]) for high limit of $PV(name$item)"
                    bell
                    set max$item 1e300
                }
                if {![regexp {^[-+]?[0-9]*\.?[0-9]*([0-9]\.?e[-+]?[0-9]*)?$} [set min$item]]}  {
                    APSSetVarAndUpdate status "Warning: overwriting bogus value ([set min$item]) for low limit of $PV(name$item)"
                    bell
                    set min$item -1e300
                }
                if {![regexp {^[-+]?[0-9]*$} [set countReq$item]] || \
                      ![string length [set countReq$item]]}  {
                    set countReq$item 1
                }
                if {![regexp {^[-+]?[0-9]*$} [set countTimeFrame$item]]}  {
                    set countTimeFrame$item 0
                }
                if {[string length [set maxReading$item]] && \
                      [expr abs($pvvalue)>[set maxReading$item]]} {
                    APSSetVarAndUpdate status "Ignored excessive value for $PV(name$item): $pvvalue"
                    update
                    continue
                }
                if {[set countTimeFrame$item] > 0} {
                    set s [clock seconds]
                    set cts ""
                    foreach ele [set countTimeSeen$item] {
                        if {[expr {$s - $ele}] <= [set countTimeFrame$item]} {
                            lappend cts $ele
                        }
                    }
                    set countTimeSeen$item $cts
                    set countSeen$item [llength [set countTimeSeen$item]]
                }
                if {($masked==1 || 
                     (((![llength [set min$item]]) || ($pvvalue >= [set min$item])) && 
                      ((![llength [set max$item]]) || ($pvvalue <= [set max$item])))) &&
                    ($cs == "OK")} {
                    set color White
                    if {[set countTimeFrame$item] <= 0} {
                        set countSeen$item 0
                    }
                } else {
                    set beep 1
                    incr countSeen$item
                    set color Red
                    if {[set countTimeFrame$item] > 0} {
                        lappend countTimeSeen$item $s
                    }
                    if {[set countSeen$item]>=[set countReq$item]} {
                        incr NumOutOfLimitsCount
                    }
                    if $hasRefPV($item) {
                        append errorList "$PV(name$item)-$RefPV(name$item) $pvvalue "
                    } else {
                        append errorList "$PV(name$item) $pvvalue "
                    }
                }
                if {[$w.pv$item cget -bg] != $color} {
                    if !$lockdown {
                        $w.pv$item configure -bg $color -readonlybackground $color
                    } else {
                        $w.pv$item configure -bg $color 
                    }
                    if {$color == "Red"} {
                        if $hasRefPV($item) {
                            APSSetVarAndUpdate status "[clock format [clock seconds] -format "%D %T"]: $PV(name$item)-$RefPV(name$item) is outside the limits : $pvvalue"
                        } else {
                            APSSetVarAndUpdate status "[clock format [clock seconds] -format "%D %T"]: $PV(name$item) is outside the limits : $pvvalue"
                        }
                    } else {
                        if $hasRefPV($item) {
                            APSSetVarAndUpdate status "[clock format [clock seconds] -format "%D %T"]: $PV(name$item)-$RefPV(name$item) is inside the limits : $pvvalue"
                        } else {
                            APSSetVarAndUpdate status "[clock format [clock seconds] -format "%D %T"]: $PV(name$item) is inside the limits : $pvvalue"
                        }
                    }
                }
            }
        }
        if {($beep) && (!$silent) && (!$silent15)} {
            if {$beepCount == 0} {
                if {[file exists $soundDir/$apsScriptHost.wav]} {
                    catch {exec aplay $soundDir/$apsScriptHost.wav &}
                }
                bell
            } else {
                bell
            }
            incr beepCount
            if {$beepCount == 30} {
                set beepCount 0
            }
        }
        set executeCmd ""
        if {($NumOutOfLimits == 0 || $repeatCmd) && ($NumOutOfLimitsCount > 0)} {
            set executeCmd $executeOnAlarmStart
        } elseif {($NumOutOfLimits > 0) && ($NumOutOfLimitsCount == 0)} {
            set executeCmd $executeOnAlarmStop
            if {[set countTimeFrame$item] > 0} {
                set countTimeSeen$item ""
            }        
        }
        if {$appendErrorPVs && [string length $executeCmd]} {
            set cmd ""
            foreach item [split $executeCmd \;] {
                append cmd "$item $errorList;"
            }
            set executeCmd $cmd
        }
        set NumOutOfLimits $NumOutOfLimitsCount
        if {$NumOutOfLimitsCount} {
            set color Red
        } else {
            set color White
        }
        if {[$u cget -bg] != $color} {
            $u configure -bg $color -readonlybackground $color
        }
        
        if {[llength $executeCmd]} {
            global labelForProcess 
            
            foreach item [split $executeCmd \;] {
                if ![string length [string trim $item]] {
                    continue
                }
                set commandDone 0
                set executeID [ APSExecLog .execDialog -name "$labelForProcess" \
                                  -unixCommand [string trim $item] -callback "set commandDone 1" \
                                  -cancelCallback "set commandDone 2" -abortCallback "set commandDone 2" ]
                if { $useRC } {
                    while { !$commandDone } {
                        if [catch {APSRunControlPing} result] {
                            APSSetVarAndUpdate status "Ending via ping from runControl: $result"
                            bell
                            APSExecLogAbort -id $executeID -destroy 1
                            APSSetVarAndUpdate status "Execution of $item aborted" 
                            EnableDisableAll -caption "Disable"
                            catch {APSRunControlExit}
                            return
                        }
                        APSWaitWithUpdate -waitSeconds 1
                    }	   
                } elseif { !$commandDone } {
                    tkwait variable commandDone
                }
                if { $commandDone==2 } break
                update
            }
        }
        if { $useRC } {
            if [catch {APSRunControlPing} result] {
                APSSetVarAndUpdate status "Ending via ping from runControl: $result" 
                bell
                EnableDisableAll -caption "Disable"
                catch {APSRunControlExit}
                return
            }
        }
        if {$updateInterval <= .001} {
            set updateInterval 2.0
        }
        APSWaitWithUpdate -waitSeconds $updateInterval
    }
    APSEnableWidget .userFrame.entryFrame.e4
}

#This procedure changes all the limits by re-reading the 
#current PV values and using the existing individual tolerances
proc ResetLimits {} {
    global row PV
    for {set i 1} {$i <= $row} {incr i} {
        if {[winfo exists .userFrame.scrollWin.frame.canvas.frame.pv${i}]} {
            global tol${i}
            if {([llength $PV(name$i)]) && ([llength [set tol${i}]])} {
                UseTolerance -row $i
            }
        }
    }
}

# This procedure allows the user to set the max reading values for all channels
proc MaxReadingsDialog {} {
    destroy .maxReadingWidget
    APSDialogBox .maxReadingWidget -name "Maximum Reading Entry Form" \
      -okCommand SetAllMaxReading
    APSLabeledEntry .enterMinLimit -parent .maxReadingWidget.userFrame \
      -label "Maximum Valid Reading Absolute Value" -textVariable globalMaxReading
}

proc SetAllMaxReading {} {
    global row globalMaxReading PV
    for {set i 1} {$i <= $row} {incr i} {
        if {[winfo exists .userFrame.scrollWin.frame.canvas.frame.pv${i}]} {
            global maxReading${i}
            if {[llength $PV(name$i)]} {
                set maxReading${i} $globalMaxReading
            }
        }
    }
}

# This procedure allows the user to set the max reading values for all channels
proc MaskIfNotEqualToDialog {} {
    destroy .maskIfNotEqToWidget
    APSDialogBox .maskIfNotEqToWidget -name "Mask value entry form" \
      -okCommand SetAllMaskIfNotEqualToValues
    APSLabeledEntry .enterValue -parent .maskIfNotEqToWidget.userFrame \
      -label "Value for Mask PV that results in unmasking of comparison" -textVariable globalMaskIfNotEqToValue
}

proc SetAllMaskIfNotEqualToValues {} {
    global row globalMaskIfNotEqToValue PV MaskNotEqTo
    for {set i 1} {$i <= $row} {incr i} {
        if {[winfo exists .userFrame.scrollWin.frame.canvas.frame.pv${i}]} {
            if {[llength $PV(name$i)]} {
                set MaskNotEqTo(value$i) $globalMaskIfNotEqToValue
            }
        }
    }
}


# This procedure allows the user to set the required count for all channels
proc CountReqsDialog {} {
    destroy .countReqWidget
    APSDialogBox .countReqWidget -name "Required Count Entry Form" \
      -okCommand SetAllCountReq
    APSLabeledEntry .enterMinLimit -parent .countReqWidget.userFrame \
      -label "Number of successive alarms before action" -textVariable globalCountReq
}

proc SetAllCountReq {} {
    global row globalCountReq PV
    for {set i 1} {$i <= $row} {incr i} {
        if {[winfo exists .userFrame.scrollWin.frame.canvas.frame.pv${i}]} {
            global countReq${i}
            if {[llength $PV(name$i)]} {
                set countReq${i} $globalCountReq
            }
        }
    }
}

#This procedure is called when a file doesn't contain any limit information.
#This allows the user to set limits on every thing at once.
proc LimitsDialog {} {
    destroy .limitsWidget
    APSDialogBox .limitsWidget -name "Limits Entry Form" \
      -okCommand SetAllLimits
    APSLabeledEntry .enterMinLimit -parent .limitsWidget.userFrame \
      -label "Enter Lower Limit" -textVariable globalLowerLimit
    APSLabeledEntry .enterMaxLimit -parent .limitsWidget.userFrame \
      -label "Enter Upper Limit" -textVariable globalUpperLimit
    pack [label .limitsWidget.userFrame.label1 -text OR]
    APSLabeledEntry .enterTolerance -parent .limitsWidget.userFrame \
      -label "Enter Tolerance" -textVariable globalTolerance
}

#This procedure is called by the LimitsDialog procedure when 
#the user clicks OK.
proc SetAllLimits {} {
    global globalTolerance
    if {[llength $globalTolerance]} {
        ChangeAllTolerances
    } else {
        global row globalLowerLimit globalUpperLimit PV
        for {set i 1} {$i <= $row} {incr i} {
            if {[winfo exists .userFrame.scrollWin.frame.canvas.frame.pv${i}]} {
                global min${i} max${i}
                if {[llength $PV(name$i)]} {
                    set min${i} $globalLowerLimit
                    set max${i} $globalUpperLimit
                }
            }
        }
    }
}

#This procedure brings up a dialog that lets the user select a tolerance
#level for every PV.
proc ChangeAllTolerancesDialog {} {
    destroy .toleranceWidget
    APSDialogBox .toleranceWidget -name "Change All Tolerance Levels" \
      -okCommand ChangeAllTolerances
    APSLabeledEntry .enterTolerance -parent .toleranceWidget.userFrame \
      -label "Enter Tolerance" -textVariable globalTolerance
}

#This procedure is called by the other procedures when they want to set
#all tolerance values at once.
proc ChangeAllTolerances {} {
    global row globalTolerance PV
    for {set i 1} {$i <= $row} {incr i} {
        if {[winfo exists .userFrame.scrollWin.frame.canvas.frame.pv${i}]} {
            global tol${i}
            if {[llength $PV(name$i)]} {
                set tol${i} $globalTolerance
                UseTolerance -row $i
            }
        }
    }
}

#This procedure replaces the horrable task of clicking on every single enable/disable button.
proc EnableDisableAll {args} {
    set caption "Enable "
    set w .userFrame.scrollWin.frame.canvas.frame
    APSStrictParseArguments {caption}
    global row abortMonitor monitor lockdown deltaMode PV RefPV MaskPV status enableDisableButton group
    set StartMonitorVar 0
    set nameList ""
    set valueList ""
    for {set i 1} {$i <= $row} {incr i} {
        if {[winfo exists $w.pv${i}]} {
            if {([llength $PV(name$i)]) && ($enableDisableButton($i) == $caption)} {
                if {$caption == "Enable "} {
                    lappend monitor "${i}"
                    lappend nameList $PV(name$i)
                    lappend valueList PV(value$i)
                    if {$deltaMode} {
                        set RefPV(name$i) [string trim $RefPV(name$i)]
                        if {[string length $RefPV(name$i)]!=0} {
                            lappend nameList $RefPV(name$i)
                            lappend valueList RefPV(value$i)
                        }
                        set MaskPV(name$i) [string trim $MaskPV(name$i)]
                        if {[string length $MaskPV(name$i)]!=0} {
                            lappend nameList $MaskPV(name$i)
                            lappend valueList MaskPV(value$i)
                        }
                    }
                    
                } else {
                    set index [lsearch $monitor "${i}"]
                    set monitor [concat [lrange $monitor 0 [expr $index - 1]] [lrange $monitor [expr $index + 1] end]] 
                    set enableDisableButton($i) "Enable "
                    if !$lockdown {
                        #$w.pv${i} configure -state normal -bg White -readonlybackground White
                    }
                    APSSetVarAndUpdate status "PV removed from monitoring list"
                    if {![llength $monitor]} {
                        APSSetVarAndUpdate abortMonitor 1
                    }
                }
            }
        }
    }
    if {[llength $nameList]} {
        APSSetVarAndUpdate status "Linking PVs"
        if {[pv linkw $valueList $nameList]!=0} {
            foreach value $valueList name $nameList {
                if {[pv linkw $value $name]!=0} {
                    APSSetVarAndUpdate status "Problem connecting to $name"
                    APSAlertBox [APSUniqueName .] -beep once -errorMessage "Problem connecting to $name"
                    return
                }
            }
        }
        APSSetVarAndUpdate status "Done Linking PVs"
        if {!$group} {
            APSSetVarAndUpdate status "Adjusting Enable/Disable button text for [llength $monitor] buttons"
        }
        foreach i $monitor {
            set enableDisableButton($i) "Disable"
            if !$lockdown {
                #$w.pv$i configure -state readonly -bg White -readonlybackground White
            }
        }
        update idletasks
        APSSetVarAndUpdate status "Monitoring PVs"
        set StartMonitorVar 1
        StartMonitor
    }
}


# SaveData allows saving the current process variables, 
# limits, and tolerances.
proc SaveData {} {
    global updateInterval appendErrorPVs deltaMode
    destroy .saveDialog
    set selection [APSFileSelectDialog .saveDialog -checkValidity 0 \
                     -title "Save Current Configuration" \
                     -contextHelp "Enter a file in which to save the process variables and limits"]
    if {[llength $selection]} {
        set count 0
        global row PV RefPV MaskPV MaskNotEqTo
        for {set i 1} {$i <= $row} {incr i} {
            if {[winfo exists .userFrame.scrollWin.frame.canvas.frame.pv${i}]} {
                global min${i} max${i} tol${i} maxReading${i} countReq$i countTimeFrame$i 
                if {[llength $PV(name$i)]} {
                    lappend sddsData(Column.ControlName) $PV(name$i)
                    if {[llength [set min${i}]]} {
                        lappend sddsData(Column.MinLimit) [set min${i}]
                    } else {
                        lappend sddsData(Column.MinLimit) NaN
                    }
                    if $deltaMode {
                        lappend sddsData(Column.ReferenceControlName) $RefPV(name$i)
                        lappend sddsData(Column.MaskControlName) $MaskPV(name$i)
                        lappend sddsData(Column.MaskIfNotEqualTo) $MaskNotEqTo(value$i)
                    }
                    if {[llength [set max${i}]]} {
                        lappend sddsData(Column.MaxLimit) [set max${i}]
                    } else {
                        lappend sddsData(Column.MaxLimit) NaN
                    }
                    if {[llength [set tol${i}]]} {
                        lappend sddsData(Column.Tolerance) [set tol${i}]
                    } else {
                        lappend sddsData(Column.Tolerance) NaN
                    }
                    if {[llength [set maxReading${i}]]} {
                        lappend sddsData(Column.MaxReading) [set maxReading${i}]
                    } else {
                        lappend sddsData(Column.MaxReading) NaN
                    }
                    lappend sddsData(Column.CountRequired) [set countReq${i}]
                    lappend sddsData(Column.CountTimeFrame) [set countTimeFrame${i}]
                    incr count
                }
            }
        }
        if {$count} {
            set sddsData(ParameterNames) "AlarmStartCmd AlarmStopCmd LabelForProcess RunControlForProcess UpdateInterval AppendErrorPVs LockdownMode RepeatCommandMode"
            set sddsData(ColumnNames) "ControlName MinLimit MaxLimit Tolerance MaxReading CountRequired CountTimeFrame"
            if $deltaMode {
                append sddsData(ColumnNames) " ReferenceControlName"
                set sddsData(ColumnInfo.ReferenceControlName) "type SDDS_STRING"
                append sddsData(ColumnNames) " MaskControlName"
                set sddsData(ColumnInfo.MaskControlName) "type SDDS_STRING"
                append sddsData(ColumnNames) " MaskIfNotEqualTo"
                set sddsData(ColumnInfo.MaskIfNotEqualTo) "type SDDS_DOUBLE"
            } 
            set sddsData(ParameterInfo.UpdateInterval) "type SDDS_DOUBLE"
            set sddsData(ParameterInfo.AppendErrorPVs) "type SDDS_SHORT"
            set sddsData(ColumnInfo.MinLimit) "type SDDS_DOUBLE"
            set sddsData(ColumnInfo.MaxLimit) "type SDDS_DOUBLE"
            set sddsData(ColumnInfo.Tolerance) "type SDDS_DOUBLE"
            set sddsData(ColumnInfo.MaxReading) "type SDDS_DOUBLE"
            set sddsData(ColumnInfo.CountRequired) "type SDDS_LONG"
            set sddsData(ColumnInfo.CountTimeFrame) "type SDDS_LONG"
            global executeOnAlarmStart executeOnAlarmStop labelForProcess runControlPV lockdown repeatCmd
            set sddsData(Parameter.AlarmStartCmd) [list $executeOnAlarmStart]
            set sddsData(Parameter.AlarmStopCmd) [list $executeOnAlarmStop]
            set sddsData(Parameter.LabelForProcess) [list $labelForProcess]
            set sddsData(Parameter.RunControlForProcess) [list $runControlPV]
            set sddsData(Parameter.UpdateInterval) [list $updateInterval]
            set sddsData(Parameter.AppendErrorPVs) [list $appendErrorPVs]
            set sddsData(Parameter.LockdownMode) [list $lockdown]
            set sddsData(Parameter.RepeatCommandMode) [list $repeatCmd]
            set sddsData(Column.ControlName) [list $sddsData(Column.ControlName)]
            if $deltaMode {
                set sddsData(Column.ReferenceControlName) [list $sddsData(Column.ReferenceControlName)]
                set sddsData(Column.MaskControlName) [list $sddsData(Column.MaskControlName)]
                set sddsData(Column.MaskIfNotEqualTo) [list $sddsData(Column.MaskIfNotEqualTo)]
            }
            set sddsData(Column.MinLimit) [list $sddsData(Column.MinLimit)]
            set sddsData(Column.MaxLimit) [list $sddsData(Column.MaxLimit)]
            set sddsData(Column.Tolerance) [list $sddsData(Column.Tolerance)]
            set sddsData(Column.MaxReading) [list $sddsData(Column.MaxReading)]
            set sddsData(Column.CountRequired) [list $sddsData(Column.CountRequired)]
            set sddsData(Column.CountTimeFrame) [list $sddsData(Column.CountTimeFrame)]
            if {[catch {sdds save $selection sddsData} results]} {
                bell
                APSSetVarAndUpdate status "Error saving data: $results"
                return
            }
            APSSetVarAndUpdate status "Data saved"
        }
    }
}

# ReadData clears out any current process variables and reads in
# a new set from a file which includes limits and tolerances.
proc ReadData {args} {
    set fileName ""
    APSStrictParseArguments {fileName}
    global deltaMode

    if {![string length $fileName] || ![file exists $fileName]} {
        destroy .ReadDialog
        set selection [APSFileSelectDialog .readDialog -checkValidity 1 \
                         -title "Load Configuration" \
                         -contextHelp "Enter a file from which to load the process variables and limits"]
    } else {
        set selection $fileName

    }
    if {[llength $selection]} {
        if {[catch {sdds load $selection data} results]} {
            APSAlertBox .readDataAlert -beep once -errorMessage "$results"
            return
        }
        set columnList $data(ColumnNames)
        #if {[catch {exec sddsquery $selection -columnlist} columnList]} {
        #    APSAlertBox .readDataAlert -beep once -errorMessage "$columnList"
        #    return
        #}
        if {[lsearch $columnList "ControlName"] == -1} {
            APSAlertBox .readDataAlert -beep once -errorMessage "File doesn't contain ControlName column"
            return
        }
        lappend ColumnNames "ControlName"
        foreach column "MinLimit MaxLimit Tolerance MaxReading CountRequired CountTimeFrame" {
            if {[lsearch $columnList $column] != -1} {
                lappend ColumnNames $column
            }
        }

        if [lsearch $columnList ReferenceControlName]!=-1 {
            lappend ColumnNames ReferenceControlName
            if !$deltaMode {
                APSAlertBox .readDataAlert -beep once -errorMessage "File $selection has ReferenceControlName. You must run this script with the -deltaMode 1 option to use this file."
                return
            }
        } else {
            if $deltaMode {
                APSSetVarAndUpdate status "Warning: this script is in delta mode, but there is no ReferenceControlName column in $selection."
            }
        }

        
        set hasMaskCN 0
        if [lsearch $columnList MaskControlName]!=-1 {
            lappend ColumnNames MaskControlName
            set hasMaskCN 1
            if !$deltaMode {
                APSAlertBox .readDataAlert -beep once -errorMessage "File $selection has MaskControlName. You must run this script with the -deltaMode 1 option to use this file."
                return
            }
        } else {
            if $deltaMode {
                APSSetVarAndUpdate status "Warning: this script is in delta mode, but there is no MaskControlName column in $selection."
            }
        }

        
        if [lsearch $columnList MaskIfNotEqualTo]!=-1 {
            lappend ColumnNames MaskIfNotEqualTo
            if !$deltaMode {
                APSAlertBox .readDataAlert -beep once -errorMessage "File $selection has MaskIfNotEqualTo. You must run this script with the -deltaMode 1 option to use this file."
                return
            }
        } else {
            if $deltaMode {
                APSSetVarAndUpdate status "Warning: this script is in delta mode, but there is no MaskIfNotEqualTo column in $selection."
            }
            if $hasMaskCN {
                APSAlertBox .readDataAlert -beep once -errorMessage "File $selection has MaskControlName, but not MaskIfNotEqualTo."
            }
        }

        
#        if {[catch {exec sdds2stream $selection -columns=[join $ColumnNames ,]} results]} {
#            APSAlertBox .readDataAlert -beep once -errorMessage "$results"
#            return
#        }
        global row PV RefPV MaskPV MaskNotEqTo
        for {set i 1} {$i <= $row} {incr i} {
            if {[winfo exists .userFrame.scrollWin.frame.canvas.frame.pv${i}]} {
                DeleteMonitor -row $i
            }
        }
        set rows [llength [lindex $data(Column.ControlName) 0]]
        for {set i 0} {$i < $rows} {incr i} {
            foreach column $ColumnNames {
                set $column [lindex [lindex $data(Column.$column) 0] $i]
            }
            
            AddPV
            global min${row} max${row} tol${row} maxReading${row} countReq${row} countTimeFrame${row}
            set PV(name$row) $ControlName
            if {[info exists ReferenceControlName]} {
                set RefPV(name$row) $ReferenceControlName
            } else {
                set RefPV(name$row) ""
            }
            if {[info exists MaskControlName]} {
                set MaskPV(name$row) $MaskControlName
            } else {
                set MaskPV(name$row) ""
            }
            if {[info exists MaskIfNotEqualTo]} {
                set MaskNotEqTo(value$row) $MaskIfNotEqualTo
            } else {
                set MaskNotEqTo(value$row) ""
            }
            if {[info exists MinLimit]} {
                if {[string compare -nocase $MinLimit NaN] == 0} {
                    set min${row} ""
                } else {
                    set min${row} [format %.6g $MinLimit]
                }
            }
            if {[info exists MaxLimit]} {
                if {[string compare -nocase $MaxLimit NaN] == 0} {
                    set max${row} ""
                } else {
                    set max${row} [format %.6g $MaxLimit]
                }
            }
            if {[info exists Tolerance]} {
                if {[string compare -nocase $Tolerance NaN] == 0} {
                    set tol${row} ""
                } else {
                    set tol${row} [format %.6g $Tolerance]
                }
            }
            if {[info exists MaxReading]} {
                if {[string compare -nocase $MaxReading NaN] == 0} {
                    set maxReading${row} ""
                } else {
                    set maxReading${row} [format %.6g $MaxReading]
                }
            } else {
                set maxReading${row} 1e300
            }
            if {[info exists CountRequired]} {
                set countReq${row} [format %ld $CountRequired]
            } else {
                set countReq${row} 1
            }
            if {[info exists CountTimeFrame]} {
                set countTimeFrame${row} [format %ld $CountTimeFrame]
            } else {
                set countTimeFrame${row} 0
            }
        }
        ScrollAdjust   

        if {[llength $ColumnNames] == 1} {
            LimitsDialog
        }

        set parameterList $data(ParameterNames)
        #if {[catch {exec sddsquery $selection -parameterlist} parameterList]} {
        #    APSAlertBox .readDataAlert -beep once -errorMessage "$parameterList"
        #    return
        #}
        #APSSetVarAndUpdate status "$parameterList"        puts "DEBUG $parameterList"
        foreach parameter "AlarmStartCmd AlarmStopCmd LabelForProcess RunControlForProcess UpdateInterval AppendErrorPVs LockdownMode RepeatCommandMode" {
            if {[lsearch $parameterList $parameter] != -1} {
                lappend ParameterNames $parameter
            }
        }
        #APSSetVarAndUpdate status "[join $ParameterNames ,]"
        foreach parameter $ParameterNames {
            set $parameter [lindex $data(Parameter.$parameter) 0]
        }

        #if {[catch {exec sdds2stream $selection -parameters=[join $ParameterNames ,]} results]} {
        #    APSAlertBox .readDataAlert -beep once -errorMessage "$results"
        #    return
        #}
        global executeOnAlarmStart executeOnAlarmStop labelForProcess runControlPV updateInterval appendErrorPVs lockdown repeatCmd
        set labelForProcess $fileName
            if {[info exists AlarmStartCmd]} {
                set executeOnAlarmStart $AlarmStartCmd
            }
            if {[info exists AlarmStopCmd]} {
                set executeOnAlarmStop $AlarmStopCmd
            }
            if {[info exists LabelForProcess]} {
                set labelForProcess $LabelForProcess
            }
            if {[info exists RunControlForProcess]} {
                set runControlPV $RunControlForProcess
            }
            if {[info exists UpdateInterval]} {
                set updateInterval [format %.2f $UpdateInterval]
            }
            if {[info exists AppendErrorPVs]} {
                set appendErrorPVs $AppendErrorPVs
            }
            if {[info exists LockdownMode]} {
                set lockdown $LockdownMode
            }
            if {[info exists RepeatCommandMode]} {
                set repeatCmd $RepeatCommandMode
            }
    }
}

# AddData leaves the current process variables unchanged and appends a new
# list of process variables from a file which includes limits and tolerances.
proc AddData {} {
    destroy .AddDialog
    global deltaMode
    if $deltaMode {
        APSAlertBox .addDataAlert -beep once -errorMessage "Unfortunately, adding data is not supported yet in delta mode."
        return 
    }
    set selection [APSFileSelectDialog .addDialog -checkValidity 1 \
                     -title "Add Configuration" \
                     -contextHelp "Enter a file from which to add additional process variables and limits"]
    if {[llength $selection]} {
        if {[catch {exec sddsquery $selection -columnlist} columnList]} {
            APSAlertBox .addDataAlert -beep once -errorMessage "$columnList"
            return
        }
        if {[lsearch $columnList "ControlName"] == -1} {
            APSAlertBox .addDataAlert -beep once -errorMessage "File doesn't contain ControlName column"
            return
        }
        lappend ColumnNames "ControlName"
        foreach column "MinLimit MaxLimit Tolerance MaxReading CountRequired CountTimeFrame" {
            if {[lsearch $columnList $column] != -1} {
                lappend ColumnNames $column
            }
        }

        if {[catch {exec sdds2stream $selection -columns=[join $ColumnNames ,]} results]} {
            APSAlertBox .addDataAlert -beep once -errorMessage "$results"
            return
        }
        global row PV
        foreach $ColumnNames $results {
            AddPV
            
            global min${row} max${row} tol${row} maxReading${row} countReq${row} countTimeFrame${row}
            set PV(name$row) $ControlName
            if {[info exists MinLimit]} {
                if {[string compare -nocase $MinLimit NaN] == 0} {
                    set min${row} ""
                } else {
                    set min${row} [format %13.5g $MinLimit]
                }
            }
            if {[info exists MaxLimit]} {
                if {[string compare -nocase $MaxLimit NaN] == 0} {
                    set max${row} ""
                } else {
                    set max${row} [format %13.5g $MaxLimit]
                }
            }
            if {[info exists Tolerance]} {
                if {[string compare -nocase $Tolerance NaN] == 0} {
                    set tol${row} ""
                } else {
                    set tol${row} [format %13.5g $Tolerance]
                }
            }
            if {[info exists MaxReading]} {
                if {[string compare -nocase $MaxReading NaN] == 0} {
                    set maxReading${row} ""
                } else {
                    set maxReading${row} [format %13.5g $MaxReading]
                }
            } else {
                set maxReading${row} 1e300
            }
            if {[info exists CountRequired]} {
                set countReq${row} [format %ld $CountRequired]
            } else {
                set countReq${row} 1
            }
            if {[info exists CountTimeFrame]} {
                set countTimeFrame${row} [format %ld $CountTimeFrame]
            } else {
                set countTimeFrame${row} 0
            }
        }
        ScrollAdjust   

        if {[llength $ColumnNames] == 1} {
            LimitsDialog
        }
        if {[catch {exec sddsquery $selection -parameterlist} parameterList]} {
            APSAlertBox .readDataAlert -beep once -errorMessage "$parameterList"
            return
        }
        foreach parameter "AlarmStartCmd AlarmStopCmd" {
            if {[lsearch $parameterList $parameter] != -1} {
                lappend ParameterNames $parameter
            }
        }
        if {[catch {exec sdds2stream $selection -parameters=[join $ParameterNames ,]} results]} {
            APSAlertBox .readDataAlert -beep once -errorMessage "$results"
            return
        }
        global executeOnAlarmStart executeOnAlarmStop
        foreach $ParameterNames $results {
            if {([info exists AlarmStartCmd]) && (![llength $executeOnAlarmStart])} {
                set executeOnAlarmStart $AlarmStartCmd
            }
            if {([info exists AlarmStopCmd]) && (![llength $executeOnAlarmStop])} {
                set executeOnAlarmStop $AlarmStopCmd
            }
        }
    }
}

APSApplication . -name "PV Monitor" \
  -overview "PV Monitor compares the current value of one or more \
process variables to the allowed limits.  If the value is outside \
the limits a red error signal appears."
wm minsize . 59 10
set status "Ready..."
APSScrolledStatus .status -parent .userFrame -textVariable status -packOption "-fill x" -withButtons 1

if {[string length $fileName]} {
    # Need to read the LockdownMode parameter, if present, before creating the UI
    if ![catch {exec sdds2stream $fileName -parameter=LockdownMode} result] {
        set lockdown $result
    }
}

.menu.file.menu insert 1 command -label "Save..." -underline 0 -command "SaveData"
.menu.file.menu insert 2 command -label "Read..." -underline 0 -command "ReadData"
.menu.file.menu insert 3 command -label "Add..." -underline 0 -command "AddData"

set monitor ""
set w .userFrame
set row 0
set column 0
pack [frame $w.buttonFrame] -fill x
pack [frame $w.buttonFrame2] -fill x
pack [frame $w.buttonFrame3] -fill x
pack [frame $w.buttonFrame4] -fill x

pack [frame $w.entryFrame -relief ridge -borderwidth 2] -fill x
pack [frame $w.labelFrame] -fill x
APSScroll .scrollWin -parent $w -name "" -packOption "-fill both -expand true" 
$w.scrollWin.frame.canvas config -yscrollincrement 22
pack configure $w.scrollWin.frame.canvas -fill both -expand true
#pack [frame $w.f1] -fill x -pady .1i
set u $w.scrollWin.frame.canvas.frame
global apsContextHelp
if !$deltaMode {
    pack [label $w.labelFrame.pvName -text "\t\tPV Name"] -side left
    pack [label $w.labelFrame.lowerLimit -text "\t  Low Limit"] -side left
    pack [label $w.labelFrame.upperLimit -text "     High Limit"] -side left
    pack [label $w.labelFrame.tolerance -text "    Tolerance"] -side left
    pack [label $w.labelFrame.maxread -text "      Max. Reading"] -side left
    pack [label $w.labelFrame.countreq -text "   Req'd Count"] -side left
    pack [label $w.labelFrame.counttimeframe -text "Count Timeframe\n(seconds)"] -side left
} else {
    pack [label $w.labelFrame.pvName -text "\t\tPV Name"] -side left
    pack [label $w.labelFrame.refPvName -text "\t    Ref. PV Name"] -side left
    pack [label $w.labelFrame.lowerLimit -text "\t Low Limit"] -side left
    pack [label $w.labelFrame.upperLimit -text "    High Limit"] -side left
    pack [label $w.labelFrame.maxread -text "   Max. Reading"] -side left
    pack [label $w.labelFrame.countreq -text "   Req'd Count"] -side left
    pack [label $w.labelFrame.counttimeframe -text "    Count\n    Timeframe\n    (seconds)"] -side left
    pack [label $w.labelFrame.maskPvName -text "      Mask PV Name"] -side left
    pack [label $w.labelFrame.maskEqValue -text "         Mask if not\n       equal to"] -side left
}

set apsContextHelp($w.labelFrame.pvName) "The names of the process variables to monitor."
set apsContextHelp($w.labelFrame.lowerLimit) "Lower limits of PV values that are in range."
set apsContextHelp($w.labelFrame.upperLimit) "Upper limits of PV values that are in range."
if !$deltaMode {
    set apsContextHelp($w.labelFrame.tolerance) "Tolerance values are not directly used while monitoring. Instead they are used to reset the upper and lower limits when the Use Tolerance button is pressed. It reads the value from the PV and subtracts the tolerance and sets that as the lower limit. It adds the tolerance to the PV value to set the upper limit."
}
set apsContextHelp($w.labelFrame.maxread) "Any PV values that are greater than the Maximum Reading are ignored. If you do not need this feature, leave it blank or put in a very large number."
set apsContextHelp($w.labelFrame.countreq) "This is the number of times the PV must be outside the limits before the alarm script is run. If the count timeframe is 0, then as soon as the PV is inside the limits, the count is reset. If the count timeframe is greater then 0 then the count is not reset until the time duration defined the by count timeframe has passed or if the alarm script has been run. This might be useful if you want to see if it goes out of range 10 times over a 60 second timeframe."
set apsContextHelp($w.labelFrame.counttimeframe) "The count timeframe is related to the required count. If the PV is outside the limits the required count number of times during the timeperiod defined by the count timeframe, then the alarm script is run. A value of 0 indicates that this column is not used."
#grid columnconfigure $u 2 -weight 1
#grid columnconfigure $u 3 -weight 1
#grid columnconfigure $u 4 -weight 1
#grid columnconfigure $u 5 -weight 1

APSFrame .f1 -parent $w.buttonFrame -relief flat
APSLabeledOutput .filename -parent $w.buttonFrame.f1.frame -label "Filename:" -width 130 \
  -textVariable fileName

if !$lockdown {
    APSButton .addPV -parent $w.buttonFrame -text "Add Process Variable" -command {AddPV} \
      -contextHelp "Add an additional sub-window to monitor another process variable"
    APSButton .enableAll -parent $w.buttonFrame -text "Enable All" \
      -contextHelp "Enable monitoring for all process variables" \
      -command {EnableDisableAll -caption "Enable "}
    APSButton .disableAll -parent $w.buttonFrame -text "Disable All" \
      -contextHelp "Disable monitoring for all process variables" \
      -command {EnableDisableAll -caption "Disable"}
    pack [checkbutton $w.buttonFrame.silent -text "Silent" \
            -variable silent -relief ridge -padx 4] -side left -fill y
    pack [checkbutton $w.buttonFrame.silent15 -text "Silent 15 Minutes" \
            -variable silent15 -relief ridge -padx 4 -command {after 900000 {set silent15 0}}] -side left -fill y
}
set NumOutOfLimits 0
set silent 0
set silent15 0
APSLabeledOutput .outOfLimits -parent $w.buttonFrame -label " \# Of PVs Outside Limits" \
  -packOption "-side left -fill y" -textVariable NumOutOfLimits -width 5
$w.buttonFrame.outOfLimits configure -relief ridge -bd 2
if !$lockdown {
    APSButton .setAllLimits -parent $w.buttonFrame2 -text "Set All Limits" \
      -contextHelp "Set Lower and Upper limits for all PVs" \
      -command LimitsDialog
    if !$deltaMode {
        APSButton .changeAllTol -parent $w.buttonFrame2 -text "Change All Tolerance Levels" \
          -contextHelp "Change all tolerance values and update the limits" \
          -command ChangeAllTolerancesDialog
        APSButton .resetLimits -parent $w.buttonFrame2 -text "Reset Limits Using Tolerances" \
          -contextHelp "Change all limits by re-reading the current PV values and using the existing individual tolerances." \
          -command ResetLimits
    }
    if $deltaMode {
        APSButton .setAllMaskValues -parent $w.buttonFrame2 -text "Set mask values" \
          -contextHelp "Set value for mask PV corresponding to masked state." \
          -command MaskIfNotEqualToDialog
    }
    APSButton .changemaxReadings -parent $w.buttonFrame3 -text "Set All Max Readings" \
      -contextHelp "Change all the max reading values for current PVs." \
      -command MaxReadingsDialog
    APSButton .changeCountReqs -parent $w.buttonFrame3 -text "Set All Required Counts" \
      -contextHelp "Change all the max reading values for current PVs." \
      -command CountReqsDialog
}
set updateInterval 2.0
set appendErrorPVs 0
if !$lockdown {
    APSLabeledEntry .updateInterval -parent $w.buttonFrame3 -label " Update interval (seconds)" \
      -packOption "-side left -fill y" -textVariable updateInterval -width 5
    $w.buttonFrame3.updateInterval configure -relief ridge -bd 2

    APSRadioButtonFrame .error -parent  $w.buttonFrame4 -variable appendErrorPVs \
      -orientation horizontal -packOption "-side top"  -buttonList {Yes No} \
      -valueList {1 0} -label "Append alarmed pvs and their value to the execution command?" \
      -contextHelp "If yes, the alarmed pvs and their values will be passed to the execumtion command in format <PVname> <value> ...."
}

grid [label $w.entryFrame.l1 -text "Execute When Alarm Starts:"] -row 0 -column 0 -sticky e
grid [label $w.entryFrame.l2 -text "Execute When Alarm Stops:"] -row 1 -column 0 -sticky e
grid [label $w.entryFrame.l3 -text "Label For This Process:"] -row 2 -column 0 -sticky e
grid [label $w.entryFrame.l4 -text "RunControl PV:"] -row 3 -column 0 -stick e
grid [button $w.entryFrame.b4 -text "MEDM" -command launchRunControlMEDM] -row 3 -column 0 -stick w

if !$lockdown {
    grid [entry $w.entryFrame.e1 -textvariable executeOnAlarmStart] -row 0 -column 1 -sticky ew
    grid [entry $w.entryFrame.e2 -textvariable executeOnAlarmStop] -row 1 -column 1 -sticky ew
    grid [entry $w.entryFrame.e3 -textvariable labelForProcess] -row 2 -column 1 -sticky ew
    grid [entry $w.entryFrame.e4 -textvariable runControlPV] -row 3 -column 1 -sticky ew
} else {
    grid [label $w.entryFrame.e1 -textvariable executeOnAlarmStart] -row 0 -column 1 -sticky ew
    grid [label $w.entryFrame.e2 -textvariable executeOnAlarmStop] -row 1 -column 1 -sticky ew
    grid [label $w.entryFrame.e3 -textvariable labelForProcess] -row 2 -column 1 -sticky ew
    grid [label $w.entryFrame.e4 -textvariable runControlPV] -row 3 -column 1 -sticky ew
}
grid columnconfigure $w.entryFrame 1 -weight 1

AddPV
AddPV
AddPV
AddPV
AddPV
ScrollAdjust

if {$lockdown && ![string length $fileName]} {
    set status "Must have a filename for lockdown mode!"
    update
}

if $lockdown {
    set start 1
}

if {[string length $fileName]} {
    set status "Reading $fileName"
    update
    ReadData -fileName $fileName
    set status "Done reading ${fileName}\nSetting up widgets"
    update
    wm deiconify .
    set status "Done setting up widgets"
    update
    if {$start == "1"} {
        set status "Starting"
        update
        EnableDisableAll -caption "Enable "
    }
}
wm deiconify .
