
proc APSExecLogHelp {} {
    return "Usage: APSExecLog widget \n\[-name <string>\]\t\n\[-parent <string>\]\t\n\[-noPack 1\]\t\n\[-packOption <list>\]\t\n\[-unixCommand <string>\]\t\n\[-callback <script>\]\t\n\[-abortCallback <script>\]\t\n\[-cancelCallback <script>\]\t\n\[-width <string>\]\t\n\[-height <string>\]\t\n\[-contextHelp <string>\]\t\n\[-lineLimit <number>\]\t\n\nCreates\t\$parent\$widget.\n\tuserFrame\n\tbuttonRow.ok.button\n\tbuttonRow.cancel.button\n\tbuttonRow.print.button\n\tuserFrame.text.text\n\tuserFrame.text.scroll\n\nReturns an <id> for subsequent use in APSExecLogAbort procedure, or -1 if open failed."
}

set apsExecLogID 0
proc APSExecLog {widget args} {
    global apsContextHelp
    set parent ""
    set noPack 0
    set packOption "-side top -fill x"
    set unixCommand ""
    set contextHelp ""
    set callback ""
    set abortCallback ""
    set cancelCallback ""
    set name "Unix Command Execution"
    set width 40
    set height 15
    set lineLimit 0
    set font ""
    set killSignal 15
    set closeInput 1
    set okButton 1
    set cancelButton 1
    set abortButton 1
    APSStrictParseArguments {parent unixCommand noPack packOption contextHelp \
                               callback name width height abortCallback cancelCallback  \
                               lineLimit font killSignal closeInput okButton cancelButton abortButton}

    set windowExists [winfo exists $parent$widget]

    if {!$windowExists} {
        APSDialogBox $widget -parent $parent -name $name \
          -noPack $noPack -packOption $packOption \
          -contextHelp $contextHelp -okButton $okButton -cancelButton $cancelButton
        
        APSScrolledText .text -parent $parent$widget.userFrame -width $width \
          -height $height -font $font 
    } else {
        catch {wm title $parent$widget $name}
    }

    set log $parent$widget.userFrame.text.text

    if {!$windowExists} {
        # Put in a null command until the process pipe is open
        if $abortButton {
            APSDialogBoxAddButton .abort -parent $parent$widget -text Abort \
              -command "" \
              -contextHelp "Abort command execution, but leave window up."
        }

        APSDialogBoxAddButton .print -parent $parent$widget -text Print \
          -command "APSPrint -textWidget $log -cmd enscript" \
          -contextHelp "Print contents of window using enscript command."

        APSDialogBoxAddButton .email -parent $parent$widget -text "Email..." \
          -command "APSScrolledTextEMail -textWidget $log -modal 1" \
          -contextHelp "Send the contents of window as an email message."

        APSButton .save -parent $parent$widget.buttonRow -text "Save As..."\
          -command "APSSaveDialog .alertSave -textWidget $log" \
          -contextHelp "Save contents of window to a file."
    } else {
        catch {APSDisableButton $parent$widget.buttonRow.ok.button}
    }

    global apsExecLogLineCount  apsExecLogLineLimit
    set apsExecLogLineCount($log) 0
    set apsExecLogLineLimit($log) $lineLimit
    
    #    set cmd1 "csh -f -c \"$unixCommand\" |& cat -u"
    set cmd1 "$unixCommand |& cat -u"
    # If at end now, then reset to end afterward
    if {[lindex [$log yview] 1] == 1} {
        set gotoend 1
    } else {
        set gotoend 0
    }
    if {[catch {open |$cmd1 r} input]} {
        $log insert end $input\n
        if {$gotoend} {$log see end}
        set pipePid -1
        return -1
    } else {
        set pipePid [pid $input]
        fileevent $input readable \
          [list APSExecLogCallback $parent$widget $input $log $callback]
        $log insert end $unixCommand\n
        if {$gotoend} {$log see end}
    }
    catch {$parent$widget.buttonRow.cancel.button configure -command \
             [list APSExecLogStop $parent$widget $input $pipePid $cancelCallback $killSignal]}
    catch {$parent$widget.buttonRow.abort.button configure \
             -command [list APSExecLogAbortInternal $input $pipePid $abortCallback $killSignal $closeInput]}
    return [list $input $parent$widget $pipePid]
}

#
# Public procedure which allows user to Abort the unixCommand, and
#  optionally destroy the ExecLog widget.
#

proc APSExecLogAbortHelp {} {
    return "Usage: APSExecLogAbort \[-id <id>\] \[-destroy 1\]"
}

proc APSExecLogAbort {args} {
    set id -1
    set destroy 0
    APSStrictParseArguments {id destroy}

    set input [lindex $id 0]
    if {[catch {pid $input} pipePid]} {
        set kill 0
    } else {
        set kill 1
    }

    if {$kill} {
        catch {eval exec kill $pipePid}
        catch {close $input}
    }
    if {$destroy} {
        set widget [lindex $id 1]
        catch {destroy $widget}
    }
}

proc APSExecLogCallback {widget input log callback} {
    if {[eof $input]} {
        catch {close $input}
        catch {APSEnableButton $widget.buttonRow.ok.button}
        if {[winfo exists $log]} {
            set output [ $log get 0.0 end]
            set output [ split $output "\n"]
            set linewidth ""
            foreach line $output {
                lappend linewidth [string length $line]
            }
            set linewidth [ lsort -integer -decreasing $linewidth ]
            set w 0
            foreach width $linewidth {
                if {$width < 150} {
                    set w $width
                    break
                }
            }
            if { $w > [ $log cget -width] } {
                $log  configure -width  [expr $w+5 ]
            }
        }
        # used to be 'eval callback', really want it done at global level
        uplevel #0 $callback
    } else {
        # Add new output, but don't disturb view if user has scrolled up
        set oldend 0
        if {![catch {$log yview} view]} {
            set oldend [lindex $view 1]
        } 
        gets $input line
        # Scan for any bell chars. Strip out the char, and do a tk bell.
        set ix [string first "\007" $line]
        while {$ix != -1} {
            bell
            set line "[string range $line 0 [expr {$ix - 1}]][string range $line [expr {$ix + 1}] end]"
            set ix [string first "\007" $line]
        }

        catch {$log insert end $line\n}
        if {$oldend == 1} {
            catch {$log see end}
        }
        global apsExecLogLineCount  apsExecLogLineLimit
        incr apsExecLogLineCount($log)
        if {$apsExecLogLineLimit($log) && \
              $apsExecLogLineCount($log)>$apsExecLogLineLimit($log)} {
            catch {$log delete 1.0 2.0}
            incr apsExecLogLineCount($log) -1
        }
    }
}

proc APSExecLogStop {widget input pipePid callback signal} {
    catch "exec kill -$signal $pipePid" 
    catch {close $input}
    destroy $widget
    if {[string length $callback]} {
        uplevel \#0 $callback
    }
}

proc APSExecLogAbortInternal {input pipePid callback signal closeInput} {
    if $closeInput {
        catch "exec kill -$signal $pipePid"
        catch {close $input}
    } else {
        catch "exec kill -$signal [lindex $pipePid 0]"
    }
    if {[string length $callback]} {
        uplevel \#0 $callback
    }
}

# Replaced by APSPrint. This kept for compatibility only.
proc APSExecLogPrint {textwidget cmd} {
    APSPrint -textWidget $textwidget -cmd $cmd
}

proc APSExecHelp {} {
    return "Usage: APSExec\n\[-unixCommand <string>\]\t\n\[-callback <script>\]\t\n\[-outputVariable <variable>\]\t\n\nExecutes command given by -unixCommand option.\nIf -callback is given, <script> will be executed upon\ncompletion of unixCommand. Output of unixCommand\nis normally thrown away. If -outputVariable is given,\ncommand output will be stored in global <variable>."
}
proc APSExec {args} {
    set unixCommand ""
    set callback ""
    set outputVariable ""
    APSStrictParseArguments {unixCommand callback outputVariable}

    if {$outputVariable != ""} {
        set saveOutput 1
        global $outputVariable
    } else {
        set saveOutput 0
    }
    #    set cmd1 "csh -f -c \"$unixCommand\" |& cat"
    set cmd1 "$unixCommand |& cat -u"
    if {[catch {open |$cmd1 r} input]} {
        if {$saveOutput} {
            set $outputVariable $input
        }
    } else {
        fileevent $input readable \
          [list APSExecCallback $input $callback $outputVariable $saveOutput]
        set $outputVariable ""
        return $input
    }
}

proc APSExecCallback {input callback outputVariable saveOutput} {
    if {$saveOutput} {
        global $outputVariable
    }
    if {[eof $input]} {
        catch {close $input}
        uplevel #0 $callback
        # used to be 'eval $callback', really want this done at global level
    } else {
        gets $input line
        if {$saveOutput} {
            set $outputVariable [subst $$outputVariable]$line\n
        }
    }
}

proc APSBGExecHelp {} {
    return "Usage: APSBGExec\n\[-command <string>\]\t\n\[-output <variable>\]\t\n\[-error <variable>\]\t\n\[-status <variable>\]\t\n\nExecutes command given by -command option.\nWorks like the normal exec command where you can catch the results and errors. If the -output, -error, and or status variables are used the related information will be stored in those global variables. One advantage of using APSBGExec over exec is that there will be a busy indicator in the menu bar and the executable is killable if necessary."
}

proc APSBGExec {args} {
    set output "apsBGExecOutput"
    set error "apsBGExecError"
    set status "apsBGExecStatus"
    set command ""
    APSStrictParseArguments {output error status command}
    global $error $output $status apsBGExecKill apsExecRenamed
    set blt 1
    if {[catch {package require BLT} result]} {
        set blt 0
    }
    set $output ""
    set $error ""
    set $status ""

    set apsBGExecKill 0
    if {([llength [info commands winfo]]) && ([winfo exists .menu.working])} {
        APSBusyIndicatorStart -killVariable $status
    }
    if {([string index [string trimright $command] end] == "&") ||
        ([string first ">" $command] != -1)} {
        set blt 0
    }
    if {$blt} {
        if {[catch {eval blt::bgexec $status -output $output -error $error $command} result]} {
            if {([llength [info commands winfo]]) && ([winfo exists .menu.working])} {
                APSBusyIndicatorStop
            }
            if {$apsBGExecKill} {
                return -code error "APSBGExec command killed by user."
            }
            if {![llength [set $error]]} {
                set $error $result
            }
            return -code error "[set $error]"
        }
    } else {
        if {$apsExecRenamed} {
            set execcommand aps_tcl_exec
        } else {
            set execcommand exec
        }
        if {[catch {eval $execcommand $command} result]} {
            if {([llength [info commands winfo]]) && ([winfo exists .menu.working])} {
                APSBusyIndicatorStop
            }
            set $error $result
            set $status ""
            return -code error "[set $error]"
        } else {
            if {([llength [info commands winfo]]) && ([winfo exists .menu.working])} {
                APSBusyIndicatorStop
            }
            set $output $result
            set $status ""
            return -code ok "[set $output]"
        }
    }
    if {$apsBGExecKill} {
        if {([llength [info commands winfo]]) && ([winfo exists .menu.working])} {
            APSBusyIndicatorStop
        }
        return -code error "APSBGExec command killed by user."
    }
    if {([llength [info commands winfo]]) && ([winfo exists .menu.working])} {
        APSBusyIndicatorStop
    }
    return -code ok "[set $output]"
}

proc APSBusyIndicatorStartHelp {} {
    return "Usage: APSBusyIndicatorStart \n\[-killVariable <variable>\]\t\n\nThis procedure is used to start the busy indicator ont he menu bar. If the killVariable option is passed it is assumed that the bgexec command is being called and that setting this variable will kill the bgexec command. This is done by the user pressing the stop sign button."
}

proc APSBusyIndicatorStart { args } {
    if {![llength [info commands winfo]]} {
        return
    }
    if {![winfo exists .menu.working]} {
        return
    }
    set killVariable ""
    APSStrictParseArguments {killVariable}
    global apsBusyIndicatorWorking apsBGExecKill
    set apsBusyIndicatorWorking 1
    .menu.working configure -image working1
    set apsBGExecKill 0
    if {[llength $killVariable]} {
        .menu.stop configure -image stopsign
        .menu.stop configure  -command "set apsBGExecKill 1 ; set $killVariable 1"
    } else {
        .menu.stop configure  -command ""
    }
    update
    after 100 "APSBusyIndicatorContinue"
}

proc APSBusyIndicatorContinue { args } {
    global apsBusyIndicatorWorking
    if {$apsBusyIndicatorWorking} {
        set i [string index [.menu.working cget -image] end]
        if {($i < 1) || ($i > 4)} { set i 1 }
        incr i
        if {$i > 4} {set i 1}
        .menu.working configure -image working$i
        after 2000 "APSBusyIndicatorContinue"
    }
}

proc APSBusyIndicatorStopHelp {} {
    return "Usage: APSBusyIndicatorStop\t\n\nUsed with APSBusyIndicatorStart to stop the busy indicator."
}
proc APSBusyIndicatorStop { args } {
    if {![llength [info commands winfo]]} {
        return
    }
    if {![winfo exists .menu.working]} {
        return
    }
    global apsBusyIndicatorWorking
    APSSetVarAndUpdate apsBusyIndicatorWorking 0
    .menu.stop configure -image nostopsign
    .menu.working configure -image working
}

proc APSExecReplacement {args} {
    if [catch {APSBGExec -command "$args"} result] {
        return -code error "$result"
    }
    return -code ok "$result"
}

proc APSRenameExecToAPSBGExecHelp {} {
    return "Usage: APSRenameExectoAPSBGExec\t\n\nAfter this is called every exec command is actually using APSBGExec. The original exec command will be renamed to aps_tcl_exec."
}

set apsExecRenamed 0
proc APSRenameExecToAPSBGExec {args} {
    global apsExecRenamed

    # Disable due to performance problems.
    return 

    if {$apsExecRenamed == 0} {
        set apsExecRenamed 1
        rename exec aps_tcl_exec
        rename APSExecReplacement exec
    }
}
